mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-21 13:51:26 +02:00
[MAJOR] implement parameter hashing for POST requests
This patch extends the "url_param" load balancing method by introducing the "check_post" option. Using this option enables analysis of the beginning of POST requests to search for the specified URL parameter. The patch also fixes a few minor typos in comments that were discovered during code review.
This commit is contained in:
parent
a532324128
commit
1c2ab96be5
@ -696,6 +696,7 @@ backlog <conns>
|
|||||||
|
|
||||||
|
|
||||||
balance <algorithm> [ <arguments> ]
|
balance <algorithm> [ <arguments> ]
|
||||||
|
balance url_param <param> [check_post [<max_wait>]]
|
||||||
Define the load balancing algorithm to be used in a backend.
|
Define the load balancing algorithm to be used in a backend.
|
||||||
May be used in sections : defaults | frontend | listen | backend
|
May be used in sections : defaults | frontend | listen | backend
|
||||||
yes | no | yes | yes
|
yes | no | yes | yes
|
||||||
@ -745,22 +746,47 @@ balance <algorithm> [ <arguments> ]
|
|||||||
effect.
|
effect.
|
||||||
|
|
||||||
url_param The URL parameter specified in argument will be looked up in
|
url_param The URL parameter specified in argument will be looked up in
|
||||||
the query string of each HTTP request. If it is found
|
the query string of each HTTP GET request.
|
||||||
followed by an equal sign ('=') and a value, then the value
|
|
||||||
is hashed and divided by the total weight of the running
|
If the modifier "check_post" is used, then an HTTP POST
|
||||||
servers. The result designates which server will receive the
|
request entity will be searched for the parameter argument,
|
||||||
request. This is used to track user identifiers in requests
|
when the question mark indicating a query string ('?') is not
|
||||||
and ensure that a same user ID will always be sent to the
|
present in the URL. Optionally, specify a number of octets to
|
||||||
same server as long as no server goes up or down. If no value
|
wait for before attempting to search the message body. If the
|
||||||
is found or if the parameter is not found, then a round robin
|
entity can not be searched, then round robin is used for each
|
||||||
algorithm is applied. Note that this algorithm may only be
|
request. For instance, if your clients always send the LB
|
||||||
used in an HTTP backend. This algorithm is static, which
|
parameter in the first 128 bytes, then specify that. The
|
||||||
means that changing a server's weight on the fly will have no
|
default is 48. The entity data will not be scanned until the
|
||||||
effect.
|
required number of octets have arrived at the gateway, this
|
||||||
|
is the minimum of: (default/max_wait, Content-Length or first
|
||||||
|
chunk length). If Content-Length is missing or zero, it does
|
||||||
|
not need to wait for more data than the client promised to
|
||||||
|
send. When Content-Length is present and larger than
|
||||||
|
<max_wait>, then waiting is limited to <max_wait> and it is
|
||||||
|
assumed that this will be enough data to search for the
|
||||||
|
presence of the parameter. In the unlikely event that
|
||||||
|
Transfer-Encoding: chunked is used, only the first chunk is
|
||||||
|
scanned. Parameter values separated by a chunk boundary, may
|
||||||
|
be randomly balanced if at all.
|
||||||
|
|
||||||
|
If the parameter is found followed by an equal sign ('=') and
|
||||||
|
a value, then the value is hashed and divided by the total
|
||||||
|
weight of the running servers. The result designates which
|
||||||
|
server will receive the request.
|
||||||
|
|
||||||
|
This is used to track user identifiers in requests and ensure
|
||||||
|
that a same user ID will always be sent to the same server as
|
||||||
|
long as no server goes up or down. If no value is found or if
|
||||||
|
the parameter is not found, then a round robin algorithm is
|
||||||
|
applied. Note that this algorithm may only be used in an HTTP
|
||||||
|
backend. This algorithm is static, which means that changing a
|
||||||
|
server's weight on the fly will have no effect.
|
||||||
|
|
||||||
<arguments> is an optional list of arguments which may be needed by some
|
<arguments> is an optional list of arguments which may be needed by some
|
||||||
algorithms. Right now, only the "url_param" algorithm supports
|
algorithms. Right now, only the "url_param" algorithm supports
|
||||||
a mandatory argument.
|
an optional argument.
|
||||||
|
|
||||||
|
balance url_param <param> [check_post [<max_wait>]]
|
||||||
|
|
||||||
The definition of the load balancing algorithm is mandatory for a backend
|
The definition of the load balancing algorithm is mandatory for a backend
|
||||||
and limited to one per backend.
|
and limited to one per backend.
|
||||||
@ -768,6 +794,39 @@ balance <algorithm> [ <arguments> ]
|
|||||||
Examples :
|
Examples :
|
||||||
balance roundrobin
|
balance roundrobin
|
||||||
balance url_param userid
|
balance url_param userid
|
||||||
|
balance url_param session_id check_post 64
|
||||||
|
|
||||||
|
Note: the following caveats and limitations on using the "check_post"
|
||||||
|
extension with "url_param" must be considered :
|
||||||
|
|
||||||
|
- all POST requests are eligable for consideration, because there is no way
|
||||||
|
to determine if the parameters will be found in the body or entity which
|
||||||
|
may contain binary data. Therefore another method may be required to
|
||||||
|
restrict consideration of POST requests that have no URL parameters in
|
||||||
|
the body. (see acl reqideny http_end)
|
||||||
|
|
||||||
|
- using a <max_wait> value larger than the request buffer size does not
|
||||||
|
make sense and is useless. The buffer size is set at build time, and
|
||||||
|
defaults to 16 kB.
|
||||||
|
|
||||||
|
- Content-Encoding is not supported, the parameter search will probably
|
||||||
|
fail; and load balancing will fall back to Round Robin.
|
||||||
|
|
||||||
|
- Expect: 100-continue is not supported, load balancing will fall back to
|
||||||
|
Round Robin.
|
||||||
|
|
||||||
|
- Transfer-Encoding (RFC2616 3.6.1) is only supported in the first chunk.
|
||||||
|
If the entire parameter value is not present in the first chunk, the
|
||||||
|
selection of server is undefined (actually, defined by how little
|
||||||
|
actually appeared in the first chunk).
|
||||||
|
|
||||||
|
- This feature does not support generation of a 100, 411 or 501 response.
|
||||||
|
|
||||||
|
- In some cases, requesting "check_post" MAY attempt to scan the entire
|
||||||
|
contents of a message body. Scaning normally terminates when linear
|
||||||
|
white space or control characters are found, indicating the end of what
|
||||||
|
might be a URL parameter list. This is probably not a concern with SGML
|
||||||
|
type message bodies.
|
||||||
|
|
||||||
See also : "dispatch", "cookie", "appsession", "transparent" and "http_proxy".
|
See also : "dispatch", "cookie", "appsession", "transparent" and "http_proxy".
|
||||||
|
|
||||||
|
@ -999,6 +999,48 @@ Some of these applications may not be able to use a cookie for whatever reason,
|
|||||||
but may be able to look for a parameter passed in the URL. If the parameter is
|
but may be able to look for a parameter passed in the URL. If the parameter is
|
||||||
missing from the URL, then the 'round robin' method applies.
|
missing from the URL, then the 'round robin' method applies.
|
||||||
|
|
||||||
|
A modifier may be added to specify that parameters in POST requests may be
|
||||||
|
found in the messsage body if the URL lacks a '?' separator character.
|
||||||
|
A wait limit may also be applied, if no limit is requested then
|
||||||
|
the default value is 48 octets, the minimum is 3. HAProxy may wait, until 48
|
||||||
|
octets are received. If Content-Length is missing, or zero it need not
|
||||||
|
wait for more data then the client promissed to send. When Content-Length is
|
||||||
|
present, and more than <max_wait>; then waiting is limited and it is assumed this
|
||||||
|
will be enough data to search for the presence of a parameter. If
|
||||||
|
Transfer-Encoding: chunked is used (unlikely), then the length of the first chunk
|
||||||
|
is the maximum number of bytes to wait for.
|
||||||
|
|
||||||
|
balance url_param <param> [check_post [<max_wait>]]
|
||||||
|
|
||||||
|
Caveats for using the check_post extension:
|
||||||
|
|
||||||
|
- all POST requests are eligable for consideration, because there is
|
||||||
|
no way to determine if the parameters will be found in the body or
|
||||||
|
entity which may contain binary data. Therefore another method may be
|
||||||
|
required to restrict consideration of POST requests that have no URL
|
||||||
|
parameters in the body. (see acl reqideny http_end)
|
||||||
|
|
||||||
|
Limitations on inspecting the entity body of a POST:
|
||||||
|
|
||||||
|
- Content-Encoding is not supported, the parameter search will probably fail;
|
||||||
|
and load balancing will fall back to Round Robin.
|
||||||
|
|
||||||
|
- Expect: 100-continue is not supported, load balancing will fall back to
|
||||||
|
Round Robin.
|
||||||
|
|
||||||
|
- Transfer-Encoding(RFC2616 3.6.1) is only supported in the first chunk. If
|
||||||
|
the entire parameter value is not present in the first chunk, the selection
|
||||||
|
of server is undefined (actually, defined by how little actually appeared in
|
||||||
|
the first chunk).
|
||||||
|
|
||||||
|
- This feature does not support generation of a 100, 411 or 501 response.
|
||||||
|
|
||||||
|
- In some cases, requesting check_post MAY attempt to scan the entire contents
|
||||||
|
of a message body. Scaning normally terminates when linear white space or
|
||||||
|
control characters are found, indicating the end of what might be a URL parameter
|
||||||
|
list. This is probably not a concern with SGML type message bodies.
|
||||||
|
|
||||||
|
|
||||||
Example :
|
Example :
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -81,6 +81,9 @@ 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);
|
||||||
int stats_check_uri_auth(struct session *t, struct proxy *backend);
|
int stats_check_uri_auth(struct session *t, struct proxy *backend);
|
||||||
void init_proto_http();
|
void init_proto_http();
|
||||||
|
int http_find_header2(const char *name, int len,
|
||||||
|
const char *sol, struct hdr_idx *idx,
|
||||||
|
struct hdr_ctx *ctx);
|
||||||
|
|
||||||
#endif /* _PROTO_PROTO_HTTP_H */
|
#endif /* _PROTO_PROTO_HTTP_H */
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
include/types/backend.h
|
include/types/backend.h
|
||||||
This file rassembles definitions for backends
|
This file assembles definitions for backends
|
||||||
|
|
||||||
Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
|
Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
|
||||||
|
|
||||||
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
/* various constants */
|
/* various constants */
|
||||||
|
|
||||||
/* The scale factor between user weight an effective weight allows smooth
|
/* The scale factor between user weight and effective weight allows smooth
|
||||||
* weight modulation even with small weights (eg: 1). It should not be too high
|
* weight modulation even with small weights (eg: 1). It should not be too high
|
||||||
* though because it limits the number of servers in FWRR mode in order to
|
* though because it limits the number of servers in FWRR mode in order to
|
||||||
* prevent any integer overflow. The max number of servers per backend is
|
* prevent any integer overflow. The max number of servers per backend is
|
||||||
|
@ -27,18 +27,6 @@
|
|||||||
#include <types/buffers.h>
|
#include <types/buffers.h>
|
||||||
#include <types/hdr_idx.h>
|
#include <types/hdr_idx.h>
|
||||||
|
|
||||||
/*
|
|
||||||
* FIXME: break this into HTTP state and TCP socket state.
|
|
||||||
* See server.h for the other end.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* different possible states for the client side */
|
|
||||||
#define CL_STHEADERS 0
|
|
||||||
#define CL_STDATA 1
|
|
||||||
#define CL_STSHUTR 2
|
|
||||||
#define CL_STSHUTW 3
|
|
||||||
#define CL_STCLOSE 4
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FIXME: break this into HTTP state and TCP socket state.
|
* FIXME: break this into HTTP state and TCP socket state.
|
||||||
* See client.h for the other end.
|
* See client.h for the other end.
|
||||||
@ -46,12 +34,13 @@
|
|||||||
|
|
||||||
/* different possible states for the server side */
|
/* different possible states for the server side */
|
||||||
#define SV_STIDLE 0
|
#define SV_STIDLE 0
|
||||||
#define SV_STCONN 1
|
#define SV_STANALYZE 1 /* this server state is set by the client to study the body for server assignment */
|
||||||
#define SV_STHEADERS 2
|
#define SV_STCONN 2
|
||||||
#define SV_STDATA 3
|
#define SV_STHEADERS 3
|
||||||
#define SV_STSHUTR 4
|
#define SV_STDATA 4
|
||||||
#define SV_STSHUTW 5
|
#define SV_STSHUTR 5
|
||||||
#define SV_STCLOSE 6
|
#define SV_STSHUTW 6
|
||||||
|
#define SV_STCLOSE 7
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Transaction flags moved from session
|
* Transaction flags moved from session
|
||||||
@ -204,27 +193,28 @@ typedef enum {
|
|||||||
* which marks the end of the line (LF or CRLF).
|
* which marks the end of the line (LF or CRLF).
|
||||||
*/
|
*/
|
||||||
struct http_msg {
|
struct http_msg {
|
||||||
unsigned int msg_state; /* where we are in the current message parsing */
|
unsigned int msg_state; /* where we are in the current message parsing */
|
||||||
char *sol; /* start of line, also start of message when fully parsed */
|
char *sol; /* start of line, also start of message when fully parsed */
|
||||||
char *eol; /* end of line */
|
char *eol; /* end of line */
|
||||||
unsigned int som; /* Start Of Message, relative to buffer */
|
unsigned int som; /* Start Of Message, relative to buffer */
|
||||||
unsigned int col, sov; /* current header: colon, start of value */
|
unsigned int col, sov; /* current header: colon, start of value */
|
||||||
unsigned int eoh; /* End Of Headers, relative to buffer */
|
unsigned int eoh; /* End Of Headers, relative to buffer */
|
||||||
char **cap; /* array of captured headers (may be NULL) */
|
char **cap; /* array of captured headers (may be NULL) */
|
||||||
union { /* useful start line pointers, relative to buffer */
|
union { /* useful start line pointers, relative to buffer */
|
||||||
struct {
|
struct {
|
||||||
int l; /* request line length (not including CR) */
|
int l; /* request line length (not including CR) */
|
||||||
int m_l; /* METHOD length (method starts at ->som) */
|
int m_l; /* METHOD length (method starts at ->som) */
|
||||||
int u, u_l; /* URI, length */
|
int u, u_l; /* URI, length */
|
||||||
int v, v_l; /* VERSION, length */
|
int v, v_l; /* VERSION, length */
|
||||||
} rq; /* request line : field, length */
|
} rq; /* request line : field, length */
|
||||||
struct {
|
struct {
|
||||||
int l; /* status line length (not including CR) */
|
int l; /* status line length (not including CR) */
|
||||||
int v_l; /* VERSION length (version starts at ->som) */
|
int v_l; /* VERSION length (version starts at ->som) */
|
||||||
int c, c_l; /* CODE, length */
|
int c, c_l; /* CODE, length */
|
||||||
int r, r_l; /* REASON, length */
|
int r, r_l; /* REASON, length */
|
||||||
} st; /* status line : field, length */
|
} st; /* status line : field, length */
|
||||||
} sl; /* start line */
|
} sl; /* start line */
|
||||||
|
unsigned long long hdr_content_len; /* cache for parsed header value */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This is an HTTP transaction. It contains both a request message and a
|
/* This is an HTTP transaction. It contains both a request message and a
|
||||||
|
@ -165,6 +165,7 @@ struct proxy {
|
|||||||
int cookie_len; /* strlen(cookie_name), computed only once */
|
int cookie_len; /* strlen(cookie_name), computed only once */
|
||||||
char *url_param_name; /* name of the URL parameter used for hashing */
|
char *url_param_name; /* name of the URL parameter used for hashing */
|
||||||
int url_param_len; /* strlen(url_param_name), computed only once */
|
int url_param_len; /* strlen(url_param_name), computed only once */
|
||||||
|
unsigned url_param_post_limit; /* if checking POST body for URI parameter, max body to wait for */
|
||||||
char *appsession_name; /* name of the cookie to look for */
|
char *appsession_name; /* name of the cookie to look for */
|
||||||
int appsession_name_len; /* strlen(appsession_name), computed only once */
|
int appsession_name_len; /* strlen(appsession_name), computed only once */
|
||||||
int appsession_len; /* length of the appsession cookie value to be used */
|
int appsession_len; /* length of the appsession cookie value to be used */
|
||||||
|
163
src/backend.c
163
src/backend.c
@ -16,6 +16,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#include <common/compat.h>
|
#include <common/compat.h>
|
||||||
#include <common/config.h>
|
#include <common/config.h>
|
||||||
@ -1122,42 +1123,41 @@ static struct server *fwlc_get_next_server(struct proxy *p, struct server *srvto
|
|||||||
* are shared but cookies are not usable. If the parameter is not found, NULL
|
* are shared but cookies are not usable. If the parameter is not found, NULL
|
||||||
* is returned. If any server is found, it will be returned. If no valid server
|
* is returned. If any server is found, it will be returned. If no valid server
|
||||||
* is found, NULL is returned.
|
* is found, NULL is returned.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
|
struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
|
||||||
{
|
{
|
||||||
unsigned long hash = 0;
|
unsigned long hash = 0;
|
||||||
char *p;
|
const char *p;
|
||||||
|
const char *params;
|
||||||
int plen;
|
int plen;
|
||||||
|
|
||||||
|
/* when tot_weight is 0 then so is srv_count */
|
||||||
if (px->lbprm.tot_weight == 0)
|
if (px->lbprm.tot_weight == 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
if ((p = memchr(uri, '?', uri_len)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
if (px->lbprm.map.state & PR_MAP_RECALC)
|
if (px->lbprm.map.state & PR_MAP_RECALC)
|
||||||
recalc_server_map(px);
|
recalc_server_map(px);
|
||||||
|
|
||||||
p = memchr(uri, '?', uri_len);
|
|
||||||
if (!p)
|
|
||||||
return NULL;
|
|
||||||
p++;
|
p++;
|
||||||
|
|
||||||
uri_len -= (p - uri);
|
uri_len -= (p - uri);
|
||||||
plen = px->url_param_len;
|
plen = px->url_param_len;
|
||||||
|
params = p;
|
||||||
if (uri_len <= plen)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
while (uri_len > plen) {
|
while (uri_len > plen) {
|
||||||
/* Look for the parameter name followed by an equal symbol */
|
/* Look for the parameter name followed by an equal symbol */
|
||||||
if (p[plen] == '=') {
|
if (params[plen] == '=') {
|
||||||
/* skip the equal symbol */
|
if (memcmp(params, px->url_param_name, plen) == 0) {
|
||||||
uri = p;
|
/* OK, we have the parameter here at <params>, and
|
||||||
p += plen + 1;
|
|
||||||
uri_len -= plen + 1;
|
|
||||||
if (memcmp(uri, px->url_param_name, plen) == 0) {
|
|
||||||
/* OK, we have the parameter here at <uri>, and
|
|
||||||
* the value after the equal sign, at <p>
|
* the value after the equal sign, at <p>
|
||||||
|
* skip the equal symbol
|
||||||
*/
|
*/
|
||||||
|
p += plen + 1;
|
||||||
|
uri_len -= plen + 1;
|
||||||
|
|
||||||
while (uri_len && *p != '&') {
|
while (uri_len && *p != '&') {
|
||||||
hash = *p + (hash << 6) + (hash << 16) - hash;
|
hash = *p + (hash << 6) + (hash << 16) - hash;
|
||||||
uri_len--;
|
uri_len--;
|
||||||
@ -1166,18 +1166,116 @@ struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
|
|||||||
return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
|
return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* skip to next parameter */
|
/* skip to next parameter */
|
||||||
uri = p;
|
p = memchr(params, '&', uri_len);
|
||||||
p = memchr(uri, '&', uri_len);
|
|
||||||
if (!p)
|
if (!p)
|
||||||
return NULL;
|
return NULL;
|
||||||
p++;
|
p++;
|
||||||
uri_len -= (p - uri);
|
uri_len -= (p - params);
|
||||||
|
params = p;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* this does the same as the previous server_ph, but check the body contents
|
||||||
|
*/
|
||||||
|
struct server *get_server_ph_post(struct session *s)
|
||||||
|
{
|
||||||
|
unsigned long hash = 0;
|
||||||
|
struct http_txn *txn = &s->txn;
|
||||||
|
struct buffer *req = s->req;
|
||||||
|
struct http_msg *msg = &txn->req;
|
||||||
|
struct proxy *px = s->be;
|
||||||
|
unsigned int plen = px->url_param_len;
|
||||||
|
|
||||||
|
/* tot_weight appears to mean srv_count */
|
||||||
|
if (px->lbprm.tot_weight == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
unsigned long body = msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 : msg->eoh + 1;
|
||||||
|
unsigned long len = req->total - body;
|
||||||
|
const char *params = req->data + body;
|
||||||
|
|
||||||
|
if ( len == 0 )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (px->lbprm.map.state & PR_MAP_RECALC)
|
||||||
|
recalc_server_map(px);
|
||||||
|
|
||||||
|
struct hdr_ctx ctx;
|
||||||
|
ctx.idx = 0;
|
||||||
|
|
||||||
|
/* if the message is chunked, we skip the chunk size, but use the value as len */
|
||||||
|
http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
|
||||||
|
if ( ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",ctx.vlen)==0) {
|
||||||
|
unsigned int chunk = 0;
|
||||||
|
while ( params < req->rlim && !HTTP_IS_CRLF(*params)) {
|
||||||
|
char c = *params;
|
||||||
|
if (ishex(c)) {
|
||||||
|
unsigned int hex = toupper(c) - '0';
|
||||||
|
if ( hex > 9 )
|
||||||
|
hex -= 'A' - '9' - 1;
|
||||||
|
chunk = (chunk << 4) | hex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
params++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
/* spec says we get CRLF */
|
||||||
|
if (HTTP_IS_CRLF(*params) && HTTP_IS_CRLF(params[1]))
|
||||||
|
params += 2;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
/* ok we have some encoded length, just inspect the first chunk */
|
||||||
|
len = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *p = params;
|
||||||
|
|
||||||
|
while (len > plen) {
|
||||||
|
/* Look for the parameter name followed by an equal symbol */
|
||||||
|
if (params[plen] == '=') {
|
||||||
|
if (memcmp(params, px->url_param_name, plen) == 0) {
|
||||||
|
/* OK, we have the parameter here at <params>, and
|
||||||
|
* the value after the equal sign, at <p>
|
||||||
|
* skip the equal symbol
|
||||||
|
*/
|
||||||
|
p += plen + 1;
|
||||||
|
len -= plen + 1;
|
||||||
|
|
||||||
|
while (len && *p != '&') {
|
||||||
|
if (unlikely(!HTTP_IS_TOKEN(*p))) {
|
||||||
|
/* if in a POST, body must be URI encoded or its not a URI.
|
||||||
|
* Do not interprete any possible binary data as a parameter.
|
||||||
|
*/
|
||||||
|
if (likely(HTTP_IS_LWS(*p))) /* eol, uncertain uri len */
|
||||||
|
break;
|
||||||
|
return NULL; /* oh, no; this is not uri-encoded.
|
||||||
|
* This body does not contain parameters.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
hash = *p + (hash << 6) + (hash << 16) - hash;
|
||||||
|
len--;
|
||||||
|
p++;
|
||||||
|
/* should we break if vlen exceeds limit? */
|
||||||
|
}
|
||||||
|
return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* skip to next parameter */
|
||||||
|
p = memchr(params, '&', len);
|
||||||
|
if (!p)
|
||||||
|
return NULL;
|
||||||
|
p++;
|
||||||
|
len -= (p - params);
|
||||||
|
params = p;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This function marks the session as 'assigned' in direct or dispatch modes,
|
* This function marks the session as 'assigned' in direct or dispatch modes,
|
||||||
* or tries to assign one in balance mode, according to the algorithm. It does
|
* or tries to assign one in balance mode, according to the algorithm. It does
|
||||||
@ -1254,9 +1352,15 @@ int assign_server(struct session *s)
|
|||||||
break;
|
break;
|
||||||
case BE_LB_ALGO_PH:
|
case BE_LB_ALGO_PH:
|
||||||
/* URL Parameter hashing */
|
/* URL Parameter hashing */
|
||||||
s->srv = get_server_ph(s->be,
|
if (s->txn.meth == HTTP_METH_POST &&
|
||||||
s->txn.req.sol + s->txn.req.sl.rq.u,
|
memchr(s->txn.req.sol + s->txn.req.sl.rq.u, '&',
|
||||||
s->txn.req.sl.rq.u_l);
|
s->txn.req.sl.rq.u_l ) == NULL)
|
||||||
|
s->srv = get_server_ph_post(s);
|
||||||
|
else
|
||||||
|
s->srv = get_server_ph(s->be,
|
||||||
|
s->txn.req.sol + s->txn.req.sl.rq.u,
|
||||||
|
s->txn.req.sl.rq.u_l);
|
||||||
|
|
||||||
if (!s->srv) {
|
if (!s->srv) {
|
||||||
/* parameter not found, fall back to round robin on the map */
|
/* parameter not found, fall back to round robin on the map */
|
||||||
s->srv = get_server_rr_with_conns(s->be, srvtoavoid);
|
s->srv = get_server_rr_with_conns(s->be, srvtoavoid);
|
||||||
@ -1879,6 +1983,21 @@ int backend_parse_balance(const char **args, char *err, int errlen, struct proxy
|
|||||||
free(curproxy->url_param_name);
|
free(curproxy->url_param_name);
|
||||||
curproxy->url_param_name = strdup(args[1]);
|
curproxy->url_param_name = strdup(args[1]);
|
||||||
curproxy->url_param_len = strlen(args[1]);
|
curproxy->url_param_len = strlen(args[1]);
|
||||||
|
if ( *args[2] ) {
|
||||||
|
if (strcmp(args[2], "check_post")) {
|
||||||
|
snprintf(err, errlen, "'balance url_param' only accepts check_post modifier.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (*args[3]) {
|
||||||
|
/* TODO: maybe issue a warning if there is no value, no digits or too long */
|
||||||
|
curproxy->url_param_post_limit = str2ui(args[3]);
|
||||||
|
}
|
||||||
|
/* if no limit, or faul value in args[3], then default to a moderate wordlen */
|
||||||
|
if (!curproxy->url_param_post_limit)
|
||||||
|
curproxy->url_param_post_limit = 48;
|
||||||
|
else if ( curproxy->url_param_post_limit < 3 )
|
||||||
|
curproxy->url_param_post_limit = 3; /* minimum example: S=3 or \r\nS=6& */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri' and 'url_param' options.");
|
snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri' and 'url_param' options.");
|
||||||
|
@ -232,7 +232,8 @@ int event_accept(int fd) {
|
|||||||
|
|
||||||
if (p->mode == PR_MODE_HTTP) {
|
if (p->mode == PR_MODE_HTTP) {
|
||||||
txn->status = -1;
|
txn->status = -1;
|
||||||
|
txn->req.hdr_content_len = 0LL;
|
||||||
|
txn->rsp.hdr_content_len = 0LL;
|
||||||
txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
|
txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
|
||||||
txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */
|
txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */
|
||||||
txn->req.sol = txn->req.eol = NULL;
|
txn->req.sol = txn->req.eol = NULL;
|
||||||
|
@ -102,8 +102,8 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
|
|||||||
#define FDSETS_ARE_INT_ALIGNED
|
#define FDSETS_ARE_INT_ALIGNED
|
||||||
#ifdef FDSETS_ARE_INT_ALIGNED
|
#ifdef FDSETS_ARE_INT_ALIGNED
|
||||||
|
|
||||||
#define WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
|
#define WE_REALLY_KNOW_THAT_FDSETS_ARE_INTS
|
||||||
#ifdef WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
|
#ifdef WE_REALLY_KNOW_THAT_FDSETS_ARE_INTS
|
||||||
sr = (rn >> count) & 1;
|
sr = (rn >> count) & 1;
|
||||||
sw = (wn >> count) & 1;
|
sw = (wn >> count) & 1;
|
||||||
#else
|
#else
|
||||||
|
144
src/proto_http.c
144
src/proto_http.c
@ -287,7 +287,7 @@ const struct http_method_desc http_methods[26][3] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* It is about twice as fast on recent architectures to lookup a byte in a
|
/* It is about twice as fast on recent architectures to lookup a byte in a
|
||||||
* table than two perform a boolean AND or OR between two tests. Refer to
|
* table than to perform a boolean AND or OR between two tests. Refer to
|
||||||
* RFC2616 for those chars.
|
* RFC2616 for those chars.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -2065,6 +2065,83 @@ int process_cli(struct session *t)
|
|||||||
goto return_bad_req;
|
goto return_bad_req;
|
||||||
t->flags |= SN_CONN_CLOSED;
|
t->flags |= SN_CONN_CLOSED;
|
||||||
}
|
}
|
||||||
|
/* Before we switch to data, was assignment set in manage_client_side_cookie?
|
||||||
|
* If not assigned, perhaps we are balancing on url_param, but this is a
|
||||||
|
* POST; and the parameters are in the body, maybe scan there to find our server.
|
||||||
|
* (unless headers overflowed the buffer?)
|
||||||
|
*/
|
||||||
|
if (!(t->flags & (SN_ASSIGNED|SN_DIRECT)) &&
|
||||||
|
t->txn.meth == HTTP_METH_POST && t->be->url_param_name != NULL &&
|
||||||
|
t->be->url_param_post_limit != 0 && req->total < BUFSIZE &&
|
||||||
|
memchr(msg->sol + msg->sl.rq.u, '?', msg->sl.rq.u_l) == NULL) {
|
||||||
|
/* are there enough bytes here? total == l || r || rlim ?
|
||||||
|
* len is unsigned, but eoh is int,
|
||||||
|
* how many bytes of body have we received?
|
||||||
|
* eoh is the first empty line of the header
|
||||||
|
*/
|
||||||
|
/* already established CRLF or LF at eoh, move to start of message, find message length in buffer */
|
||||||
|
unsigned long len = req->total - (msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 : msg->eoh + 1);
|
||||||
|
|
||||||
|
/* If we have HTTP/1.1 and Expect: 100-continue, then abort.
|
||||||
|
* We can't assume responsibility for the server's decision,
|
||||||
|
* on this URI and header set. See rfc2616: 14.20, 8.2.3,
|
||||||
|
* We also can't change our mind later, about which server to choose, so round robin.
|
||||||
|
*/
|
||||||
|
if ((likely(msg->sl.rq.v_l == 8) && req->data[msg->som + msg->sl.rq.v + 7] == '1')) {
|
||||||
|
struct hdr_ctx ctx;
|
||||||
|
ctx.idx = 0;
|
||||||
|
/* Expect is allowed in 1.1, look for it */
|
||||||
|
http_find_header2("Expect", 6, msg->sol, &txn->hdr_idx, &ctx);
|
||||||
|
if (ctx.idx != 0 &&
|
||||||
|
unlikely(ctx.vlen == 12 && strncasecmp(ctx.line+ctx.val,"100-continue",12)==0))
|
||||||
|
/* We can't reliablly stall and wait for data, because of
|
||||||
|
* .NET clients that don't conform to rfc2616; so, no need for
|
||||||
|
* the next block to check length expectations.
|
||||||
|
* We could send 100 status back to the client, but then we need to
|
||||||
|
* re-write headers, and send the message. And this isn't the right
|
||||||
|
* place for that action.
|
||||||
|
* TODO: support Expect elsewhere and delete this block.
|
||||||
|
*/
|
||||||
|
goto end_check_maybe_wait_for_body;
|
||||||
|
}
|
||||||
|
if ( likely(len > t->be->url_param_post_limit) ) {
|
||||||
|
/* nothing to do, we got enough */
|
||||||
|
} else {
|
||||||
|
/* limit implies we are supposed to need this many bytes
|
||||||
|
* to find the parameter. Let's see how many bytes we can wait for.
|
||||||
|
*/
|
||||||
|
long long hint = len;
|
||||||
|
struct hdr_ctx ctx;
|
||||||
|
ctx.idx = 0;
|
||||||
|
http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
|
||||||
|
if (unlikely(ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",7)==0)) {
|
||||||
|
t->srv_state = SV_STANALYZE;
|
||||||
|
} else {
|
||||||
|
ctx.idx = 0;
|
||||||
|
http_find_header2("Content-Length", 14, msg->sol, &txn->hdr_idx, &ctx);
|
||||||
|
/* now if we have a length, we'll take the hint */
|
||||||
|
if ( ctx.idx ) {
|
||||||
|
/* We have Content-Length */
|
||||||
|
if ( strl2llrc(ctx.line+ctx.val,ctx.vlen, &hint) )
|
||||||
|
hint = 0; /* parse failure, untrusted client */
|
||||||
|
else {
|
||||||
|
if ( hint > 0 )
|
||||||
|
msg->hdr_content_len = hint;
|
||||||
|
else
|
||||||
|
hint = 0; /* bad client, sent negative length */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* but limited to what we care about, maybe we don't expect any entity data (hint == 0) */
|
||||||
|
if ( t->be->url_param_post_limit < hint )
|
||||||
|
hint = t->be->url_param_post_limit;
|
||||||
|
/* now do we really need to buffer more data? */
|
||||||
|
if ( len < hint )
|
||||||
|
t->srv_state = SV_STANALYZE;
|
||||||
|
/* else... There are no body bytes to wait for */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end_check_maybe_wait_for_body:
|
||||||
|
|
||||||
/*************************************************************
|
/*************************************************************
|
||||||
* OK, that's finished for the headers. We have done what we *
|
* OK, that's finished for the headers. We have done what we *
|
||||||
@ -2436,7 +2513,12 @@ int process_srv(struct session *t)
|
|||||||
//EV_FD_ISSET(t->srv_fd, DIR_RD), EV_FD_ISSET(t->srv_fd, DIR_WR)
|
//EV_FD_ISSET(t->srv_fd, DIR_RD), EV_FD_ISSET(t->srv_fd, DIR_WR)
|
||||||
//);
|
//);
|
||||||
if (s == SV_STIDLE) {
|
if (s == SV_STIDLE) {
|
||||||
if (c == CL_STHEADERS)
|
/* NOTE: The client processor may switch to SV_STANALYZE, which switches back SV_STIDLE.
|
||||||
|
* This is logcially after CL_STHEADERS completed, CL_STDATA has started, but
|
||||||
|
* we need to defer server selection until more data arrives, if possible.
|
||||||
|
* This is rare, and only if balancing on parameter hash with values in the entity of a POST
|
||||||
|
*/
|
||||||
|
if (c == CL_STHEADERS )
|
||||||
return 0; /* stay in idle, waiting for data to reach the client side */
|
return 0; /* stay in idle, waiting for data to reach the client side */
|
||||||
else if (c == CL_STCLOSE || c == CL_STSHUTW ||
|
else if (c == CL_STCLOSE || c == CL_STSHUTW ||
|
||||||
(c == CL_STSHUTR &&
|
(c == CL_STSHUTR &&
|
||||||
@ -3531,6 +3613,60 @@ int process_srv(struct session *t)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
else if (s == SV_STANALYZE){
|
||||||
|
/* this server state is set by the client to study the body for server assignment */
|
||||||
|
|
||||||
|
/* Have we been through this long enough to timeout? */
|
||||||
|
if (!tv_isle(&req->rex, &now)) {
|
||||||
|
/* balance url_param check_post should have been the only to get into this.
|
||||||
|
* just wait for data, check to compare how much
|
||||||
|
*/
|
||||||
|
struct http_msg * msg = &t->txn.req;
|
||||||
|
unsigned long body = msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 :msg->eoh + 1;
|
||||||
|
unsigned long len = req->total - body;
|
||||||
|
long long limit = t->be->url_param_post_limit;
|
||||||
|
struct hdr_ctx ctx;
|
||||||
|
ctx.idx = 0;
|
||||||
|
/* now if we have a length, we'll take the hint */
|
||||||
|
http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
|
||||||
|
if ( ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",ctx.vlen)==0) {
|
||||||
|
unsigned int chunk = 0;
|
||||||
|
while ( body < req->total && !HTTP_IS_CRLF(msg->sol[body])) {
|
||||||
|
char c = msg->sol[body];
|
||||||
|
if (ishex(c)) {
|
||||||
|
unsigned int hex = toupper(c) - '0';
|
||||||
|
if ( hex > 9 )
|
||||||
|
hex -= 'A' - '9' - 1;
|
||||||
|
chunk = (chunk << 4) | hex;
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
body++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
if ( body == req->total )
|
||||||
|
return 0; /* end of buffer? data missing! */
|
||||||
|
|
||||||
|
if ( memcmp(msg->sol+body, "\r\n", 2) != 0 )
|
||||||
|
return 0; /* chunked encoding len ends with CRLF, and we don't have it yet */
|
||||||
|
|
||||||
|
/* if we support more then one chunk here, we have to do it again when assigning server
|
||||||
|
1. how much entity data do we have? new var
|
||||||
|
2. should save entity_start, entity_cursor, elen & rlen in req; so we don't repeat scanning here
|
||||||
|
3. test if elen > limit, or set new limit to elen if 0 (end of entity found)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( chunk < limit )
|
||||||
|
limit = chunk; /* only reading one chunk */
|
||||||
|
} else {
|
||||||
|
if ( msg->hdr_content_len < limit )
|
||||||
|
limit = msg->hdr_content_len;
|
||||||
|
}
|
||||||
|
if ( len < limit )
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
t->srv_state=SV_STIDLE;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
else { /* SV_STCLOSE : nothing to do */
|
else { /* SV_STCLOSE : nothing to do */
|
||||||
if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
|
if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
|
||||||
int len;
|
int len;
|
||||||
@ -3549,7 +3685,7 @@ int process_srv(struct session *t)
|
|||||||
* called with s->cli_state == CL_STSHUTR. Right now, only statistics can be
|
* called with s->cli_state == CL_STSHUTR. Right now, only statistics can be
|
||||||
* produced. It stops by itself by unsetting the SN_SELF_GEN flag from the
|
* produced. It stops by itself by unsetting the SN_SELF_GEN flag from the
|
||||||
* session, which it uses to keep on being called when there is free space in
|
* session, which it uses to keep on being called when there is free space in
|
||||||
* the buffer, of simply by letting an empty buffer upon return. It returns 1
|
* the buffer, or simply by letting an empty buffer upon return. It returns 1
|
||||||
* if it changes the session state from CL_STSHUTR, otherwise 0.
|
* if it changes the session state from CL_STSHUTR, otherwise 0.
|
||||||
*/
|
*/
|
||||||
int produce_content(struct session *s)
|
int produce_content(struct session *s)
|
||||||
@ -3640,7 +3776,7 @@ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hd
|
|||||||
/* Swithing Proxy */
|
/* Swithing Proxy */
|
||||||
t->be = (struct proxy *) exp->replace;
|
t->be = (struct proxy *) exp->replace;
|
||||||
|
|
||||||
/* right now, the backend switch is not too much complicated
|
/* right now, the backend switch is not overly complicated
|
||||||
* because we have associated req_cap and rsp_cap to the
|
* because we have associated req_cap and rsp_cap to the
|
||||||
* frontend, and the beconn will be updated later.
|
* frontend, and the beconn will be updated later.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user