]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: proto_htx: Use full HTX messages to send 103-Early-Hints responses
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 28 Nov 2018 12:55:14 +0000 (13:55 +0100)
committerWilly Tarreau <w@1wt.eu>
Sat, 1 Dec 2018 16:37:27 +0000 (17:37 +0100)
Instead of replying by adding an OOB block in the HTX structure, we now add a
valid HTX message. A header block is added to each early-hint rule, prefixed by
the start line if it is the first one. The response is terminated and forwarded
when the rules execution is stopped or when a rule of another type is applied.

src/proto_htx.c

index 026580036d8c8996d902016674d9f574a0428681..7a8101212b6ef0816b4f3b6ff6cd61cec27dd635 100644 (file)
@@ -2469,6 +2469,7 @@ int htx_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct
        s->logs.tv_request = now;
 
        /* FIXME: close for now, but it could be cool to handle the keep-alive here */
+       /* FIXME: check if EOM is here to do keep-alive or not */
        if (unlikely(txn->flags & TX_USE_PX_CONN)) {
                if (!chunk_memcat(chunk, "\r\nProxy-Connection: close\r\n\r\n", 29))
                        goto leave;
@@ -2532,61 +2533,70 @@ static int htx_transform_header(struct stream* s, struct channel *chn, struct ht
        return ret;
 }
 
+
+/* Terminate a 103-Erly-hints response and send it to the client. It returns 0
+ * on success and -1 on error. The response channel is updated accordingly.
+ */
+static int htx_reply_103_early_hints(struct channel *res)
+{
+       struct htx *htx = htx_from_buf(&res->buf);
+       size_t data;
+
+       if (!htx_add_endof(htx, HTX_BLK_EOH) || !htx_add_endof(htx, HTX_BLK_EOM)) {
+               /* If an error occurred during an Early-hint rule,
+                * remove the incomplete HTTP 103 response from the
+                * buffer */
+               channel_truncate(res);
+               return -1;
+       }
+
+       data = htx->data - co_data(res);
+       b_set_data(&res->buf, b_size(&res->buf));
+       c_adv(res, data);
+       res->total += data;
+       return 0;
+}
+
 /*
  * Build an HTTP Early Hint HTTP 103 response header with <name> as name and with a value
  * built according to <fmt> log line format.
- * If <early_hints> is NULL, it is allocated and the HTTP 103 response first
- * line is inserted before the header. If an error occurred <early_hints> is
- * released and NULL is returned. On success the updated buffer is returned.
+ * If <early_hints> is 0, it is starts a new response by adding the start
+ * line. If an error occurred -1 is returned. On success 0 is returned. The
+ * channel is not updated here. It must be done calling the function
+ * htx_reply_103_early_hints().
  */
-static struct buffer *htx_apply_early_hint_rule(struct stream* s, struct buffer *early_hints,
-                                               const char* name, unsigned int name_len,
-                                               struct list *fmt)
+static int htx_add_early_hint_header(struct stream *s, int early_hints, const struct ist name, struct list *fmt)
 {
+       struct channel *res = &s->res;
+       struct htx *htx = htx_from_buf(&res->buf);
+       struct buffer *value = alloc_trash_chunk();
+
        if (!early_hints) {
-               early_hints = alloc_trash_chunk();
-               if (!early_hints)
-                       goto fail;
-               if (!chunk_memcat(early_hints, HTTP_103.ptr, HTTP_103.len))
+               struct htx_sl *sl;
+               unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
+                                     HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
+
+               sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
+                                   ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
+               if (!sl)
                        goto fail;
+               sl->info.res.status = 103;
        }
 
-       if (!chunk_memcat(early_hints, name, name_len) || !chunk_memcat(early_hints, ": ", 2))
-               goto fail;
-
-       early_hints->data += build_logline(s, b_tail(early_hints), b_room(early_hints), fmt);
-       if (!chunk_memcat(early_hints, "\r\n", 2))
+       value->data = build_logline(s, b_tail(value), b_room(value), fmt);
+       if (!htx_add_header(htx, name, ist2(b_head(value), b_data(value))))
                goto fail;
 
-       return early_hints;
+       free_trash_chunk(value);
+       b_set_data(&res->buf, b_size(&res->buf));
+       return 1;
 
   fail:
-       free_trash_chunk(early_hints);
-       return NULL;
-}
-
-/* Sends an HTTP 103 response. Before sending it, the last CRLF finishing the
- * response is added. If an error occurred or if another response was already
- * sent, this function does nothing.
- */
-static void htx_send_early_hints(struct stream *s, struct buffer *early_hints)
-{
-       struct channel *chn = s->txn->rsp.chn;
-       struct htx *htx;
-
-       /* If a response was already sent, skip early hints */
-       if (s->txn->status > 0)
-               return;
-
-       if (!chunk_memcat(early_hints, "\r\n", 2))
-               return;
-
-       htx = htx_from_buf(&chn->buf);
-       if (!htx_add_oob(htx, ist2(early_hints->area, early_hints->data)))
-               return;
-
-       c_adv(chn, early_hints->data);
-       chn->total += early_hints->data;
+       /* If an error occurred during an Early-hint rule, remove the incomplete
+        * HTTP 103 response from the buffer */
+       channel_truncate(res);
+       free_trash_chunk(value);
+       return -1;
 }
 
 /* This function executes one of the set-{method,path,query,uri} actions. It
@@ -2674,9 +2684,9 @@ static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list
        struct act_rule *rule;
        struct http_hdr_ctx ctx;
        const char *auth_realm;
-       struct buffer *early_hints = NULL;
        enum rule_result rule_ret = HTTP_RULE_RES_CONT;
        int act_flags = 0;
+       int early_hints = 0;
 
        htx = htx_from_buf(&s->req.buf);
 
@@ -2710,6 +2720,14 @@ static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list
 
                act_flags |= ACT_FLAG_FIRST;
   resume_execution:
+               if (early_hints && rule->action != ACT_HTTP_EARLY_HINT) {
+                       early_hints = 0;
+                       if (htx_reply_103_early_hints(&s->res) == -1) {
+                               rule_ret = HTTP_RULE_RES_BADREQ;
+                               goto end;
+                       }
+               }
+
                switch (rule->action) {
                        case ACT_ACTION_ALLOW:
                                rule_ret = HTTP_RULE_RES_STOP;
@@ -2729,12 +2747,6 @@ static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list
                                goto end;
 
                        case ACT_HTTP_REQ_AUTH:
-                               /* Be sure to sned any pending HTTP 103 response first */
-                               if (early_hints) {
-                                       htx_send_early_hints(s, early_hints);
-                                       free_trash_chunk(early_hints);
-                                       early_hints = NULL;
-                               }
                                /* Auth might be performed on regular http-req rules as well as on stats */
                                auth_realm = rule->arg.auth.realm;
                                if (!auth_realm) {
@@ -2755,12 +2767,6 @@ static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list
                                goto end;
 
                        case ACT_HTTP_REDIR:
-                               /* Be sure to sned any pending HTTP 103 response first */
-                               if (early_hints) {
-                                       htx_send_early_hints(s, early_hints);
-                                       free_trash_chunk(early_hints);
-                                       early_hints = NULL;
-                               }
                                rule_ret = HTTP_RULE_RES_DONE;
                                if (!htx_apply_redirect_rule(rule->arg.redir, s, txn))
                                        rule_ret = HTTP_RULE_RES_BADREQ;
@@ -2959,12 +2965,11 @@ static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list
                        case ACT_HTTP_EARLY_HINT:
                                if (!(txn->req.flags & HTTP_MSGF_VER_11))
                                        break;
-                               early_hints = htx_apply_early_hint_rule(s, early_hints,
-                                                                       rule->arg.early_hint.name,
-                                                                       rule->arg.early_hint.name_len,
+                               early_hints = htx_add_early_hint_header(s, early_hints,
+                                                                       ist2(rule->arg.early_hint.name, rule->arg.early_hint.name_len),
                                                                        &rule->arg.early_hint.fmt);
-                               if (!early_hints) {
-                                       rule_ret = HTTP_RULE_RES_DONE;
+                               if (early_hints == -1) {
+                                       rule_ret = HTTP_RULE_RES_BADREQ;
                                        goto end;
                                }
                                break;
@@ -3042,8 +3047,8 @@ static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list
 
   end:
        if (early_hints) {
-               htx_send_early_hints(s, early_hints);
-               free_trash_chunk(early_hints);
+               if (htx_reply_103_early_hints(&s->res) == -1)
+                       rule_ret = HTTP_RULE_RES_BADREQ;
        }
 
        /* we reached the end of the rules, nothing to report */