[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