]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] appsession: add "len", "prefix" and "mode" options
authorCyril Bonté <cyril.bonte@free.fr>
Sun, 29 Nov 2009 19:04:48 +0000 (20:04 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 30 Nov 2009 10:31:53 +0000 (11:31 +0100)
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).

doc/configuration.txt
include/proto/proto_http.h
include/types/proxy.h
src/cfgparse.c
src/proto_http.c

index fae5027e1b3c999895b5cc882bc32fdd86844612..47842d6c76ebce0f86527bf91624625890b3529e 100644 (file)
@@ -861,7 +861,8 @@ acl <aclname> <criterion> [flags] [operator] <value> ...
   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.
   May be used in sections :   defaults | frontend | listen | backend
                                  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
                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.
 
     <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.
                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
   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
   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
-  string. If a known value is found, the client will be directed to the server
-  associated with this value. Otherwise, the load balancing algorithm is
+  cookie both in the "Cookie:" headers, and as a URL parameter (depending on
+  the mode used). If a known value is found, the client will be directed to the
+  server associated with this value. Otherwise, the load balancing algorithm is
   applied. Cookies are automatically removed from memory when they have been
   unused for a duration longer than <holdtime>.
 
index 34fccdf2d610f4161d0263ce8d472b4d5b61ab37..d5a7306bc369807c8113aee70782811ba99e9c50 100644 (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_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);
-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_server_side_cookies(struct session *t, struct buffer *rtr);
 void check_response_for_cacheability(struct session *t, struct buffer *rtr);
index 8348f92ed317b24d286125c133b95eb98093f416..92517ca2ec21fe7e05e43e61f38c4857d7df90b5 100644 (file)
 #define PR_O2_LOGHCHKS 0x00000800      /* log health checks */
 #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_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 timeval when;            /* date of this event, (tv_sec == 0) means "never" */
index 34d4476e2edd435f8aeb6410609c461284662153..3df0a21b08fc77ae559915181d740483359c5e97 100644 (file)
@@ -1541,7 +1541,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        err_code |= ERR_WARN;
 
                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]);
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
@@ -1568,9 +1568,34 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
 
                cur_arg = 6;
                curproxy->options2 &= ~PR_O2_AS_REQL;
+               curproxy->options2 &= ~PR_O2_AS_M_ANY;
+               curproxy->options2 |= PR_O2_AS_M_PP;
                while (*(args[cur_arg])) {
-                       if (!strcmp(args[cur_arg], "request-learn"))
+                       if (!strcmp(args[cur_arg], "request-learn")) {
                                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++;
                }
        } /* Url App Session */
index f896fbbdf9a728c30dce56417821a492a3b1ccfa..cbaae5b352b057e57d0e29bbc2052fb62368c671 100644 (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,
-        * 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.
+        * 7: Now we can work with the cookies.
         * Note that doing so might move headers in the request, but
         * the fields will stay coherent and the URI will not move.
         * 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)))
                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
         * 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.
  * 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;
        appsess *asession = NULL;
        char *sessid_temp = NULL;
 
+       if (len > t->be->appsession_len) {
+               len = t->be->appsession_len;
+       }
+
        if (t->be->options2 & PR_O2_AS_REQL) {
                /* request-learn option is enabled : store the sessid in the session for future use */
                if (t->sessid != NULL) {
@@ -3801,8 +3805,8 @@ void manage_client_side_appsession(struct session *t, const char *buf) {
                        return;
                }
 
-               memcpy(t->sessid, buf, t->be->appsession_len);
-               t->sessid[t->be->appsession_len] = 0;
+               memcpy(t->sessid, buf, len);
+               t->sessid[len] = 0;
        }
 
        if ((sessid_temp = pool_alloc2(apools.sessid)) == NULL) {
@@ -3811,8 +3815,8 @@ void manage_client_side_appsession(struct session *t, const char *buf) {
                return;
        }
 
-       memcpy(sessid_temp, buf, t->be->appsession_len);
-       sessid_temp[t->be->appsession_len] = 0;
+       memcpy(sessid_temp, buf, len);
+       sessid_temp[len] = 0;
 
        asession = appsession_hash_lookup(&(t->be->htbl_proxy), sessid_temp);
        /* 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) &&
-                                   (memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) {
-                                       /* first, let's see if the cookie is our appcookie*/
+                               if (t->be->appsession_name != NULL) {
+                                       int cmp_len, value_len;
+                                       char *value_begin;
 
-                                       /* Cool... it's the right one */
-                                       manage_client_side_appsession(t, p3);
+                                       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 = 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)
                                        Alert("manage_client_side_cookies\n");
                                        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 */
-                       else if ((t->be->appsession_name != NULL) &&
-                                (memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) {
-
-                               /* Cool... it's the right one */
-
-                               if (t->sessid != NULL) {
-                                       /* free previously allocated memory as we don't need it anymore */
-                                       pool_free2(apools.sessid, t->sessid);
+                       else if (t->be->appsession_name != NULL) {
+                               int cmp_len, value_len;
+                               char *value_begin;
+
+                               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);
                                }
-                               /* 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;
+
+                               if (memcmp(p1, t->be->appsession_name, cmp_len) == 0) {
+                                       /* Cool... it's the right one */
+                                       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;
                                }
-                               memcpy(t->sessid, p3, t->be->appsession_len);
-                               t->sessid[t->be->appsession_len] = 0;
-                       }/* end if ((t->proxy->appsession_name != NULL) ... */
+                       } /* end if ((t->be->appsession_name != NULL) ... */
                        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 */
                /* 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)
 {
-       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 ||
-           (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)))
+           (t->txn.meth != HTTP_METH_GET && t->txn.meth != HTTP_METH_POST)) {
                return;
+       }
 
-       /* skip ';' */
-       request_line++;
+       first_param = NULL;
+       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 (strncasecmp(request_line, t->be->appsession_name, t->be->appsession_name_len) != 0)
+       if (first_param == NULL) {
                return;
+       }
 
-       /* skip jsessionid= */
-       request_line += t->be->appsession_name_len + 1;
+       switch (mode) {
+       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)
        Alert("get_srv_from_appsession\n");
        appsession_hash_dump(&(t->be->htbl_proxy));