HTTP Version, Keep-alive support, No-copy POST
authorAndrew Canaday <andrew.canaday@nytimes.com>
Sun, 13 Jul 2014 05:07:36 +0000 (01:07 -0400)
committerAndy Green <andy.green@linaro.org>
Mon, 14 Jul 2014 12:19:43 +0000 (20:19 +0800)
This is a squashed commit from https://github.com/andrew-canaday/libwebsockets,
dev/http_keepalive branch (strategies changed a few times, so the commit
history is clutteread). This branch is submitted for clarity, but the other
can be used as a reference or alternative.

 * added **enum http_version** to track HTTP/1.0 vs HTTP/1.1 requests
 * added **enum http_connection_type** to track keep-alive vs close
 * replaced content_length_seen and body_index with **content_remain**
 * removed **post_buffer** (see handshake.c modifications)

 * removed post_buffer free

 * switch state to WSI_TOKEN_SKIPPING after URI is complete to store version
 * delete *spill* label (unused)

 * add vars to track HTTP version and connection type
 * HTTP version defaults to 1.0
 * connection type defaults to 'close' for 1.0, keep-alive for 1.1
 * additional checks in **cleanup:** label:
   * if HTTP version string is present and valid, set enum val appropriately
   * override connection default with the "Connection:" header, if present
 * set state to WSI_STATE_HTTP_BODY if content_length > 0
 * return 0 on HTTP requests, unless LWS_CALLBACK_HTTP indicates otherwise

 * add vars to track remaining content_length and body chunk size
 * re-arrange switch case order to facilitate creation of jump-table
 * added new labels:
   * **read_ok**: normal location reach on break from switch; just return 0
   * **http_complete**: check for keep-alive + init state, mode, hdr table
   * **http_new**: jump location for keep-alive when http_complete sees len>0
 * after libwebsocket_parse, jump to one of those labels based on state
 * POST body handling:
   * don't bother iterating over input byte-by-byte or using memcpy
   * just pass the relevant portion of the context->service_buffer to callback

lib/handshake.c
lib/libwebsockets.c
lib/parsers.c
lib/private-libwebsockets.h
lib/server.c

index dffd2d2..4d794c8 100644 (file)
  *       Sec-WebSocket-Protocol: chat
  */
 
+#ifndef min
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
 /*
  * We have to take care about parsing because the headers may be split
  * into multiple fragments.  They may contain unknown headers with arbitrary
@@ -58,97 +62,86 @@ libwebsocket_read(struct libwebsocket_context *context,
                     struct libwebsocket *wsi, unsigned char *buf, size_t len)
 {
        size_t n;
+       int body_chunk_len;
+       int content_remain = 0;
+       unsigned char *last_char;
 
        switch (wsi->state) {
-
-       case WSI_STATE_HTTP_BODY:
-http_postbody:
-               while (len--) {
-
-                       if (wsi->u.http.content_length_seen >= wsi->u.http.content_length)
-                               break;
-
-                       wsi->u.http.post_buffer[wsi->u.http.body_index++] = *buf++;
-                       wsi->u.http.content_length_seen++;
-                       n = wsi->protocol->rx_buffer_size;
-                       if (!n)
-                               n = LWS_MAX_SOCKET_IO_BUF;
-
-                       if (wsi->u.http.body_index != n &&
-                           wsi->u.http.content_length_seen != wsi->u.http.content_length)
-                               continue;
-
-                       if (wsi->protocol->callback) {
-                               n = wsi->protocol->callback(
-                                       wsi->protocol->owning_server, wsi,
-                                           LWS_CALLBACK_HTTP_BODY,
-                                           wsi->user_space, wsi->u.http.post_buffer,
-                                                       wsi->u.http.body_index);
-                               wsi->u.http.body_index = 0;
-                               if (n)
-                                       goto bail;
-                       }
-
-                       if (wsi->u.http.content_length_seen == wsi->u.http.content_length) {
-                               /* he sent the content in time */
-                               libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-                               n = wsi->protocol->callback(
-                                       wsi->protocol->owning_server, wsi,
-                                           LWS_CALLBACK_HTTP_BODY_COMPLETION,
-                                           wsi->user_space, NULL, 0);
-                               wsi->u.http.body_index = 0;
-                               if (n)
-                                       goto bail;
-                       }
-
-               }
-
-               /* 
-                * we need to spill here so everything is seen in the case
-                * there is no content-length
-                */
-               if (wsi->u.http.body_index && wsi->protocol->callback) {
-                       n = wsi->protocol->callback(
-                               wsi->protocol->owning_server, wsi,
-                                   LWS_CALLBACK_HTTP_BODY,
-                                   wsi->user_space, wsi->u.http.post_buffer,
-                                               wsi->u.http.body_index);
-                       wsi->u.http.body_index = 0;
-                       if (n)
-                               goto bail;
-               }
-               break;
-
-       case WSI_STATE_HTTP_ISSUING_FILE:
+http_new:
        case WSI_STATE_HTTP:
+       case WSI_STATE_HTTP_ISSUING_FILE:
                wsi->state = WSI_STATE_HTTP_HEADERS;
                wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
                wsi->u.hdr.lextable_pos = 0;
                /* fallthru */
        case WSI_STATE_HTTP_HEADERS:
-
                lwsl_parser("issuing %d bytes to parser\n", (int)len);
 
                if (lws_handshake_client(wsi, &buf, len))
                        goto bail;
 
+               last_char = buf;
+               if (lws_handshake_server(context, wsi, &buf, len))
+                       /* Handshake indicates this session is done. */
+                       goto bail;
+
                /* It's possible that we've exhausted our data already, but
                 * lws_handshake_server doesn't update len for us. Figure out how
                 * much was read, so that we can proceed appropriately: */
-               {
-                       unsigned char *last_char = buf;
-                       switch (lws_handshake_server(context, wsi, &buf, len)) {
-                       case 1:
-                               goto bail;
-                       case 2:
-                               len -= (buf - last_char);
+               len -= (buf - last_char);
+               switch (wsi->state) {
+                       case WSI_STATE_HTTP:
+                       case WSI_STATE_HTTP_HEADERS:
+                               goto http_complete;
+                       case WSI_STATE_HTTP_ISSUING_FILE:
+                               goto read_ok;
+                       case WSI_STATE_HTTP_BODY:
+                               wsi->u.http.content_remain = wsi->u.http.content_length;
                                goto http_postbody;
+                       default:
+                               break;
+               }
+               break;
+
+       case WSI_STATE_HTTP_BODY:
+http_postbody:
+               while (len && wsi->u.http.content_remain) {
+                       /* Copy as much as possible, up to the limit of:
+                        * what we have in the read buffer (len)
+                        * remaining portion of the POST body (content_remain)
+                        */
+                       body_chunk_len = min(wsi->u.http.content_remain,len);
+                       wsi->u.http.content_remain -= body_chunk_len;
+                       len -= body_chunk_len;
+
+                       if (wsi->protocol->callback) {
+                               n = wsi->protocol->callback(
+                                       wsi->protocol->owning_server, wsi,
+                                       LWS_CALLBACK_HTTP_BODY, wsi->user_space,
+                                       buf, body_chunk_len);
+                               if (n)
+                                       goto bail;
+                       }
+                       buf += body_chunk_len;
+
+                       if (!wsi->u.http.content_remain)  {
+                               /* he sent the content in time */
+                               libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+                               if (wsi->protocol->callback) {
+                                       n = wsi->protocol->callback(
+                                               wsi->protocol->owning_server, wsi,
+                                               LWS_CALLBACK_HTTP_BODY_COMPLETION,
+                                               wsi->user_space, NULL, 0);
+                                       if (n)
+                                               goto bail;
+                               }
+                               goto http_complete;
                        }
                }
                break;
 
-       case WSI_STATE_AWAITING_CLOSE_ACK:
        case WSI_STATE_ESTABLISHED:
+       case WSI_STATE_AWAITING_CLOSE_ACK:
                if (lws_handshake_client(wsi, &buf, len))
                        goto bail;
                switch (wsi->mode) {
@@ -166,8 +159,30 @@ http_postbody:
                break;
        }
 
+read_ok:
+       /* Nothing more to do for now. */
+       lwsl_debug("libwebsocket_read: read_ok\n");
+
        return 0;
 
+http_complete:
+       lwsl_debug("libwebsocket_read: http_complete\n");
+       /* Handle keep-alives, by preparing for a new request: */
+       if (wsi->u.http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) {
+               wsi->state = WSI_STATE_HTTP;
+               wsi->mode = LWS_CONNMODE_HTTP_SERVING;
+               lwsl_debug("libwebsocket_read: keep-alive\n");
+
+               if (lws_allocate_header_table(wsi))
+                       goto bail;
+
+               /* If we have more data, loop back around: */
+               if (len)
+                       goto http_new;
+
+               return 0;
+       }
+
 bail:
        lwsl_debug("closing connection at libwebsocket_read bail:\n");
 
index 6bebc60..ee88e2e 100644 (file)
@@ -90,10 +90,6 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context,
        }
 
        if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED) {
-               if (wsi->u.http.post_buffer) {
-                       free(wsi->u.http.post_buffer);
-                       wsi->u.http.post_buffer = NULL;
-               }
                if (wsi->u.http.fd != LWS_INVALID_FILE) {
                        lwsl_debug("closing http file\n");
                        compatible_file_close(wsi->u.http.fd);
index a8d0a5e..9626cd2 100644 (file)
@@ -248,9 +248,12 @@ int libwebsocket_parse(
                        if (!wsi->u.hdr.ah->frags[wsi->u.hdr.ah->next_frag_index].len)
                                if (issue_char(wsi, '/') < 0)
                                        return -1;
-                       c = '\0';
-                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING;
-                       goto spill;
+
+                       /* begin parsing HTTP version: */
+                       if (issue_char(wsi, '\0') < 0)
+                               return -1;
+                       wsi->u.hdr.parser_state = WSI_TOKEN_HTTP;
+                       goto start_fragment;
                }
 
                /* special URI processing... convert %xx */
@@ -394,7 +397,6 @@ check_eol:
                        goto swallow;
                }
 
-spill:
                {
                        int issue_result = issue_char(wsi, c);
                        if (issue_result < 0) {
index 6fae889..4194498 100755 (executable)
@@ -291,6 +291,16 @@ enum lws_connection_states {
        WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE,
 };
 
+enum http_version {
+       HTTP_VERSION_1_0,
+       HTTP_VERSION_1_1,
+};
+
+enum http_connection_type {
+       HTTP_CONNECTION_CLOSE,
+       HTTP_CONNECTION_KEEP_ALIVE
+};
+
 enum lws_rx_parse_state {
        LWS_RXPS_NEW,
 
@@ -520,10 +530,10 @@ struct _lws_http_mode_related {
        unsigned long filepos;
        unsigned long filelen;
 
+       enum http_version request_version;
+       enum http_connection_type connection_type;
        int content_length;
-       int content_length_seen;
-       int body_index;
-       unsigned char *post_buffer;
+       int content_remain;
 };
 
 struct _lws_header_related {
index 6afd570..9ffb18c 100644 (file)
@@ -173,6 +173,9 @@ int lws_handshake_server(struct libwebsocket_context *context,
        struct allocated_headers *ah;
        char *uri_ptr = NULL;
        int uri_len = 0;
+       enum http_version request_version;
+       enum http_connection_type connection_type;
+       int http_version_len;
        char content_length_str[32];
        int n;
 
@@ -265,18 +268,44 @@ int lws_handshake_server(struct libwebsocket_context *context,
                                wsi->u.http.content_length = atoi(content_length_str);
                        }
 
-                       if (wsi->u.http.content_length > 0) {
-                               wsi->u.http.body_index = 0;
-                               n = wsi->protocol->rx_buffer_size;
-                               if (!n)
-                                       n = LWS_MAX_SOCKET_IO_BUF;
-                               wsi->u.http.post_buffer = malloc(n);
-                               if (!wsi->u.http.post_buffer) {
-                                       lwsl_err("Unable to allocate post buffer\n");
-                                       n = -1;
-                                       goto cleanup;
+                       /* http_version? Default to 1.0, override with token: */
+                       request_version = HTTP_VERSION_1_0;
+
+                       /* Works for single digit HTTP versions. : */
+                       http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP);
+                       if (http_version_len > 7) {
+                               char http_version_str[10];
+                               lws_hdr_copy(wsi, http_version_str,
+                                               sizeof(http_version_str) - 1, WSI_TOKEN_HTTP);
+                               if (http_version_str[5] == '1' &&
+                                       http_version_str[7] == '1') {
+                                       request_version = HTTP_VERSION_1_1;
                                }
                        }
+                       wsi->u.http.request_version = request_version;
+
+                       /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */
+                       if (request_version == HTTP_VERSION_1_1)
+                               connection_type = HTTP_CONNECTION_KEEP_ALIVE;
+
+                       else
+                               connection_type = HTTP_CONNECTION_CLOSE;
+
+
+                       /* Override default if http "Connection:" header: */
+                       if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) {
+                               char http_conn_str[20];
+                               lws_hdr_copy(wsi, http_conn_str,
+                                               sizeof(http_conn_str)-1, WSI_TOKEN_CONNECTION);
+                               http_conn_str[sizeof(http_conn_str)-1] = '\0';
+                               if (strcasecmp(http_conn_str,"keep-alive") == 0)
+                                       connection_type = HTTP_CONNECTION_KEEP_ALIVE;
+
+                               else if (strcasecmp(http_conn_str,"close") == 0)
+                                       connection_type = HTTP_CONNECTION_CLOSE;
+
+                       }
+                       wsi->u.http.connection_type = connection_type;
 
                        n = 0;
                        if (wsi->protocol->callback)
@@ -311,15 +340,19 @@ cleanup:
                                return 1; /* struct ah ptr already nuked */
                        }
 
-                       /*
-                        * (if callback didn't start sending a file)
-                        * deal with anything else as body, whether
-                        * there was a content-length or not
-                        */
-
+                       /* If we're not issuing a file, check for content_length or
+                        * HTTP keep-alive. No keep-alive header allocation for
+                        * ISSUING_FILE, as this uses HTTP/1.0. 
+                        * In any case, return 0 and let libwebsocket_read decide how to
+                        * proceed based on state. */
                        if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE)
-                               wsi->state = WSI_STATE_HTTP_BODY;
-                       return 2; /* goto http_postbody; */
+
+                               /* Prepare to read body if we have a content length: */
+                               if (wsi->u.http.content_length > 0)
+                                       wsi->state = WSI_STATE_HTTP_BODY;
+
+
+                       return 0; /* don't bail out of libwebsocket_read, just yet */
                }
 
                if (!wsi->protocol)
@@ -550,7 +583,6 @@ int lws_server_socket_service(struct libwebsocket_context *context,
                        if (wsi->state != WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE) {
                        
                                /* hm this may want to send (via HTTP callback for example) */
-
                                n = libwebsocket_read(context, wsi,
                                                        context->service_buffer, len);
                                if (n < 0)