Browse Source

Add Chunked encoding support

Add support for chunked encoding to Civetweb, to enable streaming
http.
Keith Kyzivat 10 năm trước cách đây
mục cha
commit
104db368a5
1 tập tin đã thay đổi với 70 bổ sung2 xóa
  1. 70 2
      src/civetweb.c

+ 70 - 2
src/civetweb.c

@@ -883,6 +883,7 @@ struct mg_connection {
     int64_t num_bytes_sent;         /* Total bytes sent to client */
     int64_t content_len;            /* Content-Length header value */
     int64_t consumed_content;       /* How many bytes of content have been read */
+    int is_chunked;                 /* Transfer-encoding is chunked */
     char *buf;                      /* Buffer for received data */
     char *path_info;                /* PATH_INFO part of the URL */
     int must_close;                 /* 1 if connection must be closed */
@@ -2586,7 +2587,7 @@ static void discard_unread_request_data(struct mg_connection *conn)
     }
 }
 
-int mg_read(struct mg_connection *conn, void *buf, size_t len)
+int mg_read_inner(struct mg_connection *conn, void *buf, size_t len)
 {
     int64_t n, buffered_len, nread;
     int64_t len64 = (int64_t)(len > INT_MAX ? INT_MAX : len); /* since the return value is int, we may not read more bytes */
@@ -2632,6 +2633,69 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len)
     return (int)nread;
 }
 
+static int mg_getc(struct mg_connection *conn)
+{
+  char c;
+  conn->content_len++;
+  if (mg_read_inner(conn, &c, 1) <= 0)
+  {
+      return EOF;
+  }
+  return (int)c;
+}
+
+int mg_read(struct mg_connection *conn, void *buf, size_t len)
+{
+    if (conn->is_chunked)
+    {
+        if (conn->content_len <= 0)
+        {
+            conn->content_len = 0;
+        }
+        if (conn->consumed_content < conn->content_len)
+        {
+            return mg_read_inner(conn, buf,len);
+        }
+
+        int i = 0;
+        char buf[64];
+        while (1)
+        {
+            int c = mg_getc(conn);
+            if (!(c == '\n' || c == '\r'))
+            {
+                buf[i++] = c;
+                break;
+            }
+        }
+        for (; i < (int)sizeof(buf); i++)
+        {
+            int c = mg_getc(conn);
+            if ( c == EOF )
+            {
+                return -1;
+            }
+            buf[i] = (char) c;
+            if (buf[i] == '\n' && buf[i-1] == '\r')
+            {
+                break;
+            }
+        }
+        char *end = 0;
+        long chunkSize = strtol(buf,&end,16);
+        if (end != buf+(i-1))
+        {
+            return -1;
+        }
+        if (chunkSize == 0)
+        {
+            return 0;
+        }
+        conn->content_len += chunkSize;
+    }
+    return mg_read_inner(conn,buf,len);
+}
+
 int mg_write(struct mg_connection *conn, const void *buf, size_t len)
 {
     time_t now;
@@ -4491,7 +4555,7 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
     expect = mg_get_header(conn, "Expect");
     assert(fp != NULL);
 
-    if (conn->content_len == -1) {
+    if (conn->content_len == -1 && !conn->is_chunked) {
         /* Content length is not specified by the client. */
         send_http_error(conn, 411, "%s",
             "Error: Client did not specify content length");
@@ -7074,6 +7138,7 @@ static void reset_per_request_attributes(struct mg_connection *conn)
     conn->path_info = NULL;
     conn->num_bytes_sent = conn->consumed_content = 0;
     conn->status_code = -1;
+    conn->is_chunked = 0;
     conn->must_close = conn->request_len = conn->throttle = 0;
     conn->request_info.content_length = -1;
     conn->data_len = 0;
@@ -7313,10 +7378,13 @@ static int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *
             }
             /* Publish the content length back to the request info. */
             conn->request_info.content_length = conn->content_len;
+        } else if (( cl = get_header(&conn->request_info, "Transfer-encoding")) != NULL && strcmp(cl,"chunked") == 0) {
+            conn->is_chunked = 1;
         } else if (!mg_strcasecmp(conn->request_info.request_method, "POST") ||
                    !mg_strcasecmp(conn->request_info.request_method, "PUT")) {
             /* POST or PUT request without content length set */
             conn->content_len = -1;
+            conn->content_len = 0;
         } else if (!mg_strncasecmp(conn->request_info.request_method, "HTTP/", 5)) {
             /* Response without content length set */
             conn->content_len = -1;