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 <robb...@gentoo.org> (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 9266bcfae..430b42f5f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3844,7 +3844,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> | @@ -3935,13 +3935,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 682c2149f..06e8cdc4c 100644 --- a/doc/lua-api/index.rst +++ b/doc/lua-api/index.rst @@ -885,13 +885,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: @@ -1512,13 +1514,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 4ed96e305..65891d333 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -106,7 +106,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 b97f9bf9a..45ffc96a3 100644 --- a/include/types/action.h +++ b/include/types/action.h @@ -134,6 +134,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 fb3286a1f..3f7bcbf80 100644 --- a/include/types/applet.h +++ b/include/types/applet.h @@ -125,6 +125,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 31bd72d34..8e4aaf4a4 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -3504,6 +3504,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; @@ -3914,6 +3915,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); @@ -3921,6 +3923,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; } @@ -3973,12 +3976,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); @@ -4532,17 +4539,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 6151c848d..cffbf2d2e 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -12398,14 +12398,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); @@ -12416,9 +12416,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; @@ -12464,7 +12465,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; } @@ -12539,7 +12540,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; } @@ -12551,6 +12552,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