From d86118a1ef55f50a9005210c9f6b2205cb994676 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 20 Nov 2014 22:25:50 +0100 Subject: MAJOR: httpterm: basic header-only features Now it is possible to get a response. The response is still fed through the response analysers and is parsed by the HTTP message parser. Possibly that it will be possible to improve this by disabling all response analysers and correctly settings the relevant bits. It could also be considered that being able to process the response is useful (eg: mangle headers, emit data using http-response). No body is emitted yet. --- src/proto_http.c | 367 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 226 insertions(+), 141 deletions(-) diff --git a/src/proto_http.c b/src/proto_http.c index 13a9019..9fc9495 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3161,24 +3161,95 @@ int http_handle_stats(struct session *s, struct channel *req) /************************* THE CODE BELOW IS FOR HTTPTERM ******************/ +/* values for appctx->st0 */ +enum hterm_state { + HT_INIT = 0, /* not initialized yet */ + HT_REQ_HDR, /* reading request headers */ + HT_REQ_BODY, /* reading request body */ + HT_RES_HDR, /* sending response headers */ + HT_RES_BODY, /* sending response body */ + HT_RES_FINAL, /* sending last 0 CRLF CRLF for chunks */ + HT_RES_DONE, /* finished responding */ +}; + +/* cast for appctx->ctx. This one offers at least 3 ptrs + 8 ints, so we fit */ +struct hterm_ctx { + long long req_size; /* desired response size in bytes / how much left */ + int req_code; /* desired response code */ + int req_time; /* desired response time in ms */ + int req_chunked; /* if non-null, max chunk size */ + int req_cache; /* cachable if non-null */ + int req_nosplice; /* disable splicing if set */ + int req_random; /* random response size */ + int req_pieces; /* limit send() to random sizes without merging */ +}; + static int httpterm_send_http_headers(struct stream_interface *si) { struct session *s = session_from_task(si->owner); struct appctx *appctx = objt_appctx(si->end); + struct hterm_ctx *hctx = (void *)&appctx->ctx; + struct http_txn *txn = &s->txn; + struct http_msg *msg = &txn->req; - chunk_printf(&trash, - "HTTP/1.1 200 OK\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "Content-Type: %s\r\n", - (appctx->ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain"); + chunk_reset(&trash); - /* we don't send the CRLF in chunked mode, it will be sent with the first chunk's size */ + memcpy(trash.str, "HTTP/1.1 200 OK", 15); + trash.len += 15; - if (appctx->ctx.stats.flags & STAT_CHUNKED) - chunk_appendf(&trash, "Transfer-Encoding: chunked\r\n"); - else - chunk_appendf(&trash, "\r\n"); + if (unlikely(hctx->req_code != 200)) { + trash.str[9] = '0' + (hctx->req_code / 100) % 10; + trash.str[10] = '0' + (hctx->req_code / 10) % 10; + trash.str[11] = '0' + (hctx->req_code % 10); + } + + if (((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL || + (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL)) { + /* keep-alive possible */ + if (!(msg->flags & HTTP_MSGF_VER_11)) { + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(trash.str + trash.len, "\r\nProxy-Connection: keep-alive", 30); + trash.len += 30; + } else { + memcpy(trash.str + trash.len, "\r\nConnection: keep-alive", 24); + trash.len += 24; + } + } + } else { + /* keep-alive not possible */ + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(trash.str + trash.len, "\r\nProxy-Connection: close", 25); + trash.len += 25; + } else { + memcpy(trash.str + trash.len, "\r\nConnection: close", 19); + trash.len += 19; + } + } + + if (hctx->req_chunked) { + memcpy(trash.str + trash.len, "\r\nTransfer-Encoding: chunked", 28); + trash.len += 28; + } else { + memcpy(trash.str + trash.len, "\r\nContent-length: ", 18); + trash.len += 18; + trash.len = ulltoa(hctx->req_size, trash.str + trash.len, 25) - trash.str; + } + + if (!hctx->req_cache) { + memcpy(trash.str + trash.len, "\r\nCache-Control: no-cache", 25); + trash.len += 25; + } + + memcpy(trash.str + trash.len, "\r\nContent-Type: text/plain", 26); + trash.len += 26; + + /* Trick: we don't send the CRLF in chunked mode, it will be sent with + * the first chunk's size. + */ + memcpy(trash.str + trash.len, "\r\n\r\n", 4); + trash.len += 2; + if (!hctx->req_chunked) + trash.len += 2; s->txn.status = 200; s->logs.tv_request = now; @@ -3200,48 +3271,49 @@ static void httpterm_io_handler(struct stream_interface *si) struct session *s = session_from_task(si->owner); struct channel *req = si->ob; struct channel *res = si->ib; + struct hterm_ctx *hctx = (void *)&appctx->ctx; if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) goto out; /* check that the output is not closed */ if (res->flags & (CF_SHUTW|CF_SHUTW_NOW)) - appctx->st0 = STAT_HTTP_DONE; + appctx->st0 = HT_RES_DONE; /* all states are processed in sequence */ - if (appctx->st0 == STAT_HTTP_HEAD) { + if (appctx->st0 == HT_RES_HDR) { if (httpterm_send_http_headers(si)) { if (s->txn.meth == HTTP_METH_HEAD) - appctx->st0 = STAT_HTTP_DONE; + appctx->st0 = HT_RES_DONE; else - appctx->st0 = STAT_HTTP_DUMP; + appctx->st0 = HT_RES_BODY; } } - if (appctx->st0 == STAT_HTTP_DUMP) { + if (appctx->st0 == HT_RES_BODY) { unsigned int prev_len = si->ib->buf->i; unsigned int data_len; unsigned int last_len; unsigned int last_fwd = 0; - if (appctx->ctx.stats.flags & STAT_CHUNKED) { - /* One difficulty we're facing is that we must prevent - * the input data from being automatically forwarded to - * the output area. For this, we temporarily disable - * forwarding on the channel. - */ - last_fwd = si->ib->to_forward; - si->ib->to_forward = 0; - chunk_printf(&trash, "\r\n000000\r\n"); - if (bi_putchk(si->ib, &trash) == -1) { - si->ib->to_forward = last_fwd; - goto fail; - } - } + //if (hctx->req_chunked) { + // /* One difficulty we're facing is that we must prevent + // * the input data from being automatically forwarded to + // * the output area. For this, we temporarily disable + // * forwarding on the channel. + // */ + // last_fwd = si->ib->to_forward; + // si->ib->to_forward = 0; + // chunk_printf(&trash, "\r\n000000\r\n"); + // if (bi_putchk(si->ib, &trash) == -1) { + // si->ib->to_forward = last_fwd; + // goto fail; + // } + //} data_len = si->ib->buf->i; //if (stats_dump_stat_to_buffer(si, s->be->uri_auth)) - appctx->st0 = STAT_HTTP_DONE; + appctx->st0 = HT_RES_DONE; last_len = si->ib->buf->i; @@ -3252,20 +3324,20 @@ static void httpterm_io_handler(struct stream_interface *si) * only do that because no forwarding is scheduled on the stats * applet. */ - if (appctx->ctx.stats.flags & STAT_CHUNKED) { - si->ib->total -= (last_len - prev_len); - si->ib->buf->i -= (last_len - prev_len); - - if (last_len != data_len) { - chunk_printf(&trash, "\r\n%06x\r\n", (last_len - data_len)); - bi_putchk(si->ib, &trash); - - si->ib->total += (last_len - data_len); - si->ib->buf->i += (last_len - data_len); - } - /* now re-enable forwarding */ - channel_forward(si->ib, last_fwd); - } + //if (hctx->req_chunked) { + // si->ib->total -= (last_len - prev_len); + // si->ib->buf->i -= (last_len - prev_len); + // + // if (last_len != data_len) { + // chunk_printf(&trash, "\r\n%06x\r\n", (last_len - data_len)); + // bi_putchk(si->ib, &trash); + // + // si->ib->total += (last_len - data_len); + // si->ib->buf->i += (last_len - data_len); + // } + // /* now re-enable forwarding */ + // channel_forward(si->ib, last_fwd); + //} } // if (appctx->st0 == STAT_HTTP_POST) { @@ -3280,12 +3352,12 @@ static void httpterm_io_handler(struct stream_interface *si) // appctx->st0 = STAT_HTTP_DONE; // } - if (appctx->st0 == STAT_HTTP_DONE) { - if (appctx->ctx.stats.flags & STAT_CHUNKED) { - chunk_printf(&trash, "\r\n0\r\n\r\n"); - if (bi_putchk(si->ib, &trash) == -1) - goto fail; - } + if (appctx->st0 == HT_RES_DONE) { + //if (hctx->req_chunked) { + // chunk_printf(&trash, "\r\n0\r\n\r\n"); + // if (bi_putchk(si->ib, &trash) == -1) + // goto fail; + //} /* eat the whole request */ bo_skip(si->ob, si->ob->buf->o); res->flags |= CF_READ_NULL; @@ -3295,12 +3367,12 @@ static void httpterm_io_handler(struct stream_interface *si) if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST)) si_shutw(si); - if (appctx->st0 == STAT_HTTP_DONE) { - if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST)) { - si_shutr(si); - res->flags |= CF_READ_NULL; - } - } +// if (appctx->st0 == HT_RES_DONE) { +// if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST)) { +// si_shutr(si); +// res->flags |= CF_READ_NULL; +// } +// } fail: /* update all other flags and resync with the other side */ @@ -3315,6 +3387,8 @@ static void httpterm_io_handler(struct stream_interface *si) /* check that we have released everything then unregister */ stream_int_unregister_handler(si); } +//printf(" leaving with res->i=%d o=%d\n", res->buf->i, res->buf->o); +//printf(" si->st=%d st0=%d\n", si->state, appctx->st0); } struct si_applet httpterm_applet = { @@ -3330,111 +3404,122 @@ int http_handle_httpterm(struct session *s, struct channel *req) struct stream_interface *si = s->rep->prod; struct http_txn *txn = &s->txn; struct http_msg *msg = &txn->req; - //struct uri_auth *uri_auth = s->be->uri_auth; - const char *uri, *h, *lookup; + const char *end, *arg, *next; struct appctx *appctx; + struct hterm_ctx *hctx; + unsigned int mult, result; appctx = si_appctx(si); - memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats)); - appctx->st1 = appctx->st2 = 0; - appctx->ctx.stats.st_code = STAT_STATUS_INIT; - appctx->ctx.stats.flags |= STAT_FMT_HTML; /* assume HTML mode by default */ - if ((msg->flags & HTTP_MSGF_VER_11) && (s->txn.meth != HTTP_METH_HEAD)) - appctx->ctx.stats.flags |= STAT_CHUNKED; - - uri = msg->chn->buf->p + msg->sl.rq.u; - lookup = uri + 1; // "/" // + uri_auth->uri_len; + appctx->st0 = appctx->st1 = appctx->st2 = 0; + + hctx = (void *)&appctx->ctx; + memset(hctx, 0, sizeof(*hctx)); + + hctx->req_code = hctx->req_size = hctx->req_cache = hctx->req_time = -1; + + end = msg->chn->buf->p + msg->sl.rq.u + msg->sl.rq.u_l; + arg = memchr(msg->chn->buf->p + msg->sl.rq.u + 1, '?', msg->sl.rq.u_l - 1); + if (!arg) { + hctx->req_code = 200; + hctx->req_size = 0; + hctx->req_time = 0; + hctx->req_cache = 1; + goto skip_args; + } + arg++; + + while (arg + 2 <= end && arg[1] == '=') { + next = arg + 2; + result = __read_uint(&next, end); + if (next > arg + 2) { + mult = 0; + do { + if (*next == 'k' || *next == 'K') + mult += 10; + else if (*next == 'm' || *next == 'M') + mult += 20; + else if (*next == 'g' || *next == 'G') + mult += 30; + else + break; + next++; + } while (next < end && *next); - for (h = lookup; h <= uri + msg->sl.rq.u_l - 3; h++) { - if (memcmp(h, ";up", 3) == 0) { - appctx->ctx.stats.flags |= STAT_HIDE_DOWN; - break; + switch (*arg) { + case 's': + hctx->req_size = result << mult; + break; + case 'r': + hctx->req_code = result << mult; + break; + case 't': + hctx->req_time = result << mult; + break; + case 'c': + hctx->req_cache = result << mult; + break; + case 'k': + if ((msg->flags & HTTP_MSGF_VER_11) && (s->txn.meth != HTTP_METH_HEAD)) + hctx->req_chunked = result; + break; + case 'S': + hctx->req_nosplice = !result; + break; + case 'R': + hctx->req_random = result; + if (result) + hctx->req_nosplice = 1; + break; + case 'p': + hctx->req_pieces = result; + if (result) { + hctx->req_nosplice = 1; + //setsockopt(hctx->cli_fd, SOL_TCP, TCP_NODELAY, (char *) &one, sizeof(one)); + } + break; + } + arg = next; } - } - - //if (uri_auth->refresh) { - // for (h = lookup; h <= uri + msg->sl.rq.u_l - 10; h++) { - // if (memcmp(h, ";norefresh", 10) == 0) { - // appctx->ctx.stats.flags |= STAT_NO_REFRESH; - // break; - // } - // } - //} - for (h = lookup; h <= uri + msg->sl.rq.u_l - 4; h++) { - if (memcmp(h, ";csv", 4) == 0) { - appctx->ctx.stats.flags &= ~STAT_FMT_HTML; + if (*arg == '&') + arg++; + else break; - } } - for (h = lookup; h <= uri + msg->sl.rq.u_l - 8; h++) { - if (memcmp(h, ";st=", 4) == 0) { - int i; - h += 4; - appctx->ctx.stats.st_code = STAT_STATUS_UNKN; - for (i = STAT_STATUS_INIT + 1; i < STAT_STATUS_SIZE; i++) { - if (strncmp(stat_status_codes[i], h, 4) == 0) { - appctx->ctx.stats.st_code = i; - break; - } - } - break; - } - } + /* default settings */ + if (hctx->req_size < 0) + hctx->req_size = 0; - appctx->ctx.stats.scope_str = 0; - appctx->ctx.stats.scope_len = 0; - for (h = lookup; h <= uri + msg->sl.rq.u_l - 8; h++) { - if (memcmp(h, STAT_SCOPE_INPUT_NAME "=", strlen(STAT_SCOPE_INPUT_NAME) + 1) == 0) { - int itx = 0; - const char *h2; - char scope_txt[STAT_SCOPE_TXT_MAXLEN + 1]; - const char *err; + if (hctx->req_code < 0) + hctx->req_code = 200; - h += strlen(STAT_SCOPE_INPUT_NAME) + 1; - h2 = h; - appctx->ctx.stats.scope_str = h2 - msg->chn->buf->p; - while (*h != ';' && *h != '\0' && *h != '&' && *h != ' ' && *h != '\n') { - itx++; - h++; - } + if (hctx->req_time < 0) + hctx->req_time = 0; - if (itx > STAT_SCOPE_TXT_MAXLEN) - itx = STAT_SCOPE_TXT_MAXLEN; - appctx->ctx.stats.scope_len = itx; + if (hctx->req_cache < 0) + hctx->req_cache = 1; - /* scope_txt = search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ - memcpy(scope_txt, h2, itx); - scope_txt[itx] = '\0'; - err = invalid_char(scope_txt); - if (err) { - /* bad char in search text => clear scope */ - appctx->ctx.stats.scope_str = 0; - appctx->ctx.stats.scope_len = 0; - } - break; - } - } + /* when chunk mode is required, the size is adjusted by the + * chunk encoding overhead. each chunk contain 1 data byte. + * the final 3 bytes are for the "0\r\n" + */ + //if (hctx->req_chunked) + // hctx->req_size = hctx->req_size * (CHUNK_LEN * 2) + 3; + skip_args: /* Was the status page requested with a POST ? */ if (unlikely(txn->meth == HTTP_METH_POST && txn->req.body_len > 0)) { - if (appctx->ctx.stats.flags & STAT_ADMIN) { - /* we'll need the request body, possibly after sending 100-continue */ - req->analysers |= AN_REQ_HTTP_BODY; - appctx->st0 = STAT_HTTP_POST; - } - else { - appctx->ctx.stats.st_code = STAT_STATUS_DENY; - appctx->st0 = STAT_HTTP_LAST; - } + /* we'll need the request body, possibly after sending 100-continue */ + req->analysers |= AN_REQ_HTTP_BODY; + appctx->st0 = HT_REQ_BODY; } else { /* So it was another method (GET/HEAD) */ - appctx->st0 = STAT_HTTP_HEAD; + appctx->st0 = HT_RES_HDR; } - s->task->nice = -32; /* small boost for HTTP statistics */ + //s->task->nice = -32; /* small boost for HTTP statistics */ return 1; } -- 1.7.12.1