[vlc-devel] [PATCH] [WIP][RFC] network: implement an API for HTTP requests.

Felix Abecassis felix.abecassis at gmail.com
Thu Jan 30 19:37:25 CET 2014


We would like to provide a simple API for sending HTTP requests and
for parsing the response from the server. Many modules are currently
handcrafting GET/POST requests by manipulating strings.

Features to implement:
- Parse and store headers from the HTTP response.
- Integrate net_ConnectTCP and net_Close inside net_HTTP since the
  server will close the connection anyway.
- (?) Automatically parse some headers content from the response
  (e.g. the date).

Comments are welcome concerning the API and the features that should
be implemented.
---
 include/vlc_network.h |  65 ++++++++++++++++++
 src/libvlccore.sym    |   1 +
 src/network/io.c      | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 253 insertions(+)

diff --git a/include/vlc_network.h b/include/vlc_network.h
index d1c63b1..2190dfb 100644
--- a/include/vlc_network.h
+++ b/include/vlc_network.h
@@ -156,6 +156,71 @@ VLC_API ssize_t net_Printf( vlc_object_t *p_this, int fd, const v_socket_t *, co
 VLC_API ssize_t net_vaPrintf( vlc_object_t *p_this, int fd, const v_socket_t *, const char *psz_fmt, va_list args );
 #define net_vaPrintf(a,b,c,d,e) net_vaPrintf(VLC_OBJECT(a),b,c,d,e)
 
+typedef enum
+{
+    VLC_HTTP_OPTIONS,
+    VLC_HTTP_GET,
+    VLC_HTTP_HEAD,
+    VLC_HTTP_POST,
+    VLC_HTTP_PUT,
+    VLC_HTTP_DELETE,
+    VLC_HTTP_TRACE,
+    VLC_HTTP_CONNECT,
+    VLC_HTTP_PATCH
+} vlc_http_method_t;
+
+typedef struct
+{
+    vlc_http_method_t method;
+    const char*       uri;
+    int               http_version;
+} vlc_http_request_line_t;
+
+typedef struct
+{
+    const char* key;
+    const char* value;
+} vlc_http_header_pair_t;
+
+/* Headers are stored as a dictionary.
+   For instance 'User-agent: foo' is stored as { "User-agent", "foo" }
+*/
+typedef struct
+{
+    vlc_http_header_pair_t *dictionary;
+} vlc_http_header_t;
+
+typedef struct
+{
+    void*  data;
+    size_t size;
+} vlc_http_body_t;
+
+typedef struct
+{
+    vlc_http_request_line_t *request_line;
+    vlc_http_header_t       *header;
+    vlc_http_body_t         *body;
+} vlc_http_request_t;
+
+typedef struct
+{
+    int http_version;
+    int status_code;
+    /* Reason-Phrase not saved. */
+} vlc_http_status_line_t;
+
+typedef struct
+{
+    vlc_http_status_line_t  *status_line;
+    vlc_http_header_t       *header;
+    vlc_http_body_t         *body;
+} vlc_http_response_t;
+
+VLC_API ssize_t net_HTTP( vlc_object_t *p_this, int fd,
+                          vlc_http_request_t *request, vlc_http_response_t *response );
+#define net_HTTP(a,b,c,d) net_HTTP(VLC_OBJECT(a),b,c,d)
+
 #ifdef _WIN32
 /* Microsoft: same semantic, same value, different name... go figure */
 # define SHUT_RD SD_RECEIVE
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 3fc39d7..8265990 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -268,6 +268,7 @@ net_AcceptSingle
 net_Connect
 net_ConnectDgram
 net_Gets
+net_HTTP
 net_Listen
 net_ListenClose
 net_OpenDgram
diff --git a/src/network/io.c b/src/network/io.c
index d8832e2..b4d126d 100644
--- a/src/network/io.c
+++ b/src/network/io.c
@@ -533,3 +533,190 @@ ssize_t net_vaPrintf( vlc_object_t *p_this, int fd, const v_socket_t *p_vs,
 
     return i_ret;
 }
+
+/* Read a line (ended by \r\n) from an HTTP response. */
+static char* http_readline( vlc_object_t *p_this, int fd )
+{
+    size_t size = 64;
+    char *str = malloc(sizeof(*str) * size);
+
+    char c;
+    size_t i = 0;
+    while (net_Read(p_this, fd, NULL, &c, 1, false) > 0)
+    {
+        if (i == size - 1)
+        {
+            size *= 2;
+            str = realloc(str, sizeof(*str) * size);
+        }
+        str[i++] = c;
+        if (c == '\n')
+            break;
+    }
+
+    if (i == 0)
+    {
+        free(str);
+        return NULL;
+    }
+
+    str[i] = '\0';
+    return str;
+}
+
+static void http_parse_response( vlc_object_t *p_this, int fd, vlc_http_response_t *response )
+{
+    if (!response->status_line)
+        response->status_line = calloc(1, sizeof(*response->status_line));
+    if (!response->body)
+        response->body = calloc(1, sizeof(*response->body));
+    response->header = NULL; /* Headers not filled for the moment. */
+
+    vlc_http_status_line_t *status_line = response->status_line;
+    vlc_http_body_t *body = response->body;
+    if (!status_line || !body)
+    {
+        free(status_line);
+        free(body);
+        return;
+    }
+
+    /* Parse status line. */
+    char *line = http_readline(p_this, fd);
+    int ret = sscanf(line, "HTTP/1.%d %3d", &status_line->http_version, &status_line->status_code);
+    free(line);
+    if (ret != 2)
+    {
+        msg_Err(p_this, "Invalid HTTP status line.");
+        return;
+    }
+
+    /* Parse headers. */
+    size_t content_length = 0;
+    bool end_header = false;
+    while (!end_header && (line = http_readline(p_this, fd)))
+    {
+        /* Line with only \r\n: end of headers. */
+        if (!strncmp(line, "\r\n", 2))
+            end_header = true;
+        else if (!strncasecmp(line, "Content-Length", 14))
+        {
+            /* Get the Content-Length value. */
+            errno = 0;
+            content_length = strtoul(line + 15, NULL, 10);
+            if (errno)
+            {
+                content_length = 0;
+                end_header = true;
+            }
+        }
+        free(line);
+    }
+
+    body->size = content_length;
+    /* Read the body of the response. */
+    if (body->size > 0)
+    {
+        /* Adding a terminal \0 for debugging convenience. */
+        body->data = malloc(body->size + 1);
+        if (!body->data)
+            goto body_error;
+
+        size_t size = net_Read(p_this, fd, NULL, body->data, body->size, true);
+        if (size < content_length)
+            goto body_error;
+
+        char* end = (char*)body->data + body->size;
+        *end = '\0';
+    }
+    return;
+
+body_error:
+    body->size = 0;
+    free(body->data);
+    body->data = NULL;
+}
+
+typedef struct http_method_s
+{
+    vlc_http_method_t id;
+    const char        *string;
+} http_method_t;
+
+static const struct http_method_s http_methods[] =
+{
+    { VLC_HTTP_OPTIONS, "OPTIONS" },
+    { VLC_HTTP_GET, "GET" },
+    { VLC_HTTP_HEAD, "HEAD" },
+    { VLC_HTTP_POST, "POST" },
+    { VLC_HTTP_PUT, "PUT" },
+    { VLC_HTTP_DELETE, "DELETE" },
+    { VLC_HTTP_TRACE, "TRACE" },
+    { VLC_HTTP_CONNECT, "CONNECT" },
+    { VLC_HTTP_PATCH, "PATCH" },
+};
+#define HTTP_METHODS_COUNT (sizeof(http_methods)/sizeof(http_methods[0]))
+
+#undef net_HTTP
+VLC_API ssize_t net_HTTP( vlc_object_t *p_this, int fd,
+                          vlc_http_request_t *request, vlc_http_response_t *response )
+{
+    vlc_http_request_line_t *request_line = request->request_line;
+    vlc_http_header_t *request_header = request->header;
+    vlc_http_body_t *request_body = request->body;
+
+    /* Lookup the string corresponding to the method. */
+    const char* method_string = NULL;
+    for (unsigned i = 0; i < HTTP_METHODS_COUNT; ++i)
+    {
+        if (http_methods[i].id == request_line->method)
+        {
+            method_string = http_methods[i].string;
+            break;
+        }
+    }
+    if (!method_string)
+        return -1;
+
+    /* Create the header string. */
+    int ret;
+    char *header_string = strdup("");
+    for (int i = 0; request_header->dictionary[i].key; ++i)
+    {
+        const char *key = request_header->dictionary[i].key;
+        const char *value = request_header->dictionary[i].value;
+        /* Discard unauthorized headers */
+        if (!strncasecmp(key, "Content-Length", 14)
+            || !strncasecmp(key, "Connection", 10))
+        {
+            msg_Err(p_this, "Unauthorized HTTP header: %s %s", key, value);
+            continue;
+        }
+        char* prev = header_string;
+        /* Concatenate new key-value entry to the string. */
+        ret = asprintf(&header_string, "%s%s: %s\r\n", header_string, key, value);
+        free(prev);
+        if (ret < 0)
+            return -1;
+    }
+
+    /* Send request line and header. */
+    ret = net_Printf(p_this, fd, NULL,
+                     "%s %s HTTP/1.%d\r\n"
+                     "%s"
+                     "Connection: close\r\n"
+                     "Content-Length: %zu\r\n"
+                     "\r\n",
+                     method_string, request_line->uri, request_line->http_version,
+                     header_string, request_body->size);
+    if (ret == -1)
+        return -1;
+    /* Send body. */
+    ret = net_Write(p_this, fd, NULL, request_body->data, request_body->size);
+    if (ret == -1)
+        return -1;
+
+    /* Parse the response sent by the server. */
+    http_parse_response(p_this, fd, response);
+    return 0;
+}
-- 
1.8.3.2




More information about the vlc-devel mailing list