From: Andy Green Date: Wed, 8 Oct 2014 04:00:53 +0000 (+0800) Subject: http2 can keep upgraded connection up X-Git-Tag: upstream/1.7.3~559 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=024eb6c80c596284f686b0df09fd16fa4cc7d79d;p=platform%2Fupstream%2Flibwebsockets.git http2 can keep upgraded connection up Signed-off-by: Andy Green --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 473b057..59d6c3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,6 +295,7 @@ endif() if (LWS_WITH_HTTP2) list(APPEND SOURCES + lib/http2.c lib/ssl-http2.c ) endif() diff --git a/lib/client.c b/lib/client.c index 01f2aa1..3cff9a0 100755 --- a/lib/client.c +++ b/lib/client.c @@ -733,7 +733,7 @@ check_accept: memset(&wsi->u, 0, sizeof(wsi->u)); - wsi->u.ws.rxflow_change_to = LWS_RXFLOW_ALLOW; + wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* * create the frame buffer for this connection according to the diff --git a/lib/handshake.c b/lib/handshake.c index d81c1d2..5903557 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -49,7 +49,6 @@ #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 @@ -66,6 +65,27 @@ libwebsocket_read(struct libwebsocket_context *context, unsigned char *last_char; switch (wsi->state) { + case WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE: + case WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS: + case WSI_STATE_HTTP2_ESTABLISHED: + n = 0; + while (n < len) { + /* + * we were accepting input but now we stopped doing so + */ + if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { + lws_rxflow_cache(wsi, buf, n, len); + + return 1; + } + + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) + wsi->rxflow_pos++; + if (lws_http2_parser(context, wsi, buf[n++])) + goto bail; + } + break; http_new: case WSI_STATE_HTTP: wsi->hdr_parsing_completed = 0; diff --git a/lib/http2.c b/lib/http2.c new file mode 100644 index 0000000..74f196a --- /dev/null +++ b/lib/http2.c @@ -0,0 +1,324 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2013 Andy Green + * + * This library 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: + * version 2.1 of the License. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + + +#include "private-libwebsockets.h" + +const struct http2_settings lws_http2_default_settings = { { + 0, + /* LWS_HTTP2_SETTINGS__HEADER_TABLE_SIZE */ 4096, + /* LWS_HTTP2_SETTINGS__ENABLE_PUSH */ 1, + /* LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS */ 100, + /* LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE */ 65535, + /* LWS_HTTP2_SETTINGS__MAX_FRAME_SIZE */ 16384, + /* LWS_HTTP2_SETTINGS__MAX_HEADER_LIST_SIZE */ ~0, +}}; + +void lws_http2_init(struct http2_settings *settings) +{ + memcpy(settings, lws_http2_default_settings.setting, sizeof(*settings)); +} + +struct libwebsocket * +lws_http2_wsi_from_id(struct libwebsocket *wsi, unsigned int sid) +{ + do { + if (wsi->u.http2.my_stream_id == sid) + return wsi; + + wsi = wsi->u.http2.next_child_wsi; + } while (wsi); + + return NULL; +} + +struct libwebsocket * +lws_create_server_child_wsi(struct libwebsocket_context *context, struct libwebsocket *parent_wsi, unsigned int sid) +{ + struct libwebsocket *wsi = libwebsocket_create_new_server_wsi(context); + + if (!wsi) + return NULL; + + /* no more children allowed by parent */ + if (parent_wsi->u.http2.child_count + 1 == parent_wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS]) + return NULL; + + lws_http2_init(&wsi->u.http2.peer_settings); + lws_http2_init(&wsi->u.http2.my_settings); + wsi->u.http2.stream_id = sid; + + wsi->u.http2.parent_wsi = parent_wsi; + wsi->u.http2.next_child_wsi = parent_wsi->u.http2.next_child_wsi; + parent_wsi->u.http2.next_child_wsi = wsi; + parent_wsi->u.http2.child_count++; + + wsi->u.http2.my_priority = 16; + + wsi->state = WSI_STATE_HTTP2_ESTABLISHED; + wsi->mode = parent_wsi->mode; + + lwsl_info("%s: %p new child %p, sid %d\n", __func__, parent_wsi, wsi, sid); + + return wsi; +} + +int lws_remove_server_child_wsi(struct libwebsocket_context *context, struct libwebsocket *wsi) +{ + struct libwebsocket **w = &wsi->u.http2.parent_wsi; + do { + if (*w == wsi) { + *w = wsi->u.http2.next_child_wsi; + (wsi->u.http2.parent_wsi)->u.http2.child_count--; + return 0; + } + + w = &((*w)->u.http2.next_child_wsi); + } while (*w); + + lwsl_err("%s: can't find %p\n", __func__, wsi); + return 1; +} + +int +lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned char *buf, int len) +{ + unsigned int a, b; + + if (!len) + return 0; + + if (len < LWS_HTTP2_SETTINGS_LENGTH) + return 1; + + while (len >= LWS_HTTP2_SETTINGS_LENGTH) { + a = (buf[0] << 8) | buf[1]; + if (a < LWS_HTTP2_SETTINGS__COUNT) { + b = buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5]; + settings->setting[a] = b; + lwsl_info("http2 settings %d <- 0x%x\n", a, b); + } + len -= LWS_HTTP2_SETTINGS_LENGTH; + buf += LWS_HTTP2_SETTINGS_LENGTH; + } + + if (len) + return 1; + + return 0; +} + +int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigned int sid, unsigned int len, unsigned char *buf) +{ + unsigned char *p = &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH]; + int n; + + *p++ = len >> 16; + *p++ = len >> 8; + *p++ = len; + *p++ = type; + *p++ = flags; + *p++ = sid >> 24; + *p++ = sid >> 16; + *p++ = sid >> 8; + *p++ = sid; + + lwsl_info("%s: %p. type %d, flags 0x%x, sid=%d, len=%d\n", + __func__, wsi, type, flags, sid, len); + + n = lws_issue_raw(wsi, &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH], len + LWS_HTTP2_FRAME_HEADER_LENGTH); + if (n >= LWS_HTTP2_FRAME_HEADER_LENGTH) + return n - LWS_HTTP2_FRAME_HEADER_LENGTH; + + return n; +} + +static void lws_http2_settings_write(struct libwebsocket *wsi, int n, unsigned char *buf) +{ + *buf++ = n >> 8; + *buf++ = n; + *buf++ = wsi->u.http2.my_settings.setting[n] >> 24; + *buf++ = wsi->u.http2.my_settings.setting[n] >> 16; + *buf++ = wsi->u.http2.my_settings.setting[n] >> 8; + *buf = wsi->u.http2.my_settings.setting[n]; +} + +static const char const * https_client_preface = + "PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a"; + +int +lws_http2_parser(struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned char c) +{ + struct libwebsocket *wsi_new; + + switch (wsi->state) { + case WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE: + if (https_client_preface[wsi->u.http2.count++] != c) + return 1; + + if (!https_client_preface[wsi->u.http2.count]) { + lwsl_err("http2: %p: established\n", wsi); + wsi->state = WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS; + wsi->u.http2.count = 0; + + /* + * we must send a settings frame -- empty one is OK... + * that must be the first thing sent by server + * and the peer must send a SETTINGS with ACK flag... + */ + + lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_MY_SETTINGS); + } + break; + + case WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS: + case WSI_STATE_HTTP2_ESTABLISHED: + if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { // payload + switch(wsi->u.http2.type) { + case LWS_HTTP2_FRAME_TYPE_SETTINGS: + wsi->u.http2.one_setting[wsi->u.http2.count % LWS_HTTP2_SETTINGS_LENGTH] = c; + if (wsi->u.http2.count % LWS_HTTP2_SETTINGS_LENGTH == LWS_HTTP2_SETTINGS_LENGTH - 1) + if (lws_http2_interpret_settings_payload( + &wsi->u.http2.peer_settings, + wsi->u.http2.one_setting, + LWS_HTTP2_SETTINGS_LENGTH)) + return 1; + break; + } + wsi->u.http2.count++; + if (wsi->u.http2.count == wsi->u.http2.length) { + wsi->u.http2.frame_state = 0; + wsi->u.http2.count = 0; + /* set our initial window size */ + if (!wsi->u.http2.initialized) { + wsi->u.http2.tx_credit = wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE]; + lwsl_info("initial tx credit on master conn %p: %d\n", wsi, wsi->u.http2.tx_credit); + wsi->u.http2.initialized = 1; + } + } + break; + } + switch (wsi->u.http2.frame_state++) { + case 0: + wsi->u.http2.length = c; + break; + case 1: + case 2: + wsi->u.http2.length <<= 8; + wsi->u.http2.length |= c; + break; + case 3: + wsi->u.http2.type = c; + break; + case 4: + wsi->u.http2.flags = c; + break; + case 5: + case 6: + case 7: + case 8: + wsi->u.http2.stream_id <<= 8; + wsi->u.http2.stream_id |= c; + break; + } + if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { /* frame header complete */ + lwsl_info("frame: type 0x%x, flags 0x%x, sid 0x%x, len 0x%x\n", + wsi->u.http2.type, wsi->u.http2.flags, wsi->u.http2.stream_id, wsi->u.http2.length); + wsi->u.http2.count = 0; + + + switch (wsi->u.http2.type) { + case LWS_HTTP2_FRAME_TYPE_SETTINGS: + /* nonzero sid on settings is illegal */ + if (wsi->u.http2.stream_id) + return 1; + + if (wsi->u.http2.flags & 1) { // ack + } else { + lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_ACK_SETTINGS); + } + break; + case LWS_HTTP2_FRAME_TYPE_HEADERS: + wsi_new = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id); + if (!wsi_new) { + wsi_new = lws_create_server_child_wsi(context, wsi, wsi->u.http2.stream_id); + } + } + if (wsi->u.http2.length == 0) + wsi->u.http2.frame_state = 0; + + } + break; + } + + return 0; +} + +int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsocket *wsi) +{ + unsigned char settings[LWS_SEND_BUFFER_PRE_PADDING + 6 * LWS_HTTP2_SETTINGS__COUNT]; + int n, m = 0; + + switch (wsi->pps) { + case LWS_PPS_HTTP2_MY_SETTINGS: + for (n = 1; n < LWS_HTTP2_SETTINGS__COUNT; n++) + if (wsi->u.http2.my_settings.setting[n] != lws_http2_default_settings.setting[n]) { + lws_http2_settings_write(wsi, n, + &settings[LWS_SEND_BUFFER_PRE_PADDING + m]); + m += sizeof(wsi->u.http2.one_setting); + } + n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS, + 0, LWS_HTTP2_STREAM_ID_MASTER, m, + &settings[LWS_SEND_BUFFER_PRE_PADDING]); + if (n != m) { + lwsl_info("send %d %d\n", n, m); + return 1; + } + break; + case LWS_PPS_HTTP2_ACK_SETTINGS: + /* send ack ... always empty */ + n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS, + 1, LWS_HTTP2_STREAM_ID_MASTER, 0, + &settings[LWS_SEND_BUFFER_PRE_PADDING]); + if (n) { + lwsl_err("ack tells %d\n", n); + return 1; + } + /* this is the end of the preface dance then? */ + if (wsi->state == WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS) { + wsi->state = WSI_STATE_HTTP2_ESTABLISHED; + + wsi->u.http.fd = LWS_INVALID_FILE; + + /* service the http request itself */ + //lwsl_info("servicing initial http request\n"); + //n = lws_http_action(context, wsi); + + return 0; + } + break; + default: + break; + } + + return 0; +} \ No newline at end of file diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 12588f7..a332fc6 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -88,6 +88,13 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, free(wsi->u.hdr.ah); goto just_kill_connection; } + + if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING) { + if (wsi->u.hdr.ah) { + free(wsi->u.hdr.ah); + wsi->u.hdr.ah = NULL; + } + } if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED) { if (wsi->u.http.fd != LWS_INVALID_FILE) { @@ -205,6 +212,11 @@ just_kill_connection: remove_wsi_socket_from_fds(context, wsi); wsi->state = WSI_STATE_DEAD_SOCKET; + + if (wsi->rxflow_buffer) { + free(wsi->rxflow_buffer); + wsi->rxflow_buffer = NULL; + } if ((old_state == WSI_STATE_ESTABLISHED || wsi->mode == LWS_CONNMODE_WS_SERVING || @@ -214,10 +226,7 @@ just_kill_connection: free(wsi->u.ws.rx_user_buffer); wsi->u.ws.rx_user_buffer = NULL; } - if (wsi->u.ws.rxflow_buffer) { - free(wsi->u.ws.rxflow_buffer); - wsi->u.ws.rxflow_buffer = NULL; - } + if (wsi->truncated_send_malloc) { /* not going to be completed... nuke it */ free(wsi->truncated_send_malloc); @@ -546,11 +555,11 @@ lws_latency(struct libwebsocket_context *context, struct libwebsocket *wsi, LWS_VISIBLE int libwebsocket_rx_flow_control(struct libwebsocket *wsi, int enable) { - if (enable == (wsi->u.ws.rxflow_change_to & LWS_RXFLOW_ALLOW)) + if (enable == (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) return 0; lwsl_info("libwebsocket_rx_flow_control(0x%p, %d)\n", wsi, enable); - wsi->u.ws.rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !!enable; + wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !!enable; return 0; } @@ -803,3 +812,16 @@ lws_partial_buffered(struct libwebsocket *wsi) { return !!wsi->truncated_send_len; } + +void lws_set_protocol_write_pending(struct libwebsocket_context *context, + struct libwebsocket *wsi, + enum lws_pending_protocol_send pend) +{ + lwsl_err("setting pps %d\n", pend); + + if (wsi->pps) + lwsl_err("pps overwrite\n"); + wsi->pps = pend; + libwebsocket_rx_flow_control(wsi, 0); + libwebsocket_callback_on_writable(context, wsi); +} \ No newline at end of file diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index cbd1ce5..777b315 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -257,6 +257,10 @@ enum libwebsocket_write_protocol { LWS_WRITE_PING, LWS_WRITE_PONG, + /* HTTP2 */ + + LWS_WRITE_HTTP_HEADERS, + /* flags */ LWS_WRITE_NO_FIN = 0x40, diff --git a/lib/output.c b/lib/output.c index cd8da35..ccd02d7 100644 --- a/lib/output.c +++ b/lib/output.c @@ -262,7 +262,7 @@ LWS_VISIBLE int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, return 0; } - if (protocol == LWS_WRITE_HTTP) + if (protocol == LWS_WRITE_HTTP || protocol == LWS_WRITE_HTTP_HEADERS) goto send_raw; /* websocket protocol, either binary or text */ @@ -429,8 +429,15 @@ send_raw: case LWS_WRITE_CLOSE: /* lwsl_hexdump(&buf[-pre], len + post); */ case LWS_WRITE_HTTP: + case LWS_WRITE_HTTP_HEADERS: case LWS_WRITE_PONG: case LWS_WRITE_PING: + if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING) { + n = LWS_HTTP2_FRAME_TYPE_DATA; + if (protocol == LWS_WRITE_HTTP_HEADERS) + n = LWS_HTTP2_FRAME_TYPE_HEADERS; + return lws_http2_frame_write(wsi, n, 0, wsi->u.http2.my_stream_id, len, buf); + } return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre + post); default: diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 3748acb..3ec5d02 100755 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -305,6 +305,10 @@ enum lws_connection_states { WSI_STATE_RETURNED_CLOSE_ALREADY, WSI_STATE_AWAITING_CLOSE_ACK, WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE, + + WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE, + WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS, + WSI_STATE_HTTP2_ESTABLISHED, }; enum http_version { @@ -317,6 +321,12 @@ enum http_connection_type { HTTP_CONNECTION_KEEP_ALIVE }; +enum lws_pending_protocol_send { + LWS_PPS_NONE, + LWS_PPS_HTTP2_MY_SETTINGS, + LWS_PPS_HTTP2_ACK_SETTINGS, +}; + enum lws_rx_parse_state { LWS_RXPS_NEW, @@ -526,6 +536,18 @@ struct lws_fragments { unsigned char next_frag_index; }; +/* notice that these union members: + * + * hdr + * http + * http2 + * + * all have a pointer to allocated_headers struct as their first member. + * + * It means for allocated_headers access, the three union paths can all be + * used interchangably to access the same data + */ + struct allocated_headers { unsigned short next_frag_index; unsigned short pos; @@ -539,6 +561,7 @@ struct allocated_headers { }; struct _lws_http_mode_related { + /* MUST be first in struct */ struct allocated_headers *ah; /* mirroring _lws_header_related */ #if defined(WIN32) || defined(_WIN32) HANDLE fd; @@ -554,10 +577,80 @@ struct _lws_http_mode_related { int content_remain; }; +#ifdef LWS_USE_HTTP2 + +enum lws_http2_settings { + LWS_HTTP2_SETTINGS__HEADER_TABLE_SIZE = 1, + LWS_HTTP2_SETTINGS__ENABLE_PUSH, + LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS, + LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE, + LWS_HTTP2_SETTINGS__MAX_FRAME_SIZE, + LWS_HTTP2_SETTINGS__MAX_HEADER_LIST_SIZE, + + LWS_HTTP2_SETTINGS__COUNT /* always last */ +}; + +enum lws_http2_wellknown_frame_types { + LWS_HTTP2_FRAME_TYPE_DATA, + LWS_HTTP2_FRAME_TYPE_HEADERS, + LWS_HTTP2_FRAME_TYPE_PRIORITY, + LWS_HTTP2_FRAME_TYPE_RST_STREAM, + LWS_HTTP2_FRAME_TYPE_SETTINGS, + LWS_HTTP2_FRAME_TYPE_PUSH_PROMISE, + LWS_HTTP2_FRAME_TYPE_PING, + LWS_HTTP2_FRAME_TYPE_GOAWAY, + LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE, + LWS_HTTP2_FRAME_TYPE_CONTINUATION, + + LWS_HTTP2_FRAME_TYPE_COUNT /* always last */ +}; + +#define LWS_HTTP2_STREAM_ID_MASTER 0 +#define LWS_HTTP2_FRAME_HEADER_LENGTH 9 +#define LWS_HTTP2_SETTINGS_LENGTH 6 + +struct http2_settings { + unsigned int setting[LWS_HTTP2_SETTINGS__COUNT]; +}; + struct _lws_http2_related { + /* + * having this first lets us also re-use all HTTP union code + * and in turn, http_mode_related has allocated headers in right + * place so we can use the header apis on the wsi directly still + */ + struct _lws_http_mode_related http; /* MUST BE FIRST IN STRUCT */ + + struct http2_settings my_settings; + struct http2_settings peer_settings; + + struct libwebsocket *parent_wsi; + struct libwebsocket *next_child_wsi; + + unsigned int count; + + /* frame */ + unsigned int length; + unsigned int stream_id; + struct libwebsocket *stream_wsi; + unsigned char type; + unsigned char flags; + unsigned char frame_state; + + unsigned int tx_credit; + unsigned int my_stream_id; + unsigned int child_count; + int my_priority; + unsigned char initialized; + unsigned char one_setting[LWS_HTTP2_SETTINGS_LENGTH]; }; +#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->parent_wsi) + +#endif + struct _lws_header_related { + /* MUST be first in struct */ struct allocated_headers *ah; short lextable_pos; unsigned short current_token_limit; @@ -579,10 +672,7 @@ struct _lws_websocket_related { unsigned int frame_is_binary:1; unsigned int all_zero_nonce:1; short close_reason; /* enum lws_close_status */ - unsigned char *rxflow_buffer; - int rxflow_len; - int rxflow_pos; - unsigned int rxflow_change_to:2; + unsigned int this_frame_masked:1; unsigned int inside_frame:1; /* next write will be more of frame */ unsigned int clean_buffer:1; /* buffer not rewritten by extension */ @@ -609,6 +699,7 @@ struct libwebsocket { unsigned int extension_data_pending:1; #endif unsigned char ietf_spec_revision; + enum lws_pending_protocol_send pps; char mode; /* enum connection_mode */ char state; /* enum lws_connection_states */ @@ -627,6 +718,11 @@ struct libwebsocket { unsigned long action_start; unsigned long latency_start; #endif + /* rxflow handling */ + unsigned char *rxflow_buffer; + int rxflow_len; + int rxflow_pos; + unsigned int rxflow_change_to:2; /* truncated send handling */ unsigned char *truncated_send_malloc; /* non-NULL means buffering in progress */ @@ -640,7 +736,9 @@ struct libwebsocket { union u { struct _lws_http_mode_related http; +#ifdef LWS_USE_HTTP2 struct _lws_http2_related http2; +#endif struct _lws_header_related hdr; struct _lws_websocket_related ws; } u; @@ -665,6 +763,8 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, LWS_EXTERN int remove_wsi_socket_from_fds(struct libwebsocket_context *context, struct libwebsocket *wsi); +LWS_EXTERN int +lws_rxflow_cache(struct libwebsocket *wsi, unsigned char *buf, int n, int len); #ifndef LWS_LATENCY static inline void lws_latency(struct libwebsocket_context *context, @@ -680,6 +780,9 @@ lws_latency(struct libwebsocket_context *context, int ret, int completion); #endif +LWS_EXTERN void lws_set_protocol_write_pending(struct libwebsocket_context *context, + struct libwebsocket *wsi, + enum lws_pending_protocol_send pend); LWS_EXTERN int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c); @@ -688,6 +791,9 @@ libwebsocket_parse(struct libwebsocket_context *context, struct libwebsocket *wsi, unsigned char c); LWS_EXTERN int +lws_http_action(struct libwebsocket_context *context, struct libwebsocket *wsi); + +LWS_EXTERN int lws_b64_selftest(void); LWS_EXTERN struct libwebsocket * @@ -768,6 +874,18 @@ user_callback_handle_rxflow(callback_function, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len); +#ifdef LWS_USE_HTTP2 +LWS_EXTERN int +lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned char *buf, int len); +LWS_EXTERN void lws_http2_init(struct http2_settings *settings); +LWS_EXTERN int +lws_http2_parser(struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned char c); +LWS_EXTERN int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsocket *wsi); +LWS_EXTERN int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigned int sid, unsigned int len, unsigned char *buf); +LWS_EXTERN struct libwebsocket * +lws_http2_wsi_from_id(struct libwebsocket *wsi, unsigned int sid); +#endif LWS_EXTERN int lws_plat_set_socket_options(struct libwebsocket_context *context, int fd); diff --git a/lib/server-handshake.c b/lib/server-handshake.c index 21beaa0..4c0fa73 100644 --- a/lib/server-handshake.c +++ b/lib/server-handshake.c @@ -206,7 +206,7 @@ handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi) /* make a buffer big enough for everything */ - response = (char *)context->service_buffer + MAX_WEBSOCKET_04_KEY_LEN; + response = (char *)context->service_buffer + MAX_WEBSOCKET_04_KEY_LEN + LWS_SEND_BUFFER_PRE_PADDING; p = response; LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Upgrade: WebSocket\x0d\x0a" @@ -246,7 +246,7 @@ handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi) fwrite(response, 1, p - response, stderr); #endif n = libwebsocket_write(wsi, (unsigned char *)response, - p - response, LWS_WRITE_HTTP); + p - response, LWS_WRITE_HTTP_HEADERS); if (n != (p - response)) { lwsl_debug("handshake_0405: ERROR writing to socket\n"); goto bail; diff --git a/lib/server.c b/lib/server.c index 5f3ac2d..68ee481 100644 --- a/lib/server.c +++ b/lib/server.c @@ -135,11 +135,11 @@ _libwebsocket_rx_flow_control(struct libwebsocket *wsi) struct libwebsocket_context *context = wsi->protocol->owning_server; /* there is no pending change */ - if (!(wsi->u.ws.rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)) + if (!(wsi->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)) return 0; /* stuff is still buffered, not ready to really accept new input */ - if (wsi->u.ws.rxflow_buffer) { + if (wsi->rxflow_buffer) { /* get ourselves called back to deal with stashed buffer */ libwebsocket_callback_on_writable(context, wsi); return 0; @@ -147,14 +147,14 @@ _libwebsocket_rx_flow_control(struct libwebsocket *wsi) /* pending is cleared, we can change rxflow state */ - wsi->u.ws.rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE; + wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE; lwsl_info("rxflow: wsi %p change_to %d\n", wsi, - wsi->u.ws.rxflow_change_to & LWS_RXFLOW_ALLOW); + wsi->rxflow_change_to & LWS_RXFLOW_ALLOW); /* adjust the pollfd for this wsi */ - if (wsi->u.ws.rxflow_change_to & LWS_RXFLOW_ALLOW) { + if (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW) { if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { lwsl_info("%s: fail\n", __func__); return -1; @@ -166,20 +166,164 @@ _libwebsocket_rx_flow_control(struct libwebsocket *wsi) return 0; } - -int lws_handshake_server(struct libwebsocket_context *context, - struct libwebsocket *wsi, unsigned char **buf, size_t len) +int lws_http_action(struct libwebsocket_context *context, + struct libwebsocket *wsi) { - 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, protocol_len; + int http_version_len; char content_length_str[32]; + char http_version_str[10]; + char http_conn_str[20]; + int n; + + /* it's not websocket.... shall we accept it as http? */ + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) && + !lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) && + !lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)) { + lwsl_warn("Missing URI in HTTP request\n"); + goto bail_nuke_ah; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) && + lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { + lwsl_warn("GET and POST methods?\n"); + goto bail_nuke_ah; + } + + if (libwebsocket_ensure_user_space(wsi)) + goto bail_nuke_ah; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); + lwsl_info("HTTP GET request for '%s'\n", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI)); + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { + lwsl_info("HTTP POST request for '%s'\n", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_POST_URI)); + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_POST_URI); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI); + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)) { + lwsl_info("HTTP OPTIONS request for '%s'\n", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_OPTIONS_URI)); + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_OPTIONS_URI); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI); + } + + /* HTTP header had a content length? */ + + wsi->u.http.content_length = 0; + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) + wsi->u.http.content_length = 100 * 1024 * 1024; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + lws_hdr_copy(wsi, content_length_str, + sizeof(content_length_str) - 1, + WSI_TOKEN_HTTP_CONTENT_LENGTH); + wsi->u.http.content_length = atoi(content_length_str); + } + + /* 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) { + 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)) { + 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")) + connection_type = HTTP_CONNECTION_KEEP_ALIVE; + else + if (strcasecmp(http_conn_str, "close")) + connection_type = HTTP_CONNECTION_CLOSE; + } + wsi->u.http.connection_type = connection_type; + + n = 0; + if (wsi->protocol->callback) + n = wsi->protocol->callback(context, wsi, + LWS_CALLBACK_FILTER_HTTP_CONNECTION, + wsi->user_space, uri_ptr, uri_len); + + if (!n) { + /* + * if there is content supposed to be coming, + * put a timeout on it having arrived + */ + libwebsocket_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + AWAITING_TIMEOUT); + + if (wsi->protocol->callback) + n = wsi->protocol->callback(context, wsi, + LWS_CALLBACK_HTTP, + wsi->user_space, uri_ptr, uri_len); + } + + /* now drop the header info we kept a pointer to */ + if (wsi->u.http.ah) + free(wsi->u.http.ah); + /* not possible to continue to use past here */ + wsi->u.http.ah = NULL; + + if (n) { + lwsl_info("LWS_CALLBACK_HTTP closing\n"); + return 1; /* struct ah ptr already nuked */ } + + /* + * 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) + /* 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; + +bail_nuke_ah: + /* drop the header info */ + if (wsi->u.hdr.ah) { + free(wsi->u.hdr.ah); + wsi->u.hdr.ah = NULL; + } + + return 1; +} + + +int lws_handshake_server(struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned char **buf, size_t len) +{ + struct allocated_headers *ah; + int protocol_len; char protocol_list[128]; char protocol_name[32]; - char http_version_str[10]; char *p; int n, hit; @@ -203,53 +347,9 @@ int lws_handshake_server(struct libwebsocket_context *context, if (!lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE) || !lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { - - /* it's not websocket.... shall we accept it as http? */ - - if (!lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) && - !lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) && - !lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)) { - lwsl_warn("Missing URI in HTTP request\n"); - goto bail_nuke_ah; - } - - if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) && - lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { - lwsl_warn("GET and POST methods?\n"); - goto bail_nuke_ah; - } - - if (libwebsocket_ensure_user_space(wsi)) - goto bail_nuke_ah; - - if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { - uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); - uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); - lwsl_info("HTTP GET request for '%s'\n", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI)); - - } - if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { - lwsl_info("HTTP POST request for '%s'\n", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_POST_URI)); - uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_POST_URI); - uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI); - } - if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)) { - lwsl_info("HTTP OPTIONS request for '%s'\n", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_OPTIONS_URI)); - uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_OPTIONS_URI); - uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI); - } - - /* - * Hm we still need the headers so the - * callback can look at leaders like the URI, but we - * need to transition to http union state.... hold a - * copy of u.hdr.ah and deallocate afterwards - */ + ah = wsi->u.hdr.ah; - + /* union transition */ memset(&wsi->u, 0, sizeof(wsi->u)); wsi->mode = LWS_CONNMODE_HTTP_SERVING_ACCEPTED; @@ -258,100 +358,10 @@ int lws_handshake_server(struct libwebsocket_context *context, /* expose it at the same offset as u.hdr */ wsi->u.http.ah = ah; + + n = lws_http_action(context, wsi); - /* HTTP header had a content length? */ - - wsi->u.http.content_length = 0; - if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) - wsi->u.http.content_length = 100 * 1024 * 1024; - - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { - lws_hdr_copy(wsi, content_length_str, - sizeof(content_length_str) - 1, - WSI_TOKEN_HTTP_CONTENT_LENGTH); - wsi->u.http.content_length = atoi(content_length_str); - } - - /* 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) { - 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) - n = wsi->protocol->callback(context, wsi, - LWS_CALLBACK_FILTER_HTTP_CONNECTION, - wsi->user_space, uri_ptr, uri_len); - - if (!n) { - /* - * if there is content supposed to be coming, - * put a timeout on it having arrived - */ - libwebsocket_set_timeout(wsi, - PENDING_TIMEOUT_HTTP_CONTENT, - AWAITING_TIMEOUT); - - if (wsi->protocol->callback) - n = wsi->protocol->callback(context, wsi, - LWS_CALLBACK_HTTP, - wsi->user_space, uri_ptr, uri_len); - } - - /* now drop the header info we kept a pointer to */ - if (ah) - free(ah); - /* not possible to continue to use past here */ - wsi->u.http.ah = NULL; - - if (n) { - lwsl_info("LWS_CALLBACK_HTTP closing\n"); - return 1; /* struct ah ptr already nuked */ - } - - /* 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) - - /* 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 */ + return n; } lwsl_err(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE)); @@ -359,37 +369,65 @@ int lws_handshake_server(struct libwebsocket_context *context, if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "websocket")) goto upgrade_ws; - +#ifdef LWS_USE_HTTP2 if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "h2c-14")) goto upgrade_h2c; - +#endif /* dunno what he wanted to upgrade to */ goto bail_nuke_ah; - -upgrade_h2c: - strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Upgrade: h2c\x0d\x0a\x0d\x0a"); - n = libwebsocket_write(wsi, (unsigned char *)protocol_list, - strlen(protocol_list), LWS_WRITE_HTTP); - if (n != strlen(protocol_list)) { - lwsl_debug("http2 switch: ERROR writing to socket\n"); +#ifdef LWS_USE_HTTP2 +upgrade_h2c: + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { + lwsl_err("missing http2_settings\n"); goto bail_nuke_ah; } - /* drop the header info -- no bail_nuke_ah after this */ + lwsl_err("h2c upgrade...\n"); - if (wsi->u.hdr.ah) - free(wsi->u.hdr.ah); + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); + /* convert the peer's HTTP-Settings */ + n = lws_b64_decode_string(p, protocol_list, sizeof(protocol_list)); + if (n < 0) { + lwsl_parser("HTTP2_SETTINGS too long\n"); + return 1; + } + + /* adopt the header info */ + + ah = wsi->u.hdr.ah; wsi->mode = LWS_CONNMODE_HTTP2_SERVING; /* union transition */ memset(&wsi->u, 0, sizeof(wsi->u)); + /* http2 union member has http union struct at start */ + wsi->u.http.ah = ah; + + lws_http2_init(&wsi->u.http2.peer_settings); + lws_http2_init(&wsi->u.http2.my_settings); + + /* HTTP2 union */ + + lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings, (unsigned char *)protocol_list, n); + + strcpy(protocol_list, + "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Upgrade: h2c\x0d\x0a\x0d\x0a"); + n = lws_issue_raw(wsi, (unsigned char *)protocol_list, + strlen(protocol_list)); + if (n != strlen(protocol_list)) { + lwsl_debug("http2 switch: ERROR writing to socket\n"); + return 1; + } + + wsi->state = WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE; + return 0; +#endif upgrade_ws: if (!wsi->protocol) @@ -509,7 +547,6 @@ upgrade_ws: /* union transition */ memset(&wsi->u, 0, sizeof(wsi->u)); - wsi->u.ws.rxflow_change_to = LWS_RXFLOW_ALLOW; /* * create the frame buffer for this connection according to the @@ -559,6 +596,7 @@ libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) memset(new_wsi, 0, sizeof(struct libwebsocket)); new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* intialize the instance struct */ @@ -605,6 +643,7 @@ int lws_server_socket_service(struct libwebsocket_context *context, case LWS_CONNMODE_HTTP_SERVING: case LWS_CONNMODE_HTTP_SERVING_ACCEPTED: + case LWS_CONNMODE_HTTP2_SERVING: /* handle http headers coming in */ @@ -839,7 +878,8 @@ LWS_VISIBLE int libwebsockets_return_http_status( if (code >= 500 && code < (500 + ARRAY_SIZE(err500))) description = err500[code - 500]; - n = sprintf((char *)context->service_buffer, + n = sprintf((char *)context->service_buffer + + LWS_SEND_BUFFER_PRE_PADDING, "HTTP/1.0 %u %s\x0d\x0a" "Server: libwebsockets\x0d\x0a" "Content-Type: text/html\x0d\x0a\x0d\x0a" @@ -848,7 +888,8 @@ LWS_VISIBLE int libwebsockets_return_http_status( lwsl_info((const char *)context->service_buffer); - m = libwebsocket_write(wsi, context->service_buffer, n, LWS_WRITE_HTTP); + m = libwebsocket_write(wsi, context->service_buffer, n, + LWS_WRITE_HTTP_HEADERS); return m; } @@ -876,7 +917,8 @@ LWS_VISIBLE int libwebsockets_serve_http_file( struct libwebsocket *wsi, const char *file, const char *content_type, const char *other_headers) { - unsigned char *p = context->service_buffer; + unsigned char *response = context->service_buffer + LWS_SEND_BUFFER_PRE_PADDING; + unsigned char *p = response; int ret = 0; int n; @@ -889,9 +931,9 @@ LWS_VISIBLE int libwebsockets_serve_http_file( return -1; } - p += sprintf((char *)p, -"HTTP/1.0 200 OK\x0d\x0aServer: libwebsockets\x0d\x0a""Content-Type: %s\x0d\x0a", - content_type); + p += sprintf((char *)p, "HTTP/1.0 200 OK\x0d\x0a" + "Server: libwebsockets\x0d\x0a" + "Content-Type: %s\x0d\x0a", content_type); if (other_headers) { n = strlen(other_headers); memcpy(p, other_headers, n); @@ -900,10 +942,10 @@ LWS_VISIBLE int libwebsockets_serve_http_file( p += sprintf((char *)p, "Content-Length: %lu\x0d\x0a\x0d\x0a", wsi->u.http.filelen); - ret = libwebsocket_write(wsi, context->service_buffer, - p - context->service_buffer, LWS_WRITE_HTTP); - if (ret != (p - context->service_buffer)) { - lwsl_err("_write returned %d from %d\n", ret, (p - context->service_buffer)); + ret = libwebsocket_write(wsi, response, + p - response, LWS_WRITE_HTTP_HEADERS); + if (ret != (p - response)) { + lwsl_err("_write returned %d from %d\n", ret, (p - response)); return -1; } @@ -931,28 +973,15 @@ int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, /* * we were accepting input but now we stopped doing so */ - if (!(wsi->u.ws.rxflow_change_to & LWS_RXFLOW_ALLOW)) { - /* his RX is flowcontrolled, don't send remaining now */ - if (!wsi->u.ws.rxflow_buffer) { - /* a new rxflow, buffer it and warn caller */ - lwsl_info("new rxflow input buffer len %d\n", - len - n); - wsi->u.ws.rxflow_buffer = - (unsigned char *)malloc(len - n); - wsi->u.ws.rxflow_len = len - n; - wsi->u.ws.rxflow_pos = 0; - memcpy(wsi->u.ws.rxflow_buffer, - buf + n, len - n); - } else - /* rxflow while we were spilling prev rxflow */ - lwsl_info("stalling in existing rxflow buf\n"); + if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { + lws_rxflow_cache(wsi, buf, n, len); return 1; } /* account for what we're using in rxflow buffer */ - if (wsi->u.ws.rxflow_buffer) - wsi->u.ws.rxflow_pos++; + if (wsi->rxflow_buffer) + wsi->rxflow_pos++; /* process the byte */ m = libwebsocket_rx_sm(wsi, buf[n++]); diff --git a/lib/service.c b/lib/service.c index 9238967..160bda4 100644 --- a/lib/service.c +++ b/lib/service.c @@ -48,9 +48,26 @@ lws_handle_POLLOUT_event(struct libwebsocket_context *context, return -1; /* retry closing now */ } + /* protocol packets are next */ + if (wsi->pps) { + lwsl_err("servicing pps %d\n", wsi->pps); + switch (wsi->pps) { + case LWS_PPS_HTTP2_MY_SETTINGS: + case LWS_PPS_HTTP2_ACK_SETTINGS: + lws_http2_do_pps_send(context, wsi); + break; + default: + break; + } + wsi->pps = LWS_PPS_NONE; + libwebsocket_rx_flow_control(wsi, 1); + + return 0; /* leave POLLOUT active */ + } + /* pending control packets have next priority */ - if (wsi->u.ws.ping_payload_len) { + if (wsi->state == WSI_STATE_ESTABLISHED && wsi->u.ws.ping_payload_len) { n = libwebsocket_write(wsi, &wsi->u.ws.ping_payload_buf[ LWS_SEND_BUFFER_PRE_PADDING], @@ -160,8 +177,10 @@ user_service: /* one shot */ if (pollfd) { - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failled at set pollfd\n"); return 1; + } lws_libev_io(context, wsi, LWS_EV_STOP | LWS_EV_WRITE); } @@ -207,6 +226,25 @@ libwebsocket_service_timeout_check(struct libwebsocket_context *context, return 0; } +int lws_rxflow_cache(struct libwebsocket *wsi, unsigned char *buf, int n, int len) +{ + /* his RX is flowcontrolled, don't send remaining now */ + if (wsi->rxflow_buffer) { + /* rxflow while we were spilling prev rxflow */ + lwsl_info("stalling in existing rxflow buf\n"); + return 1; + } + + /* a new rxflow, buffer it and warn caller */ + lwsl_info("new rxflow input buffer len %d\n", len - n); + wsi->rxflow_buffer = (unsigned char *)malloc(len - n); + wsi->rxflow_len = len - n; + wsi->rxflow_pos = 0; + memcpy(wsi->rxflow_buffer, buf + n, len - n); + + return 0; +} + /** * libwebsocket_service_fd() - Service polled socket with something waiting * @context: Websocket context @@ -370,25 +408,25 @@ libwebsocket_service_fd(struct libwebsocket_context *context, case LWS_CONNMODE_WS_SERVING: case LWS_CONNMODE_WS_CLIENT: + case LWS_CONNMODE_HTTP2_SERVING: /* the guy requested a callback when it was OK to write */ if ((pollfd->revents & LWS_POLLOUT) && - (wsi->state == WSI_STATE_ESTABLISHED || + (wsi->state == WSI_STATE_ESTABLISHED || wsi->state == WSI_STATE_HTTP2_ESTABLISHED || wsi->state == WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS || wsi->state == WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE) && lws_handle_POLLOUT_event(context, wsi, pollfd)) { lwsl_info("libwebsocket_service_fd: closing\n"); goto close_and_handled; } - if (wsi->u.ws.rxflow_buffer && - (wsi->u.ws.rxflow_change_to & LWS_RXFLOW_ALLOW)) { + if (wsi->rxflow_buffer && + (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { lwsl_info("draining rxflow\n"); /* well, drain it */ - eff_buf.token = (char *)wsi->u.ws.rxflow_buffer + - wsi->u.ws.rxflow_pos; - eff_buf.token_len = wsi->u.ws.rxflow_len - - wsi->u.ws.rxflow_pos; + eff_buf.token = (char *)wsi->rxflow_buffer + + wsi->rxflow_pos; + eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; draining_flow = 1; goto drain; } @@ -458,11 +496,11 @@ drain: eff_buf.token_len = 0; } while (more); - if (draining_flow && wsi->u.ws.rxflow_buffer && - wsi->u.ws.rxflow_pos == wsi->u.ws.rxflow_len) { + if (draining_flow && wsi->rxflow_buffer && + wsi->rxflow_pos == wsi->rxflow_len) { lwsl_info("flow buffer: drained\n"); - free(wsi->u.ws.rxflow_buffer); - wsi->u.ws.rxflow_buffer = NULL; + free(wsi->rxflow_buffer); + wsi->rxflow_buffer = NULL; /* having drained the rxflow buffer, can rearm POLLIN */ n = _libwebsocket_rx_flow_control(wsi); /* n ignored, needed for NO_SERVER case */ } @@ -471,9 +509,6 @@ drain: goto read_pending; break; - case LWS_CONNMODE_HTTP2_SERVING: - break; - default: #ifdef LWS_NO_CLIENT break; diff --git a/test-server/test-server.c b/test-server/test-server.c index 984c43a..b89d014 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -221,7 +221,7 @@ static int callback_http(struct libwebsocket_context *context, return -1; } - /* this server has no concept of directories */ + /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { libwebsockets_return_http_status(context, wsi, HTTP_STATUS_FORBIDDEN, NULL); @@ -241,7 +241,7 @@ static int callback_http(struct libwebsocket_context *context, /* well, let's demonstrate how to send the hard way */ - p = buffer; + p = buffer + LWS_SEND_BUFFER_PRE_PADDING; #ifdef WIN32 pss->fd = open(leaf_path, O_RDONLY | _O_BINARY); @@ -264,7 +264,7 @@ static int callback_http(struct libwebsocket_context *context, "HTTP/1.0 200 OK\x0d\x0a" "Server: libwebsockets\x0d\x0a" "Content-Type: image/jpeg\x0d\x0a" - "Content-Length: %u\x0d\x0a\x0d\x0a", + "Content-Length: %u\x0d\x0a\x0d\x0a", (unsigned int)stat_buf.st_size); /* @@ -274,8 +274,10 @@ static int callback_http(struct libwebsocket_context *context, * (too small for partial) */ - n = libwebsocket_write(wsi, buffer, - p - buffer, LWS_WRITE_HTTP); + n = libwebsocket_write(wsi, + buffer + LWS_SEND_BUFFER_PRE_PADDING, + p - (buffer + LWS_SEND_BUFFER_PRE_PADDING), + LWS_WRITE_HTTP_HEADERS); if (n < 0) { close(pss->fd); @@ -367,7 +369,8 @@ static int callback_http(struct libwebsocket_context *context, */ do { - n = read(pss->fd, buffer, sizeof buffer); + n = read(pss->fd, buffer + LWS_SEND_BUFFER_PRE_PADDING, + sizeof (buffer) - LWS_SEND_BUFFER_PRE_PADDING); /* problem reading, close conn */ if (n < 0) goto bail; @@ -375,10 +378,11 @@ static int callback_http(struct libwebsocket_context *context, if (n == 0) goto flush_bail; /* - * because it's HTTP and not websocket, don't need to take - * care about pre and postamble + * To support HTTP2, must take care about preamble space */ - m = libwebsocket_write(wsi, buffer, n, LWS_WRITE_HTTP); + m = libwebsocket_write(wsi, + buffer + LWS_SEND_BUFFER_PRE_PADDING, + n, LWS_WRITE_HTTP); if (m < 0) /* write failed, close conn */ goto bail;