Welcome! Log In Create A New Profile

Advanced

[PATCH-1.7] MINOR: http: custom status reason.

Posted by Robin H. Johnson 
Robin H. Johnson
[PATCH-1.7] MINOR: http: custom status reason.
January 01, 2017 10:20PM
The older 'rsprep' directive allows modification of the status reason.

Extend 'http-response set-status' to take an optional string of the new
status reason.

http-response set-status 418 reason "I'm a coffeepot"

Matching updates in Lua code:
- AppletHTTP.set_status
- HTTP.res_set_status

Signed-off-by: Robin H. Johnson <[email protected]>
(cherry picked from commit 4ce5080b32cfc8591f5639e740a1a83079e9a308)
---
doc/configuration.txt | 9 ++++++---
doc/lua-api/index.rst | 11 +++++++----
include/proto/proto_http.h | 2 +-
include/types/action.h | 1 +
include/types/applet.h | 1 +
src/hlua.c | 14 +++++++++++---
src/proto_http.c | 23 +++++++++++++++++------
tests/setstatus.lua | 26 ++++++++++++++++++++++++++
tests/test-http-set-status-lua.cfg | 31 +++++++++++++++++++++++++++++++
tests/test-http-set-status.cfg | 32 ++++++++++++++++++++++++++++++++
10 files changed, 133 insertions(+), 17 deletions(-)
create mode 100644 tests/setstatus.lua
create mode 100644 tests/test-http-set-status-lua.cfg
create mode 100644 tests/test-http-set-status.cfg

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6795166f5..ef1f6832d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4122,7 +4122,7 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
set-header <name> <fmt> | del-header <name> |
replace-header <name> <regex-match> <replace-fmt> |
replace-value <name> <regex-match> <replace-fmt> |
- set-status <status> |
+ set-status <status> [reason <str>] |
set-log-level <level> | set-mark <mark> | set-tos <tos> |
add-acl(<file name>) <key fmt> |
del-acl(<file name>) <key fmt> |
@@ -4215,13 +4215,16 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
Cache-Control: max-age=3600, private

- "set-status" replaces the response status code with <status> which must
- be an integer between 100 and 999. Note that the reason is automatically
- adapted to the new code.
+ be an integer between 100 and 999. Optionally, a custom reason text can be
+ provided defined by <str>, or the default reason for the specified code
+ will be used as a fallback.

Example:

# return "431 Request Header Fields Too Large"
http-response set-status 431
+ # return "503 Slow Down", custom reason
+ http-response set-status 503 reason "Slow Down".

- "set-nice" sets the "nice" factor of the current request being processed.
It only has effect against the other requests being processed at the same
diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index 22c053884..e4174cf6f 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -1347,13 +1347,15 @@ HTTP class
:param class_http http: The related http object.
:param string uri: The new uri.

-.. js:function:: HTTP.res_set_status(http, status)
+.. js:function:: HTTP.res_set_status(http, status [, reason])

- Rewrites the response status code with the parameter "code". Note that the
- reason is automatically adapted to the new code.
+ Rewrites the response status code with the parameter "code".
+
+ If no custom reason is provided, it will be generated from the status.

:param class_http http: The related http object.
:param integer status: The new response status code.
+ :param string reason: The new response reason (optional).

.. _txn_class:

@@ -1982,13 +1984,14 @@ AppletHTTP class

Contains an array containing all the request headers.

-.. js:function:: AppletHTTP.set_status(applet, code)
+.. js:function:: AppletHTTP.set_status(applet, code [, reason])

This function sets the HTTP status code for the response. The allowed code are
from 100 to 599.

:param class_AppletHTTP applet: An :ref:`applethttp_class`
:param integer code: the status code returned to the client.
+ :param string reason: the status reason returned to the client (optional).

.. js:function:: AppletHTTP.add_header(applet, name, value)

diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 5ee2e2fa4..6c81766aa 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -108,7 +108,7 @@ int http_header_match2(const char *hdr, const char *end, const char *name, int l
int http_remove_header2(struct http_msg *msg, struct hdr_idx *idx, struct hdr_ctx *ctx);
int http_header_add_tail2(struct http_msg *msg, struct hdr_idx *hdr_idx, const char *text, int len);
int http_replace_req_line(int action, const char *replace, int len, struct proxy *px, struct stream *s);
-void http_set_status(unsigned int status, struct stream *s);
+void http_set_status(unsigned int status, const char *reason, struct stream *s);
int http_transform_header_str(struct stream* s, struct http_msg *msg, const char* name,
unsigned int name_len, const char *str, struct my_regex *re,
int action);
diff --git a/include/types/action.h b/include/types/action.h
index 5a70db06c..1c1715492 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -135,6 +135,7 @@ struct act_rule {
} cap;
struct {
unsigned int code; /* HTTP status code */
+ const char *reason; /* HTTP status reason */
} status;
struct {
struct sample_expr *expr;
diff --git a/include/types/applet.h b/include/types/applet.h
index 89602aac5..aa3a9d259 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -137,6 +137,7 @@ struct appctx {
int left_bytes; /* The max amount of bytes that we can read. */
int flags;
int status;
+ const char *reason;
struct task *task;
} hlua_apphttp;
struct {
diff --git a/src/hlua.c b/src/hlua.c
index 6889f2fed..39d6148fc 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -3482,6 +3482,7 @@ static int hlua_applet_http_new(lua_State *L, struct appctx *ctx)
lua_rawseti(L, -2, 0);
appctx->appctx = ctx;
appctx->appctx->ctx.hlua_apphttp.status = 200; /* Default status code returned. */
+ appctx->appctx->ctx.hlua_apphttp.reason = NULL; /* Use default reason based on status */
appctx->htxn.s = s;
appctx->htxn.p = px;

@@ -3923,6 +3924,7 @@ __LJMP static int hlua_applet_http_status(lua_State *L)
{
struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
int status = MAY_LJMP(luaL_checkinteger(L, 2));
+ const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL));

if (status < 100 || status > 599) {
lua_pushboolean(L, 0);
@@ -3930,6 +3932,7 @@ __LJMP static int hlua_applet_http_status(lua_State *L)
}

appctx->appctx->ctx.hlua_apphttp.status = status;
+ appctx->appctx->ctx.hlua_apphttp.reason = reason;
lua_pushboolean(L, 1);
return 1;
}
@@ -3982,12 +3985,16 @@ __LJMP static int hlua_applet_http_start_response(lua_State *L)
int hdr_connection = 0;
int hdr_contentlength = -1;
int hdr_chunked = 0;
+ const char *reason = appctx->appctx->ctx.hlua_apphttp.reason;
+
+ if (reason == NULL)
+ reason = get_reason(appctx->appctx->ctx.hlua_apphttp.status);

/* Use the same http version than the request. */
chunk_appendf(tmp, "HTTP/1.%c %d %s\r\n",
appctx->appctx->ctx.hlua_apphttp.flags & APPLET_HTTP11 ? '1' : '0',
appctx->appctx->ctx.hlua_apphttp.status,
- get_reason(appctx->appctx->ctx.hlua_apphttp.status));
+ reason);

/* Get the array associated to the field "response" in the object AppletHTTP. */
lua_pushvalue(L, 0);
@@ -4541,17 +4548,18 @@ static int hlua_http_req_set_uri(lua_State *L)
return 1;
}

-/* This function set the response code. */
+/* This function set the response code & optionally reason. */
static int hlua_http_res_set_status(lua_State *L)
{
struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
unsigned int code = MAY_LJMP(luaL_checkinteger(L, 2));
+ const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL));

/* Check if a valid response is parsed */
if (unlikely(htxn->s->txn->rsp.msg_state < HTTP_MSG_BODY))
return 0;

- http_set_status(code, htxn->s);
+ http_set_status(code, reason, htxn->s);
return 0;
}

diff --git a/src/proto_http.c b/src/proto_http.c
index aa8d9973e..228473788 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -12417,14 +12417,14 @@ int http_replace_req_line(int action, const char *replace, int len,
/* This function replace the HTTP status code and the associated message. The
* variable <status> contains the new status code. This function never fails.
*/
-void http_set_status(unsigned int status, struct stream *s)
+void http_set_status(unsigned int status, const char *reason, struct stream *s)
{
struct http_txn *txn = s->txn;
char *cur_ptr, *cur_end;
int delta;
char *res;
int c_l;
- const char *msg;
+ const char *msg = reason;
int msg_len;

chunk_reset(&trash);
@@ -12435,9 +12435,10 @@ void http_set_status(unsigned int status, struct stream *s)
trash.str[c_l] = ' ';
trash.len = c_l + 1;

- msg = get_reason(status);
+ /* Do we have a custom reason format string? */
+ if (msg == NULL)
+ msg = get_reason(status);
msg_len = strlen(msg);
-
strncpy(&trash.str[trash.len], msg, trash.size - trash.len);
trash.len += msg_len;

@@ -12483,7 +12484,7 @@ enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px
enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
struct session *sess, struct stream *s, int flags)
{
- http_set_status(rule->arg.status.code, s);
+ http_set_status(rule->arg.status.code, rule->arg.status.reason, s);
return ACT_RET_CONT;
}

@@ -12559,7 +12560,7 @@ enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struc

/* Check if an argument is available */
if (!*args[*orig_arg]) {
- memprintf(err, "expects exactly 1 argument <status>");
+ memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
return ACT_RET_PRS_ERR;
}

@@ -12571,6 +12572,16 @@ enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struc
}

(*orig_arg)++;
+
+ /* scustom reason string */
+ rule->arg.status.reason = NULL; // If null, we use the default reason for the status code.
+ if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
+ (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
+ (*orig_arg)++;
+ rule->arg.status.reason = strdup(args[*orig_arg]);
+ (*orig_arg)++;
+ }
+
return ACT_RET_PRS_OK;
}

diff --git a/tests/setstatus.lua b/tests/setstatus.lua
new file mode 100644
index 000000000..e2eafe12f
--- /dev/null
+++ b/tests/setstatus.lua
@@ -0,0 +1,26 @@
+-- http-response actions
+core.register_action("set-status-418-defaultreason", {"http-res"}, function(txn)
+ txn.http:res_set_status(418)
+end)
+core.register_action("set-status-418-customreason", {"http-res"}, function(txn)
+ txn.http:res_set_status(418, "I'm a coffeepot")
+end)
+
+-- http services
+core.register_service("http418-default", "http", function(applet)
+ local response = "Hello World !"
+ applet:set_status(418)
+ applet:add_header("content-length", string.len(response))
+ applet:add_header("content-type", "text/plain")
+ applet:start_response()
+ applet:send(response)
+end)
+
+core.register_service("http418-coffeepot", "http", function(applet)
+ local response = "Hello World !"
+ applet:set_status(418, "I'm a coffeepot")
+ applet:add_header("content-length", string.len(response))
+ applet:add_header("content-type", "text/plain")
+ applet:start_response()
+ applet:send(response)
+end)
diff --git a/tests/test-http-set-status-lua.cfg b/tests/test-http-set-status-lua.cfg
new file mode 100644
index 000000000..485a1ee32
--- /dev/null
+++ b/tests/test-http-set-status-lua.cfg
@@ -0,0 +1,31 @@
+global
+ maxconn 100
+ lua-load setstatus.lua
+
+defaults
+ mode http
+ timeout client 10000
+ timeout server 10000
+ timeout connect 10000
+
+# Expect HTTP/1.1 418 I'm a teapot
+listen lua-service-set-status-defaultreason
+ bind :8003
+ http-request use-service lua.http418-default
+
+# Expect HTTP/1.1 418 I'm a coffeepot
+listen lua-service-set-status-customreason
+ bind :8004
+ http-request use-service lua.http418-coffeepot
+
+# Expect HTTP/1.1 418 I'm a teapot
+listen lua-action-set-status-defaultreason
+ bind :8005
+ http-response lua.set-status-418-defaultreason
+ server host 127.0.0.1:8080
+
+# Expect HTTP/1.1 418 I'm a coffeepot
+listen lua-action-set-status-customreason
+ bind :8006
+ http-response lua.set-status-418-customreason
+ server host 127.0.0.1:8080
diff --git a/tests/test-http-set-status.cfg b/tests/test-http-set-status.cfg
new file mode 100644
index 000000000..0c66b1639
--- /dev/null
+++ b/tests/test-http-set-status.cfg
@@ -0,0 +1,32 @@
+global
+ maxconn 100
+
+defaults
+ mode http
+ timeout client 10000
+ timeout server 10000
+ timeout connect 10000
+
+# Expect HTTP/1.1 418 I'm a teapot
+listen http-response-set-status-defaultreason
+ bind :8001
+ server host 127.0.0.1:8080
+ http-response set-status 418
+
+# Expect HTTP/1.1 418 I'm a coffeepot
+listen http-response-set-status-customreason
+ bind :8002
+ server host 127.0.0.1:8080
+ http-response set-status 418 reason "I'm a coffeepot"
+
+# Expect config parse fail
+#listen parse-fail-string
+# bind :8002
+# server host 127.0.0.1:8080
+# http-response set-status 418 reason
+
+# Expect config parse fail
+#listen parse-fail-keyword
+# bind :8002
+# server host 127.0.0.1:8080
+# http-response set-status 418 "Missing reason keyword"
--
2.11.0.rc2
Willy Tarreau
Re: [PATCH-1.7] MINOR: http: custom status reason.
January 06, 2017 07:50PM
On Sun, Jan 01, 2017 at 01:16:03PM -0800, Robin H. Johnson wrote:
> The older 'rsprep' directive allows modification of the status reason.
>
> Extend 'http-response set-status' to take an optional string of the new
> status reason.
(...)

now backported, thanks for taking care of it, it saved me some time.

Willy
Sorry, only registered users may post in this forum.

Click here to login