1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

HTTP Version, Keep-alive support, No-copy POST

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
This commit is contained in:
Andrew Canaday 2014-07-13 01:07:36 -04:00 committed by Andy Green
parent 7c67634fec
commit afe26cf4a6
5 changed files with 154 additions and 99 deletions

View file

@ -46,6 +46,10 @@
* 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");

View 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);

View 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) {

View file

@ -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 {

View 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)