]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] appsession: add the "request-learn" option
authorCyril Bonté <cyril.bonte@free.fr>
Wed, 14 Oct 2009 22:15:40 +0000 (00:15 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 18 Oct 2009 09:58:53 +0000 (11:58 +0200)
This patch has 2 goals :

1. I wanted to test the appsession feature with a small PHP code,
using PHPSESSID. The problem is that when PHP gets an unknown session
id, it creates a new one with this ID. So, when sending an unknown
session to PHP, persistance is broken : haproxy won't see any new
cookie in the response and will never attach this session to a
specific server.

This also happens when you restart haproxy : the internal hash becomes
empty and all sessions loose their persistance (load balancing the
requests on all backend servers, creating a new session on each one).
For a user, it's like the service is unusable.

The patch modifies the code to make haproxy also learn the persistance
from the client : if no session is sent from the server, then the
session id found in the client part (using the URI or the client cookie)
is used to associated the server that gave the response.

As it's probably not a feature usable in all cases, I added an option
to enable it (by default it's disabled). The syntax of appsession becomes :

  appsession <cookie> len <length> timeout <holdtime> [request-learn]

This helps haproxy repair the persistance (with the risk of losing its
session at the next request, as the user will probably not be load
balanced to the same server the first time).

2. This patch also tries to reduce the memory usage.
Here is a little example to explain the current behaviour :
- Take a Tomcat server where /session.jsp is valid.
- Send a request using a cookie with an unknown value AND a path
  parameter with another unknown value :

  curl -b "JSESSIONID=12345678901234567890123456789012" http://<haproxy>/session.jsp;jsessionid=00000000000000000000000000000001

(I know, it's unexpected to have a request like that on a live service)
Here, haproxy finds the URI session ID and stores it in its internal
hash (with no server associated). But it also finds the cookie session
ID and stores it again.

- As a result, session.jsp sends a new session ID also stored in the
  internal hash, with a server associated.

=> For 1 request, haproxy has stored 3 entries, with only 1 which will be usable

The patch modifies the behaviour to store only 1 entry (maximum).

Similar patch was merged in 1.4 with commit ID bf47aeb9469b54b0547922bdffe3fcd8e70aac1e.

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

index ae6dbc34abde21b00c1b92a71c3257e6ffe685ef..7a3856c56f90cd9c8908150cbf50d0d450d538e0 100644 (file)
@@ -814,7 +814,7 @@ acl <aclname> <criterion> [flags] [operator] <value> ...
   See section 7 about ACL usage.
 
 
-appsession <cookie> len <length> timeout <holdtime>
+appsession <cookie> len <length> timeout <holdtime> [request-learn]
   Define session stickiness on an existing application cookie.
   May be used in sections :   defaults | frontend | listen | backend
                                  no    |    no    |   yes  |   yes
@@ -829,6 +829,14 @@ appsession <cookie> len <length> timeout <holdtime>
                memory if unused. If no unit is specified, this time is in
                milliseconds.
 
+    request-learn
+               If this option is specified, then haproxy will be able to learn
+               the cookie found in the request in case the server does not
+               specify any in response. This is typically what happens with
+               PHPSESSID cookies, or when haproxy's session expires before
+               the application's session and the correct server is selected.
+               It is recommended to specify this option to improve reliability.
+
   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
index 175eb04794406306e4e25b0492bd056260f97a6f..cff88ef94f04f7eab604d4710632f949ca602b7c 100644 (file)
@@ -75,6 +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_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 8b98c4a23466ffb043bb1cfc0b9bb52a5731afa9..6e89945a23dc29caeb2794f477510532e7e69ef5 100644 (file)
 #define PR_O2_LOGERRORS        0x00000040      /* log errors and retries at level LOG_ERR */
 /* 0x80..0x800 already used in 1.4 */
 #define PR_O2_INDEPSTR 0x00001000      /* independant streams, don't update rex on write */
+/* 0x2000 used in 1.4 */
+#define PR_O2_AS_REQL  0x00004000      /* appsession: learn the session id from the request */
 
 /* This structure is used to apply fast weighted round robin on a server group */
 struct fwrr_group {
index 356bdee61b622795fcc2160b0b056b5a5ac8ec75..e26033a6e3916536950855e92a7a734e5c085e65 100644 (file)
@@ -162,6 +162,7 @@ struct session {
        int conn_retries;                       /* number of connect retries left */
        int flags;                              /* some flags describing the session */
        unsigned term_trace;                    /* term trace: 4*8 bits indicating which part of the code closed */
+       char *sessid;                           /* the session id, if found in the request or in the response */
        struct buffer *req;                     /* request buffer */
        struct buffer *rep;                     /* response buffer */
        struct stream_interface si[2];          /* client and server stream interfaces */
index 6051144262360ac15e4d4d82a99af119d8b3f676..f0d690b2e4eb9e4acf5bd5b8a1c18652e7506c27 100644 (file)
@@ -1326,6 +1326,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
                }
        }/* end else if (!strcmp(args[0], "cookie"))  */
        else if (!strcmp(args[0], "appsession")) {  /* cookie name */
+               int cur_arg;
 
                if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
                        err_code |= ERR_WARN;
@@ -1355,6 +1356,14 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
                        err_code |= ERR_ALERT | ERR_ABORT;
                        goto out;
                }
+
+               cur_arg = 6;
+               curproxy->options2 &= ~PR_O2_AS_REQL;
+               while (*(args[cur_arg])) {
+                       if (!strcmp(args[cur_arg], "request-learn"))
+                               curproxy->options2 |= PR_O2_AS_REQL;
+                       cur_arg++;
+               }
        } /* Url App Session */
        else if (!strcmp(args[0], "capture")) {
                if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL))
index 3da41439a30642db6f7588badca9323b5624078b..5b8b870efe1ed31331dc3618224afaaace9d421a 100644 (file)
@@ -180,6 +180,7 @@ int event_accept(int fd) {
 
                s->ana_state = 0;  /* analysers may change it but must reset it upon exit */
                s->req = s->rep = NULL; /* will be allocated later */
+               s->sessid = NULL;
 
                s->si[0].state = s->si[0].prev_state = SI_ST_EST;
                s->si[0].err_type = SI_ET_NONE;
index 07bf629c9ea9e47ed264c4b6eb39156038031aea..2db55c1e802b10b5bcbeb51fb10ff153e357570a 100644 (file)
@@ -3367,6 +3367,72 @@ 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) {
+       struct http_txn *txn = &t->txn;
+       appsess *asession = NULL;
+       char *sessid_temp = NULL;
+
+       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) {
+                       /* free previously allocated memory as we don't need the session id found in the URL anymore */
+                       pool_free2(apools.sessid, t->sessid);
+               }
+
+               if ((t->sessid = pool_alloc2(apools.sessid)) == NULL) {
+                       Alert("Not enough memory process_cli():asession->sessid:malloc().\n");
+                       send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n");
+                       return;
+               }
+
+               memcpy(t->sessid, buf, t->be->appsession_len);
+               t->sessid[t->be->appsession_len] = 0;
+       }
+
+       if ((sessid_temp = pool_alloc2(apools.sessid)) == NULL) {
+               Alert("Not enough memory process_cli():asession->sessid:malloc().\n");
+               send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n");
+               return;
+       }
+
+       memcpy(sessid_temp, buf, t->be->appsession_len);
+       sessid_temp[t->be->appsession_len] = 0;
+
+       asession = appsession_hash_lookup(&(t->be->htbl_proxy), sessid_temp);
+       /* free previously allocated memory */
+       pool_free2(apools.sessid, sessid_temp);
+
+       if (asession != NULL) {
+               asession->expire = tick_add_ifset(now_ms, t->be->timeout.appsession);
+               if (!(t->be->options2 & PR_O2_AS_REQL))
+                       asession->request_count++;
+
+               if (asession->serverid != NULL) {
+                       struct server *srv = t->be->srv;
+                       while (srv) {
+                               if (strcmp(srv->id, asession->serverid) == 0) {
+                                       if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) {
+                                               /* we found the server and it's usable */
+                                               txn->flags &= ~TX_CK_MASK;
+                                               txn->flags |= TX_CK_VALID;
+                                               t->flags |= SN_DIRECT | SN_ASSIGNED;
+                                               t->srv = srv;
+                                               break;
+                                       } else {
+                                               txn->flags &= ~TX_CK_MASK;
+                                               txn->flags |= TX_CK_DOWN;
+                                       }
+                               }
+                               srv = srv->next;
+                       }
+               }
+       }
+}
+
 /*
  * Manage client-side cookie. It can impact performance by about 2% so it is
  * desirable to call it only when needed.
@@ -3378,9 +3444,6 @@ void manage_client_side_cookies(struct session *t, struct buffer *req)
        char *del_colon, *del_cookie, *colon;
        int app_cookies;
 
-       appsess *asession_temp = NULL;
-       appsess local_asession;
-
        char *cur_ptr, *cur_end, *cur_next;
        int cur_idx, old_idx;
 
@@ -3610,63 +3673,7 @@ void manage_client_side_cookies(struct session *t, struct buffer *req)
                                        /* first, let's see if the cookie is our appcookie*/
 
                                        /* Cool... it's the right one */
-
-                                       asession_temp = &local_asession;
-                         
-                                       if ((asession_temp->sessid = pool_alloc2(apools.sessid)) == NULL) {
-                                               Alert("Not enough memory process_cli():asession->sessid:malloc().\n");
-                                               send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n");
-                                               return;
-                                       }
-
-                                       memcpy(asession_temp->sessid, p3, t->be->appsession_len);
-                                       asession_temp->sessid[t->be->appsession_len] = 0;
-                                       asession_temp->serverid = NULL;
-
-                                       /* only do insert, if lookup fails */
-                                       asession_temp = appsession_hash_lookup(&(t->be->htbl_proxy), asession_temp->sessid);
-                                       if (asession_temp == NULL) {
-                                               if ((asession_temp = pool_alloc2(pool2_appsess)) == NULL) {
-                                                       /* free previously allocated memory */
-                                                       pool_free2(apools.sessid, local_asession.sessid);
-                                                       Alert("Not enough memory process_cli():asession:calloc().\n");
-                                                       send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n");
-                                                       return;
-                                               }
-
-                                               asession_temp->sessid = local_asession.sessid;
-                                               asession_temp->serverid = local_asession.serverid;
-                                               asession_temp->request_count = 0;
-                                               appsession_hash_insert(&(t->be->htbl_proxy), asession_temp);
-                                       } else {
-                                               /* free previously allocated memory */
-                                               pool_free2(apools.sessid, local_asession.sessid);
-                                       }
-                                       if (asession_temp->serverid == NULL) {
-                                               /* TODO redispatch request */
-                                               Alert("Found Application Session without matching server.\n");
-                                       } else {
-                                               struct server *srv = t->be->srv;
-                                               while (srv) {
-                                                       if (strcmp(srv->id, asession_temp->serverid) == 0) {
-                                                               if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) {
-                                                                       /* we found the server and it's usable */
-                                                                       txn->flags &= ~TX_CK_MASK;
-                                                                       txn->flags |= TX_CK_VALID;
-                                                                       t->flags |= SN_DIRECT | SN_ASSIGNED;
-                                                                       t->srv = srv;
-                                                                       break;
-                                                               } else {
-                                                                       txn->flags &= ~TX_CK_MASK;
-                                                                       txn->flags |= TX_CK_DOWN;
-                                                               }
-                                                       }
-                                                       srv = srv->next;
-                                               }/* end while(srv) */
-                                       }/* end else if server == NULL */
-
-                                       asession_temp->expire = tick_add_ifset(now_ms, t->be->timeout.appsession);
-                                       asession_temp->request_count++;
+                                       manage_client_side_appsession(t, p3);
 #if defined(DEBUG_HASH)
                                        Alert("manage_client_side_cookies\n");
                                        appsession_hash_dump(&(t->be->htbl_proxy));
@@ -3944,9 +3951,6 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr)
        struct http_txn *txn = &t->txn;
        char *p1, *p2, *p3, *p4;
 
-       appsess *asession_temp = NULL;
-       appsess local_asession;
-
        char *cur_ptr, *cur_end, *cur_next;
        int cur_idx, old_idx, delta;
 
@@ -4087,60 +4091,64 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr)
 
                                /* Cool... it's the right one */
 
-                               size_t server_id_len = strlen(t->srv->id) + 1;
-                               asession_temp = &local_asession;
-                     
-                               if ((asession_temp->sessid = pool_alloc2(apools.sessid)) == NULL) {
+                               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(asession_temp->sessid, p3, t->be->appsession_len);
-                               asession_temp->sessid[t->be->appsession_len] = 0;
-                               asession_temp->serverid = NULL;
-
-                               /* only do insert, if lookup fails */
-                               asession_temp = appsession_hash_lookup(&(t->be->htbl_proxy), asession_temp->sessid);
-                               if (asession_temp == NULL) {
-                                       if ((asession_temp = pool_alloc2(pool2_appsess)) == NULL) {
-                                               Alert("Not enough Memory process_srv():asession:calloc().\n");
-                                               send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession:calloc().\n");
-                                               return;
-                                       }
-                                       asession_temp->sessid = local_asession.sessid;
-                                       asession_temp->serverid = local_asession.serverid;
-                                       asession_temp->request_count = 0;
-                                       appsession_hash_insert(&(t->be->htbl_proxy), asession_temp);
-                               } else {
-                                       /* free wasted memory */
-                                       pool_free2(apools.sessid, local_asession.sessid);
-                               }
-
-                               if (asession_temp->serverid == NULL) {
-                                       if ((asession_temp->serverid = pool_alloc2(apools.serverid)) == 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;
-                                       }
-                                       asession_temp->serverid[0] = '\0';
-                               }
-                     
-                               if (asession_temp->serverid[0] == '\0')
-                                       memcpy(asession_temp->serverid, t->srv->id, server_id_len);
-                     
-                               asession_temp->expire = tick_add_ifset(now_ms, t->be->timeout.appsession);
-                               asession_temp->request_count++;
-#if defined(DEBUG_HASH)
-                               Alert("manage_server_side_cookies\n");
-                               appsession_hash_dump(&(t->be->htbl_proxy));
-#endif
+                               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 */
                } /* we're now at the end of the cookie value */
-
                /* keep the link from this header to next one */
                old_idx = cur_idx;
        } /* end of cookie processing on this header */
+
+       if (t->sessid != NULL) {
+               appsess *asession = NULL;
+               /* only do insert, if lookup fails */
+               asession = appsession_hash_lookup(&(t->be->htbl_proxy), t->sessid);
+               if (asession == NULL) {
+                       if ((asession = pool_alloc2(pool2_appsess)) == NULL) {
+                               Alert("Not enough Memory process_srv():asession:calloc().\n");
+                               send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession:calloc().\n");
+                               return;
+                       }
+                       if ((asession->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(asession->sessid, t->sessid, t->be->appsession_len);
+                       asession->sessid[t->be->appsession_len] = 0;
+
+                       size_t server_id_len = strlen(t->srv->id) + 1;
+                       if ((asession->serverid = pool_alloc2(apools.serverid)) == 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;
+                       }
+                       asession->serverid[0] = '\0';
+                       memcpy(asession->serverid, t->srv->id, server_id_len);
+
+                       asession->request_count = 0;
+                       appsession_hash_insert(&(t->be->htbl_proxy), asession);
+               }
+
+               asession->expire = tick_add_ifset(now_ms, t->be->timeout.appsession);
+               asession->request_count++;
+       }
+
+#if defined(DEBUG_HASH)
+       Alert("manage_server_side_cookies\n");
+       appsession_hash_dump(&(t->be->htbl_proxy));
+#endif
 }
 
 
@@ -4238,9 +4246,6 @@ void check_response_for_cacheability(struct session *t, struct buffer *rtr)
  */
 void get_srv_from_appsession(struct session *t, const char *begin, int len)
 {
-       struct http_txn *txn = &t->txn;
-       appsess *asession_temp = NULL;
-       appsess local_asession;
        char *request_line;
 
        if (t->be->appsession_name == NULL ||
@@ -4259,69 +4264,11 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len)
        /* skip jsessionid= */
        request_line += t->be->appsession_name_len + 1;
        
-       /* First try if we already have an appsession */
-       asession_temp = &local_asession;
-       
-       if ((asession_temp->sessid = pool_alloc2(apools.sessid)) == NULL) {
-               Alert("Not enough memory process_cli():asession_temp->sessid:calloc().\n");
-               send_log(t->be, LOG_ALERT, "Not enough Memory process_cli():asession_temp->sessid:calloc().\n");
-               return;
-       }
-       
-       /* Copy the sessionid */
-       memcpy(asession_temp->sessid, request_line, t->be->appsession_len);
-       asession_temp->sessid[t->be->appsession_len] = 0;
-       asession_temp->serverid = NULL;
-       
-       /* only do insert, if lookup fails */
-       asession_temp = appsession_hash_lookup(&(t->be->htbl_proxy), asession_temp->sessid);
-       if (asession_temp == NULL) {
-               if ((asession_temp = pool_alloc2(pool2_appsess)) == NULL) {
-                       /* free previously allocated memory */
-                       pool_free2(apools.sessid, local_asession.sessid);
-                       Alert("Not enough memory process_cli():asession:calloc().\n");
-                       send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n");
-                       return;
-               }
-               asession_temp->sessid = local_asession.sessid;
-               asession_temp->serverid = local_asession.serverid;
-               asession_temp->request_count=0;
-               appsession_hash_insert(&(t->be->htbl_proxy), asession_temp);
-       }
-       else {
-               /* free previously allocated memory */
-               pool_free2(apools.sessid, local_asession.sessid);
-       }
-
-       asession_temp->expire = tick_add_ifset(now_ms, t->be->timeout.appsession);
-       asession_temp->request_count++;
-
+       manage_client_side_appsession(t, request_line);
 #if defined(DEBUG_HASH)
        Alert("get_srv_from_appsession\n");
        appsession_hash_dump(&(t->be->htbl_proxy));
 #endif
-       if (asession_temp->serverid == NULL) {
-               /* TODO redispatch request */
-               Alert("Found Application Session without matching server.\n");
-       } else {
-               struct server *srv = t->be->srv;
-               while (srv) {
-                       if (strcmp(srv->id, asession_temp->serverid) == 0) {
-                               if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) {
-                                       /* we found the server and it's usable */
-                                       txn->flags &= ~TX_CK_MASK;
-                                       txn->flags |= TX_CK_VALID;
-                                       t->flags |= SN_DIRECT | SN_ASSIGNED;
-                                       t->srv = srv;
-                                       break;
-                               } else {
-                                       txn->flags &= ~TX_CK_MASK;
-                                       txn->flags |= TX_CK_DOWN;
-                               }
-                       }
-                       srv = srv->next;
-               }
-       }
 }
 
 
index 659dadb5be3e3908e34ebbf87ffd2ab70f8bb493..1348aa04ffedef7ce8504c046f47a3550af96694 100644 (file)
@@ -78,6 +78,9 @@ void session_free(struct session *s)
        pool_free2(pool2_buffer, s->req);
        pool_free2(pool2_buffer, s->rep);
 
+       if (s->sessid)
+               pool_free2(apools.sessid, s->sessid);
+
        if (fe) {
                pool_free2(fe->hdr_idx_pool, txn->hdr_idx.v);