]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon/http: refactor code style
authorTomas Krizek <tomas.krizek@nic.cz>
Mon, 17 Aug 2020 14:22:06 +0000 (16:22 +0200)
committerTomas Krizek <tomas.krizek@nic.cz>
Tue, 13 Oct 2020 10:55:24 +0000 (12:55 +0200)
daemon/http.c

index 8c6748615253a99d86c877193c8ae6e2a952a8c5..0837b867a8d5534ba6329007650f58c17a5c154f 100644 (file)
@@ -39,6 +39,9 @@ struct http_data_buffer {
        size_t pos;
 };
 
+/*
+ * Write HTTP/2 data to underlying transport layer.
+ */
 static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length,
                             int flags, void *user_data)
 {
@@ -98,9 +101,8 @@ static int header_callback(nghttp2_session *session, const nghttp2_frame *frame,
 
        /* If the HEADERS don't have END_STREAM set, there are some DATA frames,
         * which implies POST method.  Skip header processing for POST. */
-       if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+       if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0)
                return 0;
-       }
 
        /* If there is incomplete data in the buffer, we can't process the new stream. */
        if (ctx->incomplete_stream) {
@@ -116,55 +118,66 @@ static int header_callback(nghttp2_session *session, const nghttp2_frame *frame,
        return 0;
 }
 
-/* This method is called for data received via POST. */
-static int data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data)
+/*
+ * Process DATA chunk sent by the client (by POST method).
+ *
+ * We use a single DNS message buffer for the entire connection. Therefore, we
+ * don't support interweaving DATA chunks from different streams. To successfully
+ * parse multiple subsequent streams, each one must be fully received before
+ * processing a new stream.
+ */
+static int data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id,
+                                   const uint8_t *data, size_t len, void *user_data)
 {
        struct http_ctx *ctx = (struct http_ctx *)user_data;
+       ssize_t remaining;
+       ssize_t required;
 
        if (ctx->incomplete_stream) {
                if (queue_len(ctx->streams) <= 0) {
                        return NGHTTP2_ERR_CALLBACK_FAILURE;
                } else if (queue_tail(ctx->streams) != stream_id) {
-                       /* If the received DATA chunk is from a new stream and the previous
-                        * one still has unfinished DATA, refuse the new stream. */
-                       kr_log_verbose("[http] refusing http DATA chunk, other stream has incomplete DATA\n");
-                       nghttp2_submit_rst_stream(
-                               session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_REFUSED_STREAM);
+                       kr_log_verbose("[http] previous stream incomplete, refusing\n");
+                       refuse_stream(session, stream_id);
                        return 0;
                }
        }
 
-       /* Check message and its length can still fit into the wire buffer. */
-       ssize_t remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
-       ssize_t required = len + sizeof(uint16_t);
+       remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
+       required = len + sizeof(uint16_t);
        if (required > remaining) {
-               kr_log_error(
-                       "[http] insufficient space in buffer: remaining %zd B, required %zd B\n",
-                       remaining, required);
+               kr_log_error("[http] insufficient space in buffer\n");
                return NGHTTP2_ERR_CALLBACK_FAILURE;
        }
 
        if (!ctx->incomplete_stream) {
                ctx->incomplete_stream = true;
+               ctx->buf_pos = sizeof(uint16_t);  /* Reserve 2B for dnsmsg len. */
                queue_push(ctx->streams, stream_id);
-
-               /* 2B at the start of buffer is reserved for message length. */
-               ctx->buf_pos = sizeof(uint16_t);
        }
+
        memcpy(ctx->buf + ctx->buf_pos, data, len);
        ctx->buf_pos += len;
-
        return 0;
 }
 
+/*
+ * Finalize existing buffer upon receiving the last frame in the stream.
+ *
+ * For GET, this would be HEADERS frame.
+ * For POST, it is a DATA frame.
+ *
+ * Unrelated frames (such as SETTINGS) are ignored (no data was buffered).
+ */
 static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
 {
        struct http_ctx *ctx = (struct http_ctx *)user_data;
+       ssize_t len;
 
        if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->buf_pos != 0) {
                ctx->incomplete_stream = false;
 
-               ssize_t len = ctx->buf_pos - sizeof(uint16_t);
+               len = ctx->buf_pos - sizeof(uint16_t);
                if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) {
                        kr_log_verbose("[http] invalid dnsmsg size: %zd B\n", len);
                        return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -179,18 +192,28 @@ static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame
        return 0;
 }
 
+/*
+ * Setup and initialize connection with new HTTP/2 context.
+ */
 struct http_ctx* http_new(http_send_callback cb, void *user_ctx)
 {
        assert(cb != NULL);
 
        nghttp2_session_callbacks *callbacks;
+       struct http_ctx *ctx;
+       static const nghttp2_settings_entry iv[] = {
+               { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
+       };
+
        nghttp2_session_callbacks_new(&callbacks);
        nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
        nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
-       nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, data_chunk_recv_callback);
-       nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
+       nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+               callbacks, data_chunk_recv_callback);
+       nghttp2_session_callbacks_set_on_frame_recv_callback(
+               callbacks, on_frame_recv_callback);
 
-       struct http_ctx *ctx = calloc(1UL, sizeof(struct http_ctx));
+       ctx = calloc(1UL, sizeof(struct http_ctx));
        ctx->send_cb = cb;
        ctx->user_ctx = user_ctx;
        queue_init(ctx->streams);
@@ -200,42 +223,53 @@ struct http_ctx* http_new(http_send_callback cb, void *user_ctx)
        nghttp2_session_server_new(&ctx->session, callbacks, ctx);
        nghttp2_session_callbacks_del(callbacks);
 
-       static const nghttp2_settings_entry iv[] = {
-               { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
-       };
-
-       nghttp2_submit_settings(ctx->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv)/sizeof(*iv) );
+       nghttp2_submit_settings(ctx->session, NGHTTP2_FLAG_NONE,
+               iv, sizeof(iv)/sizeof(*iv));
 
        return ctx;
 }
 
+/*
+ * Process inbound HTTP/2 data and return number of bytes read into session wire buffer.
+ *
+ * This function may trigger outgoing HTTP/2 data, such as stream resets, window updates etc.
+ */
 ssize_t http_process_input_data(struct session *s, const uint8_t *in_buf, ssize_t in_buf_len)
 {
-       struct http_ctx *http_p = session_http_get_server_ctx(s);
-       if (!http_p->session) {
+       struct http_ctx *ctx = session_http_get_server_ctx(s);
+       ssize_t ret = 0;
+
+       if (!ctx->session)  // TODO session vs h2; assert session equals
                return kr_error(ENOSYS);
-       }
 
-       http_p->submitted = 0;
-       http_p->buf = session_wirebuf_get_free_start(s);
-       http_p->buf_pos = 0;
-       http_p->buf_size = session_wirebuf_get_free_size(s);
+       ctx->submitted = 0;
+       ctx->buf = session_wirebuf_get_free_start(s);
+       ctx->buf_pos = 0;
+       ctx->buf_size = session_wirebuf_get_free_size(s);
 
-       ssize_t ret = 0;
-       if ((ret = nghttp2_session_mem_recv(http_p->session, in_buf, in_buf_len)) < 0) {
-               kr_log_error("[http] nghttp2_session_mem_recv failed: %s (%zd)\n", nghttp2_strerror(ret), ret);
+       ret = nghttp2_session_mem_recv(ctx->session, in_buf, in_buf_len);
+       if (ret < 0) {
+               kr_log_error("[http] nghttp2_session_mem_recv failed: %s (%zd)\n",
+                            nghttp2_strerror(ret), ret);
                return kr_error(EIO);
        }
 
-       if ((ret = nghttp2_session_send(http_p->session)) < 0) {
-               kr_log_error("[http] nghttp2_session_send failed: %s (%zd)\n", nghttp2_strerror(ret), ret);
+       ret = nghttp2_session_send(ctx->session);
+       if (ret < 0) {
+               kr_log_error("[http] nghttp2_session_send failed: %s (%zd)\n",
+                            nghttp2_strerror(ret), ret);
                return kr_error(EIO);
        }
 
-       return http_p->submitted;
+       return ctx->submitted;
 }
 
-static ssize_t send_response_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
+/*
+ *
+ */
+static ssize_t send_response_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf,
+                                     size_t length, uint32_t *data_flags,
+                                     nghttp2_data_source *source, void *user_data)
 {
        struct http_data_buffer *buffer = (struct http_data_buffer *)source->ptr;
        assert(buffer != NULL);
@@ -309,12 +343,15 @@ int http_write(uv_write_t *req, uv_handle_t *handle, int32_t stream_id, knot_pkt
        return kr_ok();
 }
 
+/*
+ * Release HTTP/2 context.
+ */
 void http_free(struct http_ctx *ctx)
 {
-       if (ctx == NULL || ctx->session == NULL) {
+       if (!ctx)
                return;
-       }
+
        queue_deinit(ctx->streams);
        nghttp2_session_del(ctx->session);
-       ctx->session = NULL;
+       free(ctx);
 }