[vlc-devel] [PATCH] network: implement an API for HTTP requests.
Felix Abecassis
felix.abecassis at gmail.com
Mon Feb 3 19:07:48 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.
Comments are welcome concerning the API and the features that should
be implemented.
Difference from last draft:
- Now correctly split headers.
- Now automatically use Expect: 100-continue for large request bodies
if HTTP version is 1.1.
- Moved declaration to vlc_http.h and definition to a new file.
To discuss:
- HTTP 2.0: no official standard yet. What should we do?
- For courmisch: you've mentioned special FD polling in LUA. Could you give me more details?
---
include/vlc_http.h | 68 ++++++++++
modules/misc/Modules.am | 4 +
src/Makefile.am | 1 +
src/libvlccore.sym | 2 +
src/network/http.c | 349 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 424 insertions(+)
create mode 100644 src/network/http.c
diff --git a/include/vlc_http.h b/include/vlc_http.h
index ddde13e..a64e61f 100644
--- a/include/vlc_http.h
+++ b/include/vlc_http.h
@@ -64,4 +64,72 @@ VLC_API char *http_auth_FormatAuthorizationHeader
const char *, const char *,
const char *, const char * ) VLC_USED;
+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;
+ size_t size;
+} 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 void http_response_Delete(vlc_http_response_t *response);
+
+VLC_API vlc_http_response_t *http_request_Send(vlc_object_t *p_this, vlc_url_t *url,
+ vlc_http_request_t *request);
+#define http_request_Send(a,b,c) http_request_Send(VLC_OBJECT(a),b,c)
+
#endif /* VLC_HTTP_H */
diff --git a/modules/misc/Modules.am b/modules/misc/Modules.am
index f0b69d4..dd71f28 100644
--- a/modules/misc/Modules.am
+++ b/modules/misc/Modules.am
@@ -6,6 +6,10 @@ libaudioscrobbler_plugin_la_SOURCES = audioscrobbler.c
libaudioscrobbler_plugin_la_LIBADD = $(SOCKET_LIBS) $(LIBPTHREAD)
misc_LTLIBRARIES += libaudioscrobbler_plugin.la
+libhttprequest_plugin_la_SOURCES = httprequest.c
+libhttprequest_plugin_la_LIBADD = $(SOCKET_LIBS) $(LIBPTHREAD)
+misc_LTLIBRARIES += libhttprequest_plugin.la
+
libexport_plugin_la_SOURCES = \
playlist/html.c \
playlist/m3u.c \
diff --git a/src/Makefile.am b/src/Makefile.am
index 474da25..9d46adf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -424,6 +424,7 @@ SOURCES_libvlc_common = \
network/udp.c \
network/rootbind.c \
network/tls.c \
+ network/http.c \
text/charset.c \
text/strings.c \
text/unicode.c \
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 3fc39d7..6fd95d8 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -144,6 +144,8 @@ http_auth_Reset
http_auth_ParseWwwAuthenticateHeader
http_auth_ParseAuthenticationInfoHeader
http_auth_FormatAuthorizationHeader
+http_request_Send
+http_response_Delete
httpd_ClientIP
httpd_FileDelete
httpd_FileNew
diff --git a/src/network/http.c b/src/network/http.c
new file mode 100644
index 0000000..8326533
--- /dev/null
+++ b/src/network/http.c
@@ -0,0 +1,349 @@
+/*****************************************************************************
+ * http.c: functions to send HTTP requests and receive responses.
+ *****************************************************************************
+ * Copyright (C) 2014 VLC authors and VideoLAN
+ *
+ * Authors: Felix Abecassis <felix.abecassis at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_url.h>
+#include <vlc_network.h>
+#include <vlc_http.h>
+
+#include <errno.h>
+
+/* 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)
+ {
+ /* Ran out of space for storing the line, grow the buffer. */
+ size *= 2;
+ str = realloc(str, sizeof(*str) * size);
+ if (!str)
+ return NULL;
+ }
+ str[i++] = c;
+ if (c == '\n')
+ break;
+ }
+
+ if (i == 0)
+ {
+ free(str);
+ return NULL;
+ }
+
+ str[i] = '\0';
+ return str;
+}
+
+/* Concatenate the given key-value pair to string headers. */
+static char *http_add_header(char *headers, const char *key, const char *value)
+{
+ char *new_headers;
+ int ret = asprintf(&new_headers, "%s%s: %s\r\n", headers, key, value);
+ free(headers);
+ return ret >= 0 ? new_headers : NULL;
+}
+
+/* Split a header line with format "Key: Value" */
+static int http_split_header_line(const char *line, char** key, char **value)
+{
+ *key = NULL;
+ *value = NULL;
+
+ const char *key_begin = line;
+ const char *key_end = strchr(key_begin, ':');
+ *key = strndup(key_begin, key_end - key_begin);
+ if (!*key)
+ goto error;
+
+ const char *value_begin = key_end + 1;
+ while (*value_begin == ' ')
+ ++value_begin;
+ const char *value_end = strchr(value_begin, '\r');
+ if (!value_end)
+ goto error;
+ /* If value_end == value_begin, the field is empty, this is valid. */
+ if (value_end != value_begin)
+ {
+ *value = strndup(value_begin, value_end - value_begin);
+ if (!*value)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ free(*key);
+ free(*value);
+ return -1;
+}
+
+static vlc_http_response_t *http_get_response(vlc_object_t *p_this, int fd)
+{
+ vlc_http_response_t *response = calloc(1, sizeof(*response));
+ if (!response)
+ return NULL;
+
+ vlc_http_status_line_t *status_line = &response->status_line;
+ vlc_http_header_t *header = &response->header;
+ vlc_http_body_t *body = &response->body;
+
+ /* 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, "Received invalid HTTP status line.");
+ goto error;
+ }
+
+ /* Parse headers, line by line. */
+ while ((line = http_readline(p_this, fd)))
+ {
+ /* Line with only \r\n: end of headers. */
+ if (!strncmp(line, "\r\n", 2))
+ {
+ free(line);
+ break;
+ }
+
+ /* Extract the key-value pair from the raw line. */
+ char *key = NULL;
+ char *value = NULL;
+ ret = http_split_header_line(line, &key, &value);
+ free(line);
+ if (ret < 0)
+ goto error;
+
+ /* Grow the dictionary and add the new key-value pair. */
+ header->dictionary = realloc(header->dictionary, sizeof(*header->dictionary) * (header->size + 1));
+ if (!header->dictionary)
+ {
+ free(key);
+ free(value);
+ goto error;
+ }
+ header->dictionary[header->size].key = key;
+ header->dictionary[header->size].value = value;
+ ++header->size;
+ }
+
+ /* Try to find Content-Length entry in the header */
+ for (size_t i = 0; i < header->size; ++i)
+ {
+ const char *key = header->dictionary[i].key;
+ const char *value = header->dictionary[i].value;
+ if (!strncasecmp(key, "Content-Length", 14))
+ {
+ /* Get the field value. */
+ errno = 0;
+ body->size = strtoul(value, NULL, 10);
+ if (errno)
+ {
+ msg_Err(p_this, "Invalid Content-Length in HTTP response.");
+ goto error;
+ }
+ break;
+ }
+ }
+
+ /* Read the body of the response. */
+ if (body->size > 0)
+ {
+ /* We add a terminal \0 for debugging convenience. */
+ body->data = malloc(body->size + 1);
+ if (!body->data)
+ goto error;
+
+ size_t size = net_Read(p_this, fd, NULL, body->data, body->size, true);
+ if (size < body->size)
+ goto error;
+
+ char* end = (char*)body->data + body->size;
+ *end = '\0';
+ }
+ return response;
+
+error:
+ msg_Err(p_this, "Error in %s", __FUNCTION__);
+ http_response_Delete(response);
+ return 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]))
+
+#define HTTP_EXPECT_CONTINUE_THRESHOLD 1024
+
+#undef http_request_Send
+vlc_http_response_t *http_request_Send(vlc_object_t *p_this, vlc_url_t *url,
+ vlc_http_request_t *request)
+{
+ 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;
+
+ char *header_string = NULL;
+ vlc_http_response_t *response = NULL;
+
+ int fd = net_ConnectTCP(p_this, url->psz_host, url->i_port);
+ if (fd < 0)
+ {
+ msg_Err(p_this, "Could not connect to %s:%d", url->psz_host, url->i_port);
+ return NULL;
+ }
+
+ /* Lookup the string corresponding to the HTTP 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)
+ goto end;
+
+ /* Create the header string. */
+ int ret;
+ header_string = strdup("");
+ for (size_t i = 0; i < request_header->size; ++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 (overwritten by VLC)", key, value);
+ continue;
+ }
+ /* Concatenate new key-value entry to the string. */
+ header_string = http_add_header(header_string, key, value);
+ if (!header_string)
+ goto end;
+ }
+ /* If the body is larger than the threshold, we first ask the
+ server if the headers are acceptable using Expect: 100-continue.
+ This is only for HTTP 1.1. */
+ bool use_expect_continue = request_line->http_version >= 1 && request_body->size >= HTTP_EXPECT_CONTINUE_THRESHOLD;
+ if (use_expect_continue)
+ if (!(header_string = http_add_header(header_string, "Expect", "100-continue")))
+ goto end;
+
+ /* Force "Connection: close" mode. */
+ if (!(header_string = http_add_header(header_string, "Connection", "close")))
+ goto end;
+
+ /* Send request line and header. */
+ ret = net_Printf(p_this, fd, NULL,
+ "%s %s HTTP/1.%d\r\n"
+ "%s"
+ "Content-Length: %zu\r\n"
+ "\r\n",
+ method_string, request_line->uri, request_line->http_version,
+ header_string, request_body->size);
+ if (ret == -1)
+ goto end;
+
+ if (use_expect_continue)
+ {
+ vlc_http_response_t *expect_response = http_get_response(p_this, fd);
+ if (!expect_response) /* Error while reading the response. */
+ goto end;
+ int status_code = expect_response->status_line.status_code;
+ http_response_Delete(expect_response);
+ if (status_code != 100) /* Server did not accept the headers. */
+ {
+ msg_Err(p_this, "\"Expect: 100-continue\" request denied (status: %d).", status_code);
+ goto end;
+ }
+ }
+
+ /* Send body. */
+ if (request_body->size > 0)
+ {
+ ret = net_Write(p_this, fd, NULL, request_body->data, request_body->size);
+ if (ret == -1)
+ {
+ msg_Err(p_this, "Could not send HTTP request body.");
+ goto end;
+ }
+ }
+
+ response = http_get_response(p_this, fd);
+
+end:
+ free(header_string);
+ net_Close(fd);
+ return response;
+}
+
+void http_response_Delete(vlc_http_response_t *response)
+{
+ if (!response)
+ return;
+
+ vlc_http_header_t *header = &response->header;
+ vlc_http_body_t *body = &response->body;
+
+ for (size_t i = 0; i < header->size; ++i)
+ {
+ char *key = (char*)header->dictionary[i].key;
+ char *value = (char*)header->dictionary[i].value;
+ free(key);
+ free(value);
+ }
+ free(header->dictionary);
+ free(body->data);
+ free(response);
+}
--
1.8.3.2
More information about the vlc-devel
mailing list