[MEDIUM] appsession: add "len", "prefix" and "mode" options

To sum up :
- len : it's now the max number of characters for the value, preventing
  garbaged results.
- a new option "prefix" is added, this allows to use dynamic cookie
  names (e.g. ASPSESSIONIDXXX).

Previously in the thread, I wanted to use the value found with
"capture cookie" but when i started to update the documentation, I
found this solution quite weird. I've made a small rework to not
depend on "capture cookie".

- There's the posssiblity to define the URL parser mode (path parameters
  or query string).
This commit is contained in:
Cyril Bonté 2009-11-29 20:04:48 +01:00 committed by Willy Tarreau
parent fa355d4a51
commit b21570ae0f
5 changed files with 181 additions and 56 deletions

View File

@ -861,7 +861,8 @@ acl <aclname> <criterion> [flags] [operator] <value> ...
See section 7 about ACL usage. See section 7 about ACL usage.
appsession <cookie> len <length> timeout <holdtime> [request-learn] appsession <cookie> len <length> timeout <holdtime>
[request-learn] [prefix] [mode <path-parameters|query-string>]
Define session stickiness on an existing application cookie. Define session stickiness on an existing application cookie.
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
no | no | yes | yes no | no | yes | yes
@ -869,7 +870,7 @@ appsession <cookie> len <length> timeout <holdtime> [request-learn]
<cookie> this is the name of the cookie used by the application and which <cookie> this is the name of the cookie used by the application and which
HAProxy will have to learn for each new session. HAProxy will have to learn for each new session.
<length> this is the number of characters that will be memorized and <length> this is the max number of characters that will be memorized and
checked in each cookie value. checked in each cookie value.
<holdtime> this is the time after which the cookie will be removed from <holdtime> this is the time after which the cookie will be removed from
@ -884,13 +885,34 @@ appsession <cookie> len <length> timeout <holdtime> [request-learn]
the application's session and the correct server is selected. the application's session and the correct server is selected.
It is recommended to specify this option to improve reliability. It is recommended to specify this option to improve reliability.
prefix When this option is specified, haproxy will match on the cookie
prefix (or URL parameter prefix). The appsession value is the
data following this prefix.
Example :
appsession ASPSESSIONID len 64 timeout 3h prefix
This will match the cookie ASPSESSIONIDXXXX=XXXXX,
the appsession value will be XXXX=XXXXX.
mode This option allows to change the URL parser mode.
2 modes are currently supported :
- path-parameters :
The parser looks for the appsession in the path parameters
part (each parameter is separated by a semi-colon), which is
convenient for JSESSIONID for example.
This is the default mode if the option is not set.
- query-string :
In this mode, the parser will look for the appsession in the
query string.
When an application cookie is defined in a backend, HAProxy will check when When an application cookie is defined in a backend, HAProxy will check when
the server sets such a cookie, and will store its value in a table, and the server sets such a cookie, and will store its value in a table, and
associate it with the server's identifier. Up to <length> characters from associate it with the server's identifier. Up to <length> characters from
the value will be retained. On each connection, haproxy will look for this the value will be retained. On each connection, haproxy will look for this
cookie both in the "Cookie:" headers, and as a URL parameter in the query cookie both in the "Cookie:" headers, and as a URL parameter (depending on
string. If a known value is found, the client will be directed to the server the mode used). If a known value is found, the client will be directed to the
associated with this value. Otherwise, the load balancing algorithm is server associated with this value. Otherwise, the load balancing algorithm is
applied. Cookies are automatically removed from memory when they have been applied. Cookies are automatically removed from memory when they have been
unused for a duration longer than <holdtime>. unused for a duration longer than <holdtime>.

View File

@ -75,7 +75,7 @@ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hd
int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp); int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp);
int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_exp *exp); int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_exp *exp);
int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp); int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp);
void manage_client_side_appsession(struct session *t, const char *buf); void manage_client_side_appsession(struct session *t, const char *buf, int len);
void manage_client_side_cookies(struct session *t, struct buffer *req); void manage_client_side_cookies(struct session *t, struct buffer *req);
void manage_server_side_cookies(struct session *t, struct buffer *rtr); void manage_server_side_cookies(struct session *t, struct buffer *rtr);
void check_response_for_cacheability(struct session *t, struct buffer *rtr); void check_response_for_cacheability(struct session *t, struct buffer *rtr);

View File

@ -123,7 +123,15 @@
#define PR_O2_LOGHCHKS 0x00000800 /* log health checks */ #define PR_O2_LOGHCHKS 0x00000800 /* log health checks */
#define PR_O2_INDEPSTR 0x00001000 /* independant streams, don't update rex on write */ #define PR_O2_INDEPSTR 0x00001000 /* independant streams, don't update rex on write */
#define PR_O2_SOCKSTAT 0x00002000 /* collect & provide separate statistics for sockets */ #define PR_O2_SOCKSTAT 0x00002000 /* collect & provide separate statistics for sockets */
#define PR_O2_AS_REQL 0x00004000 /* appsession: learn the session id from the request */
/* appsession */
#define PR_O2_AS_REQL 0x00004000 /* learn the session id from the request */
#define PR_O2_AS_PFX 0x00008000 /* match on the cookie prefix */
/* Encoding of appsession cookie matching modes : 2 possible values => 1 bit */
#define PR_O2_AS_M_PP 0x00000000 /* path-parameters mode (the default mode) */
#define PR_O2_AS_M_QS 0x00010000 /* query-string mode */
#define PR_O2_AS_M_ANY 0x00010000 /* mask covering all PR_O2_AS_M_* values */
struct error_snapshot { struct error_snapshot {
struct timeval when; /* date of this event, (tv_sec == 0) means "never" */ struct timeval when; /* date of this event, (tv_sec == 0) means "never" */

View File

@ -1541,7 +1541,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= ERR_WARN; err_code |= ERR_WARN;
if (*(args[5]) == 0) { if (*(args[5]) == 0) {
Alert("parsing [%s:%d] : '%s' expects 'appsession' <cookie_name> 'len' <len> 'timeout' <timeout>.\n", Alert("parsing [%s:%d] : '%s' expects 'appsession' <cookie_name> 'len' <len> 'timeout' <timeout> [options*].\n",
file, linenum, args[0]); file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
@ -1568,9 +1568,34 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
cur_arg = 6; cur_arg = 6;
curproxy->options2 &= ~PR_O2_AS_REQL; curproxy->options2 &= ~PR_O2_AS_REQL;
curproxy->options2 &= ~PR_O2_AS_M_ANY;
curproxy->options2 |= PR_O2_AS_M_PP;
while (*(args[cur_arg])) { while (*(args[cur_arg])) {
if (!strcmp(args[cur_arg], "request-learn")) if (!strcmp(args[cur_arg], "request-learn")) {
curproxy->options2 |= PR_O2_AS_REQL; curproxy->options2 |= PR_O2_AS_REQL;
} else if (!strcmp(args[cur_arg], "prefix")) {
curproxy->options2 |= PR_O2_AS_PFX;
} else if (!strcmp(args[cur_arg], "mode")) {
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n",
file, linenum, args[0], args[cur_arg]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
cur_arg++;
if (!strcmp(args[cur_arg], "query-string")) {
curproxy->options2 &= ~PR_O2_AS_M_ANY;
curproxy->options2 |= PR_O2_AS_M_QS;
} else if (!strcmp(args[cur_arg], "path-parameters")) {
curproxy->options2 &= ~PR_O2_AS_M_ANY;
curproxy->options2 |= PR_O2_AS_M_PP;
} else {
Alert("parsing [%s:%d] : unknown mode '%s'\n", file, linenum, args[cur_arg]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
}
cur_arg++; cur_arg++;
} }
} /* Url App Session */ } /* Url App Session */

View File

@ -2479,17 +2479,7 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit)
} }
/* /*
* 7: the appsession cookie was looked up very early in 1.2, * 7: Now we can work with the cookies.
* so let's do the same now.
*/
/* It needs to look into the URI */
if (s->be->appsession_name) {
get_srv_from_appsession(s, &req->data[msg->som], msg->sl.rq.l);
}
/*
* 8: Now we can work with the cookies.
* Note that doing so might move headers in the request, but * Note that doing so might move headers in the request, but
* the fields will stay coherent and the URI will not move. * the fields will stay coherent and the URI will not move.
* This should only be performed in the backend. * This should only be performed in the backend.
@ -2498,6 +2488,16 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit)
&& !(txn->flags & (TX_CLDENY|TX_CLTARPIT))) && !(txn->flags & (TX_CLDENY|TX_CLTARPIT)))
manage_client_side_cookies(s, req); manage_client_side_cookies(s, req);
/*
* 8: the appsession cookie was looked up very early in 1.2,
* so let's do the same now.
*/
/* It needs to look into the URI */
if ((s->sessid == NULL) && s->be->appsession_name) {
get_srv_from_appsession(s, &req->data[msg->som + msg->sl.rq.u], msg->sl.rq.u_l);
}
/* /*
* 9: add X-Forwarded-For if either the frontend or the backend * 9: add X-Forwarded-For if either the frontend or the backend
* asks for it. * asks for it.
@ -3783,11 +3783,15 @@ int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_e
* Try to retrieve the server associated to the appsession. * Try to retrieve the server associated to the appsession.
* If the server is found, it's assigned to the session. * If the server is found, it's assigned to the session.
*/ */
void manage_client_side_appsession(struct session *t, const char *buf) { void manage_client_side_appsession(struct session *t, const char *buf, int len) {
struct http_txn *txn = &t->txn; struct http_txn *txn = &t->txn;
appsess *asession = NULL; appsess *asession = NULL;
char *sessid_temp = NULL; char *sessid_temp = NULL;
if (len > t->be->appsession_len) {
len = t->be->appsession_len;
}
if (t->be->options2 & PR_O2_AS_REQL) { if (t->be->options2 & PR_O2_AS_REQL) {
/* request-learn option is enabled : store the sessid in the session for future use */ /* request-learn option is enabled : store the sessid in the session for future use */
if (t->sessid != NULL) { if (t->sessid != NULL) {
@ -3801,8 +3805,8 @@ void manage_client_side_appsession(struct session *t, const char *buf) {
return; return;
} }
memcpy(t->sessid, buf, t->be->appsession_len); memcpy(t->sessid, buf, len);
t->sessid[t->be->appsession_len] = 0; t->sessid[len] = 0;
} }
if ((sessid_temp = pool_alloc2(apools.sessid)) == NULL) { if ((sessid_temp = pool_alloc2(apools.sessid)) == NULL) {
@ -3811,8 +3815,8 @@ void manage_client_side_appsession(struct session *t, const char *buf) {
return; return;
} }
memcpy(sessid_temp, buf, t->be->appsession_len); memcpy(sessid_temp, buf, len);
sessid_temp[t->be->appsession_len] = 0; sessid_temp[len] = 0;
asession = appsession_hash_lookup(&(t->be->htbl_proxy), sessid_temp); asession = appsession_hash_lookup(&(t->be->htbl_proxy), sessid_temp);
/* free previously allocated memory */ /* free previously allocated memory */
@ -4080,12 +4084,25 @@ void manage_client_side_cookies(struct session *t, struct buffer *req)
} }
} }
if ((t->be->appsession_name != NULL) && if (t->be->appsession_name != NULL) {
(memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) { int cmp_len, value_len;
/* first, let's see if the cookie is our appcookie*/ char *value_begin;
/* Cool... it's the right one */ if (t->be->options2 & PR_O2_AS_PFX) {
manage_client_side_appsession(t, p3); cmp_len = MIN(p4 - p1, t->be->appsession_name_len);
value_begin = p1 + t->be->appsession_name_len;
value_len = p4 - p1 - t->be->appsession_name_len;
} else {
cmp_len = p2 - p1;
value_begin = p3;
value_len = p4 - p3;
}
/* let's see if the cookie is our appcookie */
if (memcmp(p1, t->be->appsession_name, cmp_len) == 0) {
/* Cool... it's the right one */
manage_client_side_appsession(t, value_begin, value_len);
}
#if defined(DEBUG_HASH) #if defined(DEBUG_HASH)
Alert("manage_client_side_cookies\n"); Alert("manage_client_side_cookies\n");
appsession_hash_dump(&(t->be->htbl_proxy)); appsession_hash_dump(&(t->be->htbl_proxy));
@ -4497,24 +4514,36 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr)
} }
} }
/* next, let's see if the cookie is our appcookie */ /* next, let's see if the cookie is our appcookie */
else if ((t->be->appsession_name != NULL) && else if (t->be->appsession_name != NULL) {
(memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) { int cmp_len, value_len;
char *value_begin;
/* Cool... it's the right one */ if (t->be->options2 & PR_O2_AS_PFX) {
cmp_len = MIN(p4 - p1, t->be->appsession_name_len);
value_begin = p1 + t->be->appsession_name_len;
value_len = MIN(t->be->appsession_len, p4 - p1 - t->be->appsession_name_len);
} else {
cmp_len = p2 - p1;
value_begin = p3;
value_len = MIN(t->be->appsession_len, p4 - p3);
}
if (t->sessid != NULL) { if (memcmp(p1, t->be->appsession_name, cmp_len) == 0) {
/* free previously allocated memory as we don't need it anymore */ /* Cool... it's the right one */
pool_free2(apools.sessid, t->sessid); if (t->sessid != NULL) {
/* free previously allocated memory as we don't need it anymore */
pool_free2(apools.sessid, t->sessid);
}
/* Store the sessid in the session for future use */
if ((t->sessid = pool_alloc2(apools.sessid)) == NULL) {
Alert("Not enough Memory process_srv():asession->sessid:malloc().\n");
send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession->sessid:malloc().\n");
return;
}
memcpy(t->sessid, value_begin, value_len);
t->sessid[value_len] = 0;
} }
/* Store the sessid in the session for future use */ } /* end if ((t->be->appsession_name != NULL) ... */
if ((t->sessid = pool_alloc2(apools.sessid)) == NULL) {
Alert("Not enough Memory process_srv():asession->sessid:malloc().\n");
send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession->sessid:malloc().\n");
return;
}
memcpy(t->sessid, p3, t->be->appsession_len);
t->sessid[t->be->appsession_len] = 0;
}/* end if ((t->proxy->appsession_name != NULL) ... */
break; /* we don't want to loop again since there cannot be another cookie on the same line */ break; /* we don't want to loop again since there cannot be another cookie on the same line */
} /* we're now at the end of the cookie value */ } /* we're now at the end of the cookie value */
/* keep the link from this header to next one */ /* keep the link from this header to next one */
@ -4657,25 +4686,66 @@ void check_response_for_cacheability(struct session *t, struct buffer *rtr)
*/ */
void get_srv_from_appsession(struct session *t, const char *begin, int len) void get_srv_from_appsession(struct session *t, const char *begin, int len)
{ {
char *request_line; char *end_params, *first_param, *cur_param, *next_param;
char separator;
int value_len;
int mode = t->be->options2 & PR_O2_AS_M_ANY;
if (t->be->appsession_name == NULL || if (t->be->appsession_name == NULL ||
(t->txn.meth != HTTP_METH_GET && t->txn.meth != HTTP_METH_POST) || (t->txn.meth != HTTP_METH_GET && t->txn.meth != HTTP_METH_POST)) {
(request_line = memchr(begin, ';', len)) == NULL ||
((1 + t->be->appsession_name_len + 1 + t->be->appsession_len) > (begin + len - request_line)))
return; return;
}
/* skip ';' */ first_param = NULL;
request_line++; switch (mode) {
case PR_O2_AS_M_PP:
first_param = memchr(begin, ';', len);
break;
case PR_O2_AS_M_QS:
first_param = memchr(begin, '?', len);
break;
}
/* look if we have a jsessionid */ if (first_param == NULL) {
if (strncasecmp(request_line, t->be->appsession_name, t->be->appsession_name_len) != 0)
return; return;
}
/* skip jsessionid= */ switch (mode) {
request_line += t->be->appsession_name_len + 1; case PR_O2_AS_M_PP:
if ((end_params = memchr(first_param, '?', len - (begin - first_param))) == NULL) {
end_params = (char *) begin + len;
}
separator = ';';
break;
case PR_O2_AS_M_QS:
end_params = (char *) begin + len;
separator = '&';
break;
default:
/* unknown mode, shouldn't happen */
return;
}
manage_client_side_appsession(t, request_line); cur_param = next_param = end_params;
while (cur_param > first_param) {
cur_param--;
if ((cur_param[0] == separator) || (cur_param == first_param)) {
/* let's see if this is the appsession parameter */
if ((cur_param + t->be->appsession_name_len + 1 < next_param) &&
((t->be->options2 & PR_O2_AS_PFX) || cur_param[t->be->appsession_name_len + 1] == '=') &&
(strncasecmp(cur_param + 1, t->be->appsession_name, t->be->appsession_name_len) == 0)) {
/* Cool... it's the right one */
cur_param += t->be->appsession_name_len + (t->be->options2 & PR_O2_AS_PFX ? 1 : 2);
value_len = MIN(t->be->appsession_len, next_param - cur_param);
if (value_len > 0) {
manage_client_side_appsession(t, cur_param, value_len);
}
break;
}
next_param = cur_param;
}
}
#if defined(DEBUG_HASH) #if defined(DEBUG_HASH)
Alert("get_srv_from_appsession\n"); Alert("get_srv_from_appsession\n");
appsession_hash_dump(&(t->be->htbl_proxy)); appsession_hash_dump(&(t->be->htbl_proxy));