mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-05 22:56:57 +02:00
MINOR: http-rules: add a new "ignore-empty" option to redirects.
Sometimes it is convenient to remap large sets of URIs to new ones (e.g. after a site migration for example). This can be achieved using "http-request redirect" combined with maps, but one difficulty there is that non-matching entries will return an empty response. In order to avoid this, duplicating the operation as an ACL condition ending in "-m found" is possible but it becomes complex and error-prone while it's known that an empty URL is not valid in a location header. This patch addresses this by improving the redirect rules to be able to simply ignore the rule and skip to the next one if the result of the evaluation of the "location" expression is empty. However in order not to break existing setups, it requires a new "ignore-empty" keyword. There used to be an ACT_FLAG_FINAL on redirect rules that's used during the parsing to emit a warning if followed by another rule, so here we only set it if the option is not there. The http_apply_redirect_rule() function now returns a 3rd value to mention that it did nothing and that this was not an error, so that callers can just ignore the rule. The regular "redirect" rules were not modified however since this does not apply there. The map_redirect VTC was completed with such a test and updated to 2.5 and an example was added into the documentation.
This commit is contained in:
parent
b061fb31ab
commit
bc1223be79
@ -10015,6 +10015,13 @@ redirect scheme <sch> [code <code>] <option> [{if | unless} <condition>]
|
||||
It can be useful to ensure that search engines will only see one URL.
|
||||
For this, a return code 301 is preferred.
|
||||
|
||||
- "ignore-empty"
|
||||
This keyword only has effect when a location is produced using a log
|
||||
format expression (i.e. when used in http-request or http-response).
|
||||
It indicates that if the result of the expression is empty, the rule
|
||||
should silently be skipped. The main use is to allow mass-redirects
|
||||
of known paths using a simple map.
|
||||
|
||||
- "set-cookie NAME[=value]"
|
||||
A "Set-Cookie" header will be added with NAME (and optionally "=value")
|
||||
to the response. This is sometimes used to indicate that a user has
|
||||
@ -10057,6 +10064,10 @@ redirect scheme <sch> [code <code>] <option> [{if | unless} <condition>]
|
||||
http://www.%[hdr(host)]%[capture.req.uri] \
|
||||
unless { hdr_beg(host) -i www }
|
||||
|
||||
Example: permanently redirect only old URLs to new ones
|
||||
http-request redirect code 301 location \
|
||||
%[path,map_str(old-blog-articles.map)] ignore-empty
|
||||
|
||||
See section 7 about ACL usage.
|
||||
|
||||
|
||||
|
@ -105,6 +105,7 @@ enum {
|
||||
REDIRECT_FLAG_DROP_QS = 1, /* drop query string */
|
||||
REDIRECT_FLAG_APPEND_SLASH = 2, /* append a slash if missing at the end */
|
||||
REDIRECT_FLAG_FROM_REQ = 4, /* redirect rule on the request path */
|
||||
REDIRECT_FLAG_IGNORE_EMPTY = 8, /* silently ignore empty location expressions */
|
||||
};
|
||||
|
||||
/* Redirect types (location, prefix, extended ) */
|
||||
|
@ -1,3 +1,5 @@
|
||||
# These entries are used for http-request redirect rules
|
||||
example.org https://www.example.org
|
||||
subdomain.example.org https://www.subdomain.example.org
|
||||
|
||||
/path/to/old/file /path/to/new/file
|
||||
|
@ -1,5 +1,5 @@
|
||||
varnishtest "haproxy host header: map / redirect tests"
|
||||
#REQUIRE_OPTIONS=PCRE|PCRE2
|
||||
feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.5-dev5) && (feature(PCRE) || feature(PCRE2))'"
|
||||
feature ignore_unknown_macro
|
||||
|
||||
|
||||
@ -43,6 +43,9 @@ haproxy h1 -conf {
|
||||
frontend fe1
|
||||
bind "fd@${fe1}"
|
||||
|
||||
# automatically redirect matching paths from maps but skip rule on no-match
|
||||
http-request redirect code 301 location %[path,map_str(${testdir}/map_redirect.map)] ignore-empty
|
||||
|
||||
# redirect Host: example.org / subdomain.example.org
|
||||
http-request redirect prefix %[req.hdr(Host),lower,regsub(:\d+$,,),map_str(${testdir}/map_redirect.map)] code 301 if { hdr(Host),lower,regsub(:\d+$,,),map_str(${testdir}/map_redirect.map) -m found }
|
||||
|
||||
@ -83,6 +86,12 @@ client c1 -connect ${h1_fe1_sock} {
|
||||
rxresp
|
||||
expect resp.status == 301
|
||||
expect resp.http.location ~ "https://www.example.org"
|
||||
|
||||
txreq -url /path/to/old/file
|
||||
rxresp
|
||||
expect resp.status == 301
|
||||
expect resp.http.location ~ "/path/to/new/file"
|
||||
|
||||
# Closes connection
|
||||
} -run
|
||||
|
||||
@ -145,7 +154,7 @@ client c7 -connect ${h1_fe1_sock} {
|
||||
# cli show maps
|
||||
haproxy h1 -cli {
|
||||
send "show map ${testdir}/map_redirect.map"
|
||||
expect ~ "^0x[a-f0-9]+ example\\.org https://www\\.example\\.org\\n0x[a-f0-9]+ subdomain\\.example\\.org https://www\\.subdomain\\.example\\.org\\n$"
|
||||
expect ~ "^0x[a-f0-9]+ example\\.org https://www\\.example\\.org\\n0x[a-f0-9]+ subdomain\\.example\\.org https://www\\.subdomain\\.example\\.org\\n0x[a-f0-9]+ /path/to/old/file /path/to/new/file\n$"
|
||||
|
||||
send "show map ${testdir}/map_redirect-be.map"
|
||||
expect ~ "^0x[a-f0-9]+ test1\\.example\\.com test1_be\\n0x[a-f0-9]+ test1\\.example\\.invalid test1_be\\n0x[a-f0-9]+ test2\\.example\\.com test2_be\\n$"
|
||||
|
@ -1767,7 +1767,6 @@ static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg,
|
||||
int dir, cur_arg;
|
||||
|
||||
rule->action = ACT_HTTP_REDIR;
|
||||
rule->flags |= ACT_FLAG_FINAL;
|
||||
rule->release_ptr = release_http_redir;
|
||||
|
||||
cur_arg = *orig_arg;
|
||||
@ -1776,6 +1775,9 @@ static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg,
|
||||
if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
|
||||
return ACT_RET_PRS_ERR;
|
||||
|
||||
if (!(redir->flags & REDIRECT_FLAG_IGNORE_EMPTY))
|
||||
rule->flags |= ACT_FLAG_FINAL;
|
||||
|
||||
rule->arg.redir = redir;
|
||||
rule->cond = redir->cond;
|
||||
redir->cond = NULL;
|
||||
|
@ -2338,8 +2338,9 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit
|
||||
}
|
||||
|
||||
/* Perform an HTTP redirect based on the information in <rule>. The function
|
||||
* returns zero on success, or zero in case of a, irrecoverable error such
|
||||
* as too large a request to build a valid response.
|
||||
* returns zero in case of an irrecoverable error such as too large a request
|
||||
* to build a valid response, 1 in case of successful redirect (hence the rule
|
||||
* is final), or 2 if the rule has to be silently skipped.
|
||||
*/
|
||||
int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn)
|
||||
{
|
||||
@ -2482,9 +2483,13 @@ int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struc
|
||||
}
|
||||
else {
|
||||
/* add location with executing log format */
|
||||
chunk->data += build_logline(s, chunk->area + chunk->data,
|
||||
chunk->size - chunk->data,
|
||||
&rule->rdr_fmt);
|
||||
int len = build_logline(s, chunk->area + chunk->data,
|
||||
chunk->size - chunk->data,
|
||||
&rule->rdr_fmt);
|
||||
if (!len && rule->flags & REDIRECT_FLAG_IGNORE_EMPTY)
|
||||
return 2;
|
||||
|
||||
chunk->data += len;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -2791,11 +2796,15 @@ static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct lis
|
||||
rule_ret = HTTP_RULE_RES_DENY;
|
||||
goto end;
|
||||
|
||||
case ACT_HTTP_REDIR:
|
||||
rule_ret = HTTP_RULE_RES_ABRT;
|
||||
if (!http_apply_redirect_rule(rule->arg.redir, s, txn))
|
||||
rule_ret = HTTP_RULE_RES_ERROR;
|
||||
case ACT_HTTP_REDIR: {
|
||||
int ret = http_apply_redirect_rule(rule->arg.redir, s, txn);
|
||||
|
||||
if (ret == 2) // 2 == skip
|
||||
break;
|
||||
|
||||
rule_ret = ret ? HTTP_RULE_RES_ABRT : HTTP_RULE_RES_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* other flags exists, but normally, they never be matched. */
|
||||
default:
|
||||
@ -2916,12 +2925,15 @@ static enum rule_result http_res_get_intercept_rule(struct proxy *px, struct lis
|
||||
rule_ret = HTTP_RULE_RES_DENY;
|
||||
goto end;
|
||||
|
||||
case ACT_HTTP_REDIR:
|
||||
rule_ret = HTTP_RULE_RES_ABRT;
|
||||
if (!http_apply_redirect_rule(rule->arg.redir, s, txn))
|
||||
rule_ret = HTTP_RULE_RES_ERROR;
|
||||
goto end;
|
||||
case ACT_HTTP_REDIR: {
|
||||
int ret = http_apply_redirect_rule(rule->arg.redir, s, txn);
|
||||
|
||||
if (ret == 2) // 2 == skip
|
||||
break;
|
||||
|
||||
rule_ret = ret ? HTTP_RULE_RES_ABRT : HTTP_RULE_RES_ERROR;
|
||||
goto end;
|
||||
}
|
||||
/* other flags exists, but normally, they never be matched. */
|
||||
default:
|
||||
break;
|
||||
|
@ -379,6 +379,9 @@ struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, st
|
||||
else if (strcmp(args[cur_arg], "append-slash") == 0) {
|
||||
flags |= REDIRECT_FLAG_APPEND_SLASH;
|
||||
}
|
||||
else if (strcmp(args[cur_arg], "ignore-empty") == 0) {
|
||||
flags |= REDIRECT_FLAG_IGNORE_EMPTY;
|
||||
}
|
||||
else if (strcmp(args[cur_arg], "if") == 0 ||
|
||||
strcmp(args[cur_arg], "unless") == 0) {
|
||||
cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + cur_arg, errmsg);
|
||||
@ -390,7 +393,7 @@ struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, st
|
||||
}
|
||||
else {
|
||||
memprintf(errmsg,
|
||||
"expects 'code', 'prefix', 'location', 'scheme', 'set-cookie', 'clear-cookie', 'drop-query' or 'append-slash' (was '%s')",
|
||||
"expects 'code', 'prefix', 'location', 'scheme', 'set-cookie', 'clear-cookie', 'drop-query', 'ignore-empty' or 'append-slash' (was '%s')",
|
||||
args[cur_arg]);
|
||||
return NULL;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user