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:
parent
7c67634fec
commit
afe26cf4a6
5 changed files with 154 additions and 99 deletions
153
lib/handshake.c
153
lib/handshake.c
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
70
lib/server.c
70
lib/server.c
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue