From ac1229f2f7f27104df4893c4affdbd708066be5b Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 30 Jan 2020 13:19:11 +0000 Subject: [PATCH] minimal-http-client-multi: add POST This adds support for POST in both h1 and h2 queues / stream binding. The previous queueing tried to keep the "leader" wsi who made the actual connection around and have it act on the transaction queue tail if it had done its own thing. This refactors it so instead, who is the "leader" moves down the queue and the queued guys inherit the fd, SSL * and queue from the old leader as they take over. This lets them operate in their own wsi identity directly and gets rid of all the "effective wsi" checks, which was applied incompletely and getting out of hand considering the separate lws_mux checks for h2 and other muxed protocols alongside it. This change also allows one wsi at a time to own the transaction for POST. --post is added as an option to lws-minimal-http-client-multi and 6 extra selftests with POST on h1/h2, pipelined or not and staggered or not are added to the CI. --- lib/core-net/README.md | 58 ++++ lib/core-net/output.c | 2 +- lib/core-net/pollfd.c | 7 +- lib/core-net/private-lib-core-net.h | 128 +++++---- lib/core-net/service.c | 12 +- lib/core-net/vhost.c | 25 +- lib/core-net/wsi.c | 140 ++++++++-- lib/roles/h1/ops-h1.c | 12 +- lib/roles/h2/http2.c | 8 +- lib/roles/h2/ops-h2.c | 13 +- lib/roles/http/client/client-handshake.c | 9 +- lib/roles/http/client/client-http.c | 248 ++++++------------ lib/roles/http/parsers.c | 2 +- lib/roles/http/private-lib-roles-http.h | 2 + lib/roles/raw-proxy/ops-raw-proxy.c | 2 +- lib/roles/ws/ops-ws.c | 2 +- lib/tls/mbedtls/mbedtls-client.c | 2 +- lib/tls/mbedtls/wrapper/library/ssl_lib.c | 25 +- lib/tls/tls-network.c | 4 +- .../minimal-http-client-multi/README.md | 3 + .../minimal-http-client-multi.c | 147 ++++++++--- .../minimal-http-client-multi/selftest.sh | 8 +- 22 files changed, 523 insertions(+), 336 deletions(-) create mode 100644 lib/core-net/README.md diff --git a/lib/core-net/README.md b/lib/core-net/README.md new file mode 100644 index 000000000..f7c794f67 --- /dev/null +++ b/lib/core-net/README.md @@ -0,0 +1,58 @@ +# Implementation background + +## Client connection Queueing + +By default lws treats each client connection as completely separate, and each is +made from scratch with its own network connection independently. + +If the user code sets the `LCCSCF_PIPELINE` bit on `info.ssl_connection` when +creating the client connection though, lws attempts to optimize multiple client +connections to the same place by sharing any existing connection and its tls +tunnel where possible. + +There are two basic approaches, for h1 additional connections of the same type +and endpoint basically queue on a leader and happen sequentially. + +For muxed protocols like h2, they may also queue if the initial connection is +not up yet, but subsequently the will all join the existing connection +simultaneously "broadside". + +## h1 queueing + +The initial wsi to start the network connection becomes the "leader" that +subsequent connection attempts will queue against. Each vhost has a dll2_owner +`wsi->dll_cli_active_conns_owner` that "leaders" who are actually making network +connections themselves can register on as "active client connections". + +Other client wsi being created who find there is already a leader on the active +client connection list for the vhost, can join their dll2 wsi->dll2_cli_txn_queue +to the leader's wsi->dll2_cli_txn_queue_owner to "queue" on the leader. + +The user code does not know which wsi was first or is queued, it just waits for +stuff to happen the same either way. + +When the "leader" wsi connects, it performs its client transaction as normal, +and at the end arrives at `lws_http_transaction_completed_client()`. Here, it +calls through to the lws_mux `_lws_generic_transaction_completed_active_conn()` +helper. This helper sees if anything else is queued, and if so, migrates assets +like the SSL *, the socket fd, and any remaining queue from the original leader +to the head of the list, which replaces the old leader as the "active client +connection" any subsequent connects would queue on. + +It has to be done this way so that user code which may know each client wsi by +its wsi, or have marked it with an opaque_user_data pointer, is getting its +specific request handled by the wsi it expects it to be handled by. + +A side effect of this, and in order to be able to handle POSTs cleanly, lws +does not attempt to send the headers for the next queued child before the +previous child has finished. + +The process of moving the SSL context and fd etc between the queued wsi continues +until the queue is all handled. + +## muxed protocol queueing and stream binding + +h2 connections act the same as h1 before the initial connection has been made, +but once it is made all the queued connections join the network connection as +child mux streams immediately, "broadside", binding the stream to the existing +network connection. diff --git a/lib/core-net/output.c b/lib/core-net/output.c index fcf64d3ed..fcb6a5a0e 100644 --- a/lib/core-net/output.c +++ b/lib/core-net/output.c @@ -94,7 +94,7 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) return 0; if (!wsi->mux_substream && !lws_socket_is_valid(wsi->desc.sockfd)) - lwsl_warn("** error invalid sock but expected to send\n"); + lwsl_err("%s: invalid sock %p\n", __func__, wsi); /* limit sending */ if (wsi->protocol->tx_packet_size) diff --git a/lib/core-net/pollfd.c b/lib/core-net/pollfd.c index 9624b24aa..c9728edca 100644 --- a/lib/core-net/pollfd.c +++ b/lib/core-net/pollfd.c @@ -502,6 +502,7 @@ int lws_callback_on_writable(struct lws *wsi) { struct lws_context_per_thread *pt; + struct lws *w = wsi; if (lwsi_state(wsi) == LRS_SHUTDOWN) return 0; @@ -527,16 +528,16 @@ lws_callback_on_writable(struct lws *wsi) if (wsi->role_ops->callback_on_writable) { if (wsi->role_ops->callback_on_writable(wsi)) return 1; - wsi = lws_get_network_wsi(wsi); + w = lws_get_network_wsi(wsi); } - if (wsi->position_in_fds_table == LWS_NO_FDS_POS) { + if (w->position_in_fds_table == LWS_NO_FDS_POS) { lwsl_debug("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd); return -1; } - if (__lws_change_pollfd(wsi, 0, LWS_POLLOUT)) + if (__lws_change_pollfd(w, 0, LWS_POLLOUT)) return -1; return 1; diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 55a5dde19..fa9157208 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -856,21 +856,21 @@ lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt); const struct lws_role_ops * lws_role_by_name(const char *name); -LWS_EXTERN int +int lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, const char *iface, int ipv6_allowed); #if defined(LWS_WITH_IPV6) -LWS_EXTERN unsigned long +unsigned long lws_get_addr_scope(const char *ipaddr); #endif -LWS_EXTERN void +void lws_close_free_wsi(struct lws *wsi, enum lws_close_status, const char *caller); -LWS_EXTERN void +void __lws_close_free_wsi(struct lws *wsi, enum lws_close_status, const char *caller); -LWS_EXTERN void +void __lws_free_wsi(struct lws *wsi); #if LWS_MAX_SMP > 1 @@ -917,61 +917,61 @@ lws_pt_stats_unlock(struct lws_context_per_thread *pt) #define lws_context_init_extensions(_a, _b) #endif -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_client_interpret_server_handshake(struct lws *wsi); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len); -LWS_EXTERN void +void lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state, const struct lws_role_ops *ops); int lws_http_to_fallback(struct lws *wsi, unsigned char *buf, size_t len); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); -LWS_EXTERN int +int lws_plat_set_nonblocking(lws_sockfd_type fd); -LWS_EXTERN int +int lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd, int unix_skt); -LWS_EXTERN int +int lws_plat_check_connection_error(struct lws *wsi); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_header_table_attach(struct lws *wsi, int autoservice); -LWS_EXTERN int +int lws_header_table_detach(struct lws *wsi, int autoservice); -LWS_EXTERN int +int __lws_header_table_detach(struct lws *wsi, int autoservice); -LWS_EXTERN void +void lws_header_table_reset(struct lws *wsi, int autoservice); void __lws_header_table_reset(struct lws *wsi, int autoservice); -LWS_EXTERN char * LWS_WARN_UNUSED_RESULT +char * LWS_WARN_UNUSED_RESULT lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_ensure_user_space(struct lws *wsi); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_change_pollfd(struct lws *wsi, int _and, int _or); #if defined(LWS_WITH_SERVER) @@ -990,7 +990,7 @@ lws_change_pollfd(struct lws *wsi, int _and, int _or); #define lws_server_get_canonical_hostname(_a, _b) #endif -LWS_EXTERN int +int __remove_wsi_socket_from_fds(struct lws *wsi); enum { @@ -1004,32 +1004,32 @@ enum { int _lws_plat_service_forced_tsi(struct lws_context *context, int tsi); -LWS_EXTERN int +int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len); -LWS_EXTERN int +int lws_service_flag_pending(struct lws_context *context, int tsi); static LWS_INLINE int lws_has_buffered_out(struct lws *wsi) { return !!wsi->buflist_out; } -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_ws_client_rx_sm(struct lws *wsi, unsigned char c); -LWS_EXTERN lws_parser_return_t LWS_WARN_UNUSED_RESULT +lws_parser_return_t LWS_WARN_UNUSED_RESULT lws_parse(struct lws *wsi, unsigned char *buf, int *len); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_parse_urldecode(struct lws *wsi, uint8_t *_c); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_http_action(struct lws *wsi); -LWS_EXTERN void +void __lws_close_free_wsi_final(struct lws *wsi); -LWS_EXTERN void +void lws_libuv_closehandle(struct lws *wsi); -LWS_EXTERN int +int lws_libuv_check_watcher_active(struct lws *wsi); LWS_VISIBLE LWS_EXTERN int @@ -1089,63 +1089,61 @@ void lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs); #endif -LWS_EXTERN int +int __lws_timed_callback_remove(struct lws_vhost *vh, struct lws_timed_vh_protocol *p); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT __insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len); -LWS_EXTERN lws_usec_t +lws_usec_t __lws_seq_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow); -LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +struct lws * LWS_WARN_UNUSED_RESULT lws_client_connect_2_dnsreq(struct lws *wsi); LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT lws_client_reset(struct lws **wsi, int ssl, const char *address, int port, const char *path, const char *host, char weak); -LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +struct lws * LWS_WARN_UNUSED_RESULT lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi); -LWS_EXTERN char * LWS_WARN_UNUSED_RESULT +char * LWS_WARN_UNUSED_RESULT lws_generate_client_handshake(struct lws *wsi, char *pkt); -LWS_EXTERN int +int lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd); -LWS_EXTERN struct lws * +struct lws * lws_http_client_connect_via_info2(struct lws *wsi); #if defined(LWS_WITH_CLIENT) -LWS_EXTERN int lws_client_socket_service(struct lws *wsi, - struct lws_pollfd *pollfd, - struct lws *wsi_conn); -LWS_EXTERN struct lws * -lws_client_wsi_effective(struct lws *wsi); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int +lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd); + +int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed_client(struct lws *wsi); #if !defined(LWS_WITH_TLS) #define lws_context_init_client_ssl(_a, _b) (0) #endif -LWS_EXTERN void +void lws_decode_ssl_error(void); #else #define lws_context_init_client_ssl(_a, _b) (0) #endif -LWS_EXTERN int +int __lws_rx_flow_control(struct lws *wsi); -LWS_EXTERN int +int _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa); #if defined(LWS_WITH_SERVER) -LWS_EXTERN int +int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); #else #define lws_server_socket_service(_b, _c) (0) @@ -1153,9 +1151,9 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); #endif #ifdef LWS_WITH_ACCESS_LOG -LWS_EXTERN int +int lws_access_log(struct lws *wsi); -LWS_EXTERN void +void lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int len, int meth); #else #define lws_access_log(_a) @@ -1207,35 +1205,35 @@ lws_plat_pipe_close(struct lws *wsi); void lws_addrinfo_clean(struct lws *wsi); -LWS_EXTERN void +void lws_add_wsi_to_draining_ext_list(struct lws *wsi); -LWS_EXTERN void +void lws_remove_wsi_from_draining_ext_list(struct lws *wsi); -LWS_EXTERN int +int lws_poll_listen_fd(struct lws_pollfd *fd); -LWS_EXTERN int +int lws_plat_service(struct lws_context *context, int timeout_ms); -LWS_EXTERN LWS_VISIBLE int +LWS_VISIBLE int _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi); -LWS_EXTERN int +int lws_pthread_self_to_tsi(struct lws_context *context); -LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT +const char * LWS_WARN_UNUSED_RESULT lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_plat_inet_pton(int af, const char *src, void *dst); -LWS_EXTERN void +void lws_same_vh_protocol_remove(struct lws *wsi); -LWS_EXTERN void +void __lws_same_vh_protocol_remove(struct lws *wsi); -LWS_EXTERN void +void lws_same_vh_protocol_insert(struct lws *wsi, int n); void lws_seq_destroy_all_on_pt(struct lws_context_per_thread *pt); -LWS_EXTERN int +int lws_broadcast(struct lws_context_per_thread *pt, int reason, void *in, size_t len); #if defined(LWS_WITH_STATS) @@ -1320,7 +1318,7 @@ lws_async_dns_deinit(lws_async_dns_t *dns); int lws_protocol_init_vhost(struct lws_vhost *vh, int *any); int -_lws_generic_transaction_completed_active_conn(struct lws *wsi); +_lws_generic_transaction_completed_active_conn(struct lws **wsi); #define ACTIVE_CONNS_SOLO 0 #define ACTIVE_CONNS_MUXED 1 diff --git a/lib/core-net/service.c b/lib/core-net/service.c index 6490d00a2..db8791ce2 100644 --- a/lib/core-net/service.c +++ b/lib/core-net/service.c @@ -450,11 +450,13 @@ lws_buflist_aware_finished_consuming(struct lws *wsi, struct lws_tokens *ebuf, return 0; if (used && buffered) { - m = (int)lws_buflist_use_segment(&wsi->buflist, (size_t)used); - // lwsl_notice("%s: used %d, next %d\n", __func__, used, m); - // lws_buflist_describe(&wsi->buflist, wsi, __func__); - if (m) - return 0; + if (wsi->buflist) { + m = (int)lws_buflist_use_segment(&wsi->buflist, (size_t)used); + // lwsl_notice("%s: used %d, next %d\n", __func__, used, m); + // lws_buflist_describe(&wsi->buflist, wsi, __func__); + if (m) + return 0; + } lwsl_info("%s: removed %p from dll_buflist\n", __func__, wsi); lws_dll2_remove(&wsi->dll_buflist); diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 77a07b313..2c801f4dc 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -1444,15 +1444,25 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin) */ if (w->client_h2_alpn && w->client_mux_migrated && (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS || - lwsi_state(w) == LRS_ESTABLISHED)) { + lwsi_state(w) == LRS_ESTABLISHED || + lwsi_state(w) == LRS_IDLING)) { - lwsl_info("%s: just join h2 directly\n", - __func__); + lwsl_notice("%s: just join h2 directly 0x%x\n", + __func__, lwsi_state(w)); + + if (lwsi_state(w) == LRS_IDLING) { + // lwsi_set_state(w, LRS_ESTABLISHED); + _lws_generic_transaction_completed_active_conn(&w); + } + + //lwsi_set_state(w, LRS_H1C_ISSUE_HANDSHAKE2); wsi->client_h2_alpn = 1; lws_wsi_h2_adopt(w, wsi); lws_vhost_unlock(wsi->vhost); /* } ---------- */ + *nwsi = w; + return ACTIVE_CONNS_MUXED; } #endif @@ -1461,11 +1471,16 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin) wsi, w, (unsigned long)w->wsistate); /* * ...let's add ourselves to his transaction queue... - * we are adding ourselves at the HEAD + * we are adding ourselves at the TAIL */ - lws_dll2_add_head(&wsi->dll2_cli_txn_queue, + lws_dll2_add_tail(&wsi->dll2_cli_txn_queue, &w->dll2_cli_txn_queue_owner); + if (lwsi_state(w) == LRS_IDLING) { + // lwsi_set_state(w, LRS_ESTABLISHED); + _lws_generic_transaction_completed_active_conn(&w); + } + /* * h1: pipeline our headers out on him, * and wait for our turn at client transaction_complete diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index ca9a6a4e1..ddf389609 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -759,9 +759,9 @@ lws_get_context(const struct lws *wsi) #if defined(LWS_WITH_CLIENT) int -_lws_generic_transaction_completed_active_conn(struct lws *wsi) +_lws_generic_transaction_completed_active_conn(struct lws **_wsi) { - struct lws *wsi_eff = lws_client_wsi_effective(wsi); + struct lws *wnew, *wsi = *_wsi; /* * Are we constitutionally capable of having a queue, ie, we are on @@ -773,34 +773,25 @@ _lws_generic_transaction_completed_active_conn(struct lws *wsi) if (lws_dll2_is_detached(&wsi->dll_cli_active_conns)) return 0; /* no new transaction */ - /* if this was a queued guy, close him and remove from queue */ - - if (wsi->transaction_from_pipeline_queue) { - lwsl_debug("closing queued wsi %p\n", wsi_eff); - /* so the close doesn't trigger a CCE */ - wsi_eff->already_did_cce = 1; - __lws_close_free_wsi(wsi_eff, - LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE, - "queued client done"); - } - - /* after the first one, they can only be coming from the queue */ - wsi->transaction_from_pipeline_queue = 1; - - wsi->hdr_parsing_completed = 0; - - /* is there a new tail after removing that one? */ - wsi_eff = lws_client_wsi_effective(wsi); - /* - * Do we have something pipelined waiting? - * it's OK if he hasn't managed to send his headers yet... he's next - * in line to do that... + * With h1 queuing, the original "active client" moves his attributes + * like fd, ssl, queue and active client list entry to the next guy in + * the queue before closing... it's because the user code knows the + * individual wsi and the action must take place in the correct wsi + * context. Note this means we don't truly pipeline headers. + * + * Trying to keep the original "active client" in place to do the work + * of the wsi breaks down when dealing with queued POSTs otherwise; it's + * also competing with the real mux child arrangements and complicating + * the code. + * + * For that reason, see if we have any queued child now... */ - if (wsi_eff == wsi) { + + if (!wsi->dll2_cli_txn_queue_owner.head) { /* * Nothing pipelined... we should hang around a bit - * in case something turns up... + * in case something turns up... otherwise we'll close */ lwsl_info("%s: nothing pipelined waiting\n", __func__); lwsi_set_state(wsi, LRS_IDLING); @@ -810,6 +801,96 @@ _lws_generic_transaction_completed_active_conn(struct lws *wsi) return 0; /* no new transaction right now */ } + /* + * We have a queued child wsi we should bequeath our assets to, before + * closing ourself + */ + + lws_vhost_lock(wsi->vhost); + + wnew = lws_container_of(wsi->dll2_cli_txn_queue_owner.head, struct lws, + dll2_cli_txn_queue); + + assert(wsi != wnew); + + lws_dll2_remove(&wnew->dll2_cli_txn_queue); + + assert(lws_socket_is_valid(wsi->desc.sockfd)); + + /* copy the fd */ + wnew->desc = wsi->desc; + + assert(lws_socket_is_valid(wnew->desc.sockfd)); + + /* disconnect the fd from association with old wsi */ + + if (__remove_wsi_socket_from_fds(wsi)) + return -1; + wsi->desc.sockfd = LWS_SOCK_INVALID; + + /* point the fd table entry to new guy */ + + assert(lws_socket_is_valid(wnew->desc.sockfd)); + + if (__insert_wsi_socket_into_fds(wsi->context, wnew)) + return -1; + +#if defined(LWS_WITH_TLS) + /* pass on the tls */ + + wnew->tls = wsi->tls; + wsi->tls.client_bio = NULL; + wsi->tls.ssl = NULL; + wsi->tls.use_ssl = 0; +#endif + + /* take over his copy of his endpoint as an active connection */ + + wnew->cli_hostname_copy = wsi->cli_hostname_copy; + wsi->cli_hostname_copy = NULL; + + + /* + * selected queued guy now replaces the original leader on the + * active client conn list + */ + + lws_dll2_remove(&wsi->dll_cli_active_conns); + lws_dll2_add_tail(&wnew->dll_cli_active_conns, + &wsi->vhost->dll_cli_active_conns_owner); + + /* move any queued guys to queue on new active conn */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + wsi->dll2_cli_txn_queue_owner.head) { + struct lws *ww = lws_container_of(d, struct lws, + dll2_cli_txn_queue); + + lws_dll2_remove(&ww->dll2_cli_txn_queue); + lws_dll2_add_tail(&ww->dll2_cli_txn_queue, + &wnew->dll2_cli_txn_queue_owner); + + } lws_end_foreach_dll_safe(d, d1); + + lws_vhost_unlock(wsi->vhost); + + /* + * The original leader who passed on all his powers already can die... + * in the call stack above us there are guys who still want to touch + * him, so have him die next time around the event loop, not now. + */ + + wsi->already_did_cce = 1; /* so the close doesn't trigger a CCE */ + lws_set_timeout(wsi, 1, LWS_TO_KILL_ASYNC); + + /* after the first one, they can only be coming from the queue */ + wnew->transaction_from_pipeline_queue = 1; + + lwsl_notice("%s: pipeline queue passed wsi %p on to queued wsi %p\n", + __func__, wsi, wnew); + + *_wsi = wnew; /* inform caller we swapped */ + return 1; /* new transaction */ } #endif @@ -1095,7 +1176,8 @@ lws_wsi_mux_mark_parents_needing_writeable(struct lws *wsi) wsi2 = wsi; while (wsi2) { wsi2->mux.requested_POLLOUT = 1; - lwsl_info("mark %p pending writable\n", wsi2); + lwsl_info("%s: mark %p (sid %u) pending writable\n", __func__, + wsi2, wsi2->mux.my_sid); wsi2 = wsi2->mux.parent_wsi; } @@ -1237,9 +1319,11 @@ lws_wsi_mux_apply_queue(struct lws *wsi) dll2_cli_txn_queue); if (lwsi_role_http(wsi) && - lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2) { + lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS) { lwsl_info("%s: cli pipeq %p to be h2\n", __func__, w); + lwsi_set_state(w, LRS_H1C_ISSUE_HANDSHAKE2); + /* remove ourselves from client queue */ lws_dll2_remove(&w->dll2_cli_txn_queue); diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c index 3199feaaf..b64397179 100644 --- a/lib/roles/h1/ops-h1.c +++ b/lib/roles/h1/ops-h1.c @@ -688,7 +688,7 @@ rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi, return LWS_HPI_RET_PLEASE_CLOSE_ME; } - if (lws_client_socket_service(wsi, pollfd, NULL)) + if (lws_client_socket_service(wsi, pollfd)) return LWS_HPI_RET_WSI_ALREADY_DIED; #endif @@ -1095,16 +1095,14 @@ static int rops_close_kill_connection_h1(struct lws *wsi, enum lws_close_status reason) { #if defined(LWS_WITH_HTTP_PROXY) - struct lws *wsi_eff = lws_client_wsi_effective(wsi); - - if (!wsi_eff->http.proxy_clientside) + if (!wsi->http.proxy_clientside) return 0; - wsi_eff->http.proxy_clientside = 0; + wsi->http.proxy_clientside = 0; - if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff, + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, - wsi_eff->user_space, NULL, 0)) + wsi->user_space, NULL, 0)) return 0; #endif return 0; diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 432f54dd4..de9a8fe5d 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -314,6 +314,11 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi) #endif wsi->h2.initialized = 1; + if (!wsi->mux.my_sid) { + wsi->mux.my_sid = nwsi->h2.h2n->highest_sid; + nwsi->h2.h2n->highest_sid += 2; + } + lws_wsi_mux_insert(wsi, parent_wsi, wsi->mux.my_sid); wsi->txc.tx_cr = nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE]; @@ -2392,7 +2397,8 @@ lws_h2_client_handshake(struct lws *wsi) #if defined(LWS_WITH_CLIENT) /* below is not needed in spec, indeed it destroys the long poll * feature, but required by nghttp2 */ - if (wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM) + if ((wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM) && + !(wsi->client_http_body_pending)) m |= LWS_WRITE_H2_STREAM_END; #endif diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index 673c580f1..19ebb0957 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -132,7 +132,7 @@ rops_handle_POLLIN_h2(struct lws_context_per_thread *pt, struct lws *wsi, return LWS_HPI_RET_PLEASE_CLOSE_ME; } - n = lws_client_socket_service(wsi, pollfd, NULL); + n = lws_client_socket_service(wsi, pollfd); if (n) return LWS_HPI_RET_WSI_ALREADY_DIED; #endif @@ -647,14 +647,13 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason) #if defined(LWS_WITH_HTTP_PROXY) if (wsi->http.proxy_clientside) { - struct lws *wsi_eff = lws_client_wsi_effective(wsi); wsi->http.proxy_clientside = 0; - if (user_callback_handle_rxflow(wsi_eff->protocol->callback, - wsi_eff, + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, - wsi_eff->user_space, NULL, 0)) + wsi->user_space, NULL, 0)) wsi->http.proxy_clientside = 0; } #endif @@ -732,12 +731,14 @@ rops_callback_on_writable_h2(struct lws *wsi) /* is this for DATA or for control messages? */ if (wsi->upgraded_to_http2 && !wsi->h2.h2n->pps && - lws_wsi_txc_check_skint(&wsi->txc, lws_h2_tx_cr_get(wsi))) + lws_wsi_txc_check_skint(&wsi->txc, lws_h2_tx_cr_get(wsi))) { /* * refuse his efforts to get WRITABLE if we have no credit and * no non-DATA pps to send */ + lwsl_err("%s: skint\n", __func__); return 0; + } #if defined(LWS_WITH_CLIENT) network_wsi = lws_get_network_wsi(wsi); diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index 3ccbfa198..08f5c48d8 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -166,9 +166,12 @@ send_hs: /* * We are pipelining on an already-established connection... * we can skip tls establishment. + * + * Set these queued guys to a state where they won't actually + * send their headers until we decide later. */ - lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); + lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); /* * we can't send our headers directly, because they have to @@ -760,12 +763,14 @@ lws_client_connect_2_dnsreq(struct lws *wsi) case ACTIVE_CONNS_MUXED: lwsl_notice("%s: ACTIVE_CONNS_MUXED\n", __func__); if (lwsi_role_h2(wsi)) { + if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, wsi->user_space, NULL, 0)) goto failed1; - lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); + //lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); + //lwsi_set_state(w, LRS_ESTABLISHED); lws_callback_on_writable(wsi); } diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index 9d542172a..ad99acc6d 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2019 Andy Green + * Copyright (C) 2010 - 2020 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -30,48 +30,12 @@ lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) wsi->client_http_body_pending = !!something_left_to_send; } -/* - * return self, or queued client wsi we are acting on behalf of - * - * That is the TAIL of the queue (new queue elements are added at the HEAD) - */ - -struct lws * -lws_client_wsi_effective(struct lws *wsi) -{ - struct lws_dll2 *tail = lws_dll2_get_tail(&wsi->dll2_cli_txn_queue_owner); - - if (!wsi->transaction_from_pipeline_queue || !tail) - return wsi; - - return lws_container_of(tail, struct lws, dll2_cli_txn_queue); -} - -/* - * return self or the guy we are queued under - * - * REQUIRES VHOST LOCK HELD - */ - -static struct lws * -_lws_client_wsi_master(struct lws *wsi) -{ - struct lws_dll2_owner *o = wsi->dll2_cli_txn_queue.owner; - - if (!o) - return wsi; - - return lws_container_of(o, struct lws, dll2_cli_txn_queue_owner); -} - int -lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, - struct lws *wsi_conn) +lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; - struct lws *w; #if defined(LWS_WITH_TLS) char ebuf[128]; #endif @@ -83,61 +47,6 @@ lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, ssize_t len; #endif - if ((pollfd->revents & LWS_POLLOUT) && - wsi->keepalive_active && - wsi->dll2_cli_txn_queue_owner.head) { - struct lws *wfound = NULL; - - lwsl_debug("%s: pollout HANDSHAKE2\n", __func__); - - /* - * We have a transaction / stream queued that wants to pipeline. - * - * We have to allow it to send headers strictly in the order - * that it was queued, ie, tail-first. - */ - lws_vhost_lock(wsi->vhost); - lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, - wsi->dll2_cli_txn_queue_owner.head) { - struct lws *w = lws_container_of(d, struct lws, - dll2_cli_txn_queue); - - lwsl_debug("%s: %p states 0x%lx\n", __func__, w, - (unsigned long)w->wsistate); - if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2) - wfound = w; - } lws_end_foreach_dll_safe(d, d1); - - if (wfound) { -#if defined(LWS_WITH_DETAILED_LATENCY) - wfound->detlat.earliest_write_req_pre_write = lws_now_usecs(); -#endif - /* - * pollfd has the nwsi sockfd in it... but we're a - * logical child stream sharing that... recurse with - * both the correct child stream wsi and the nwsi. - * - * Second time around wsi / child stream wsi is not - * going to trigger this as it has no pipelined queue - * children of its own - */ - if (lws_client_socket_service(wfound, pollfd, wsi) < 0) { - /* closed */ - - lws_vhost_unlock(wsi->vhost); - - return -1; - } - - lws_callback_on_writable(wsi); - } else - lwsl_debug("%s: nothing in txn q in HS2\n", __func__); - - lws_vhost_unlock(wsi->vhost); - - return 0; - } - switch (lwsi_state(wsi)) { case LRS_WAITING_DNS: @@ -427,16 +336,14 @@ start_ws_handshake: /* send our request to the server */ - w = _lws_client_wsi_master(wsi); - lwsl_info("%s: HANDSHAKE2: %p: sending headers on %p " - "(wsistate 0x%lx 0x%lx), w sock %d, wsi sock %d\n", - __func__, wsi, w, (unsigned long)wsi->wsistate, - (unsigned long)w->wsistate, w->desc.sockfd, + lwsl_info("%s: HANDSHAKE2: %p: sending headers " + "(wsistate 0x%lx), w sock %d\n", + __func__, wsi, (unsigned long)wsi->wsistate, wsi->desc.sockfd); #if defined(LWS_WITH_DETAILED_LATENCY) wsi->detlat.earliest_write_req_pre_write = lws_now_usecs(); #endif - n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb)); + n = lws_ssl_capable_write(wsi, (unsigned char *)sb, (int)(p - sb)); switch (n) { case LWS_SSL_CAPABLE_ERROR: lwsl_debug("ERROR writing to client socket\n"); @@ -468,14 +375,14 @@ start_ws_handshake: lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); wsi->hdr_parsing_completed = 0; - if (lwsi_state(w) == LRS_IDLING) { - lwsi_set_state(w, LRS_WAITING_SERVER_REPLY); - w->hdr_parsing_completed = 0; + if (lwsi_state(wsi) == LRS_IDLING) { + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + wsi->hdr_parsing_completed = 0; #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) - w->http.ah->parser_state = WSI_TOKEN_NAME_PART; - w->http.ah->lextable_pos = 0; + wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; + wsi->http.ah->lextable_pos = 0; #if defined(LWS_WITH_CUSTOM_HEADERS) - w->http.ah->unk_pos = 0; + wsi->http.ah->unk_pos = 0; #endif /* If we're (re)starting on hdr, need other implied init */ wsi->http.ah->ues = URIES_IDLE; @@ -485,7 +392,7 @@ start_ws_handshake: lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, wsi->context->timeout_secs); - lws_callback_on_writable(w); + lws_callback_on_writable(wsi); goto client_http_body_sent; @@ -629,21 +536,25 @@ bail3: int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed_client(struct lws *wsi) { - struct lws *wsi_eff = lws_client_wsi_effective(wsi); int n; - lwsl_info("%s: wsi: %p, wsi_eff: %p (%s)\n", __func__, wsi, wsi_eff, - wsi_eff->protocol->name); + lwsl_info("%s: wsi: %p (%s)\n", __func__, wsi, wsi->protocol->name); - if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff, + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, - wsi_eff->user_space, NULL, 0)) { + wsi->user_space, NULL, 0)) { lwsl_debug("%s: Completed call returned nonzero (role 0x%lx)\n", - __func__, (unsigned long)lwsi_role(wsi_eff)); + __func__, (unsigned long)lwsi_role(wsi)); return -1; } - n = _lws_generic_transaction_completed_active_conn(wsi); + wsi->http.rx_content_length = 0; + + /* + * For h1, wsi may pass some assets on to a queued child and be + * destroyed during this. + */ + n = _lws_generic_transaction_completed_active_conn(&wsi); if (wsi->http.ah) { if (wsi->client_mux_substream) @@ -654,9 +565,9 @@ lws_http_transaction_completed_client(struct lws *wsi) */ __lws_header_table_detach(wsi, 0); else - _lws_header_table_reset(wsi->http.ah); + if (!n) + _lws_header_table_reset(wsi->http.ah); } - wsi->http.rx_content_length = 0; if (!n || !wsi->http.ah) return 0; @@ -680,30 +591,21 @@ lws_http_transaction_completed_client(struct lws *wsi) /* If we're (re)starting on headers, need other implied init */ wsi->http.ah->ues = URIES_IDLE; + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); - lwsl_info("%s: %p: new queued transaction as %p\n", __func__, wsi, - wsi_eff); + lwsl_info("%s: %p: new queued transaction\n", __func__, wsi); lws_callback_on_writable(wsi); return 0; } unsigned int -lws_http_client_http_response(struct lws *_wsi) +lws_http_client_http_response(struct lws *wsi) { - struct lws *wsi; - unsigned int resp = 0; + if (wsi->http.ah && wsi->http.ah->http_response) + return wsi->http.ah->http_response; - if (_wsi->http.ah && _wsi->http.ah->http_response) - return _wsi->http.ah->http_response; - - lws_vhost_lock(_wsi->vhost); - wsi = _lws_client_wsi_master(_wsi); - if (wsi->http.ah) - resp = wsi->http.ah->http_response; - lws_vhost_unlock(_wsi->vhost); - - return resp; + return 0; } #endif @@ -722,7 +624,6 @@ lws_client_interpret_server_handshake(struct lws *wsi) int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; const char *prot, *ads = NULL, *path, *cce = NULL; struct allocated_headers *ah, *ah1; - struct lws *w = lws_client_wsi_effective(wsi); struct lws *nwsi = lws_get_network_wsi(wsi); char *p, *q; char new_path[300]; @@ -918,8 +819,8 @@ lws_client_interpret_server_handshake(struct lws *wsi) /* if h1 KA is allowed, enable the queued pipeline guys */ - if (!wsi->client_h2_alpn && !wsi->client_mux_substream && - w == wsi) { /* ie, coming to this for the first time */ + if (!wsi->client_h2_alpn && !wsi->client_mux_substream) { + /* ie, coming to this for the first time */ if (wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE) wsi->keepalive_active = 1; else { @@ -1016,12 +917,12 @@ lws_client_interpret_server_handshake(struct lws *wsi) * we seem to be good to go, give client last chance to check * headers and OK it */ - ah1 = w->http.ah; - w->http.ah = ah; - if (w->protocol->callback(w, + ah1 = wsi->http.ah; + wsi->http.ah = ah; + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, - w->user_space, NULL, 0)) { - w->http.ah = ah1; + wsi->user_space, NULL, 0)) { + wsi->http.ah = ah1; cce = "HS: disallowed by client filter"; goto bail2; } @@ -1032,24 +933,15 @@ lws_client_interpret_server_handshake(struct lws *wsi) wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* call him back to inform him he is up */ - if (w->protocol->callback(w, + if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, - w->user_space, NULL, 0)) { - w->http.ah = ah1; + wsi->user_space, NULL, 0)) { + wsi->http.ah = ah1; cce = "HS: disallowed at ESTABLISHED"; goto bail3; } - w->http.ah = ah1; - - /* - * for pipelining, master needs to keep his ah... guys who - * queued on him can drop it now though. - */ - - if (w != wsi) - /* free up parsing allocations for queued guy */ - lws_header_table_detach(w, 0); + wsi->http.ah = ah1; lwsl_info("%s: client connection up\n", __func__); @@ -1404,13 +1296,15 @@ spin_chunks: lwsl_err("%s: chunking failure B\n", __func__); return -1; } - wsi->chunk_parser = ELCP_CONTENT; - //lwsl_info("starting chunk size %d (block rem %d)\n", - // wsi->chunk_remaining, *len); - if (wsi->chunk_remaining) + if (wsi->chunk_remaining) { + wsi->chunk_parser = ELCP_CONTENT; + //lwsl_notice("starting chunk size %d (block rem %d)\n", + // wsi->chunk_remaining, *len); break; - lwsl_info("final chunk\n"); - goto completed; + } + + wsi->chunk_parser = ELCP_TRAILER_CR; + break; case ELCP_CONTENT: break; @@ -1436,6 +1330,32 @@ spin_chunks: wsi->chunk_parser = ELCP_HEX; wsi->chunk_remaining = 0; break; + + case ELCP_TRAILER_CR: + if ((*buf)[0] != '\x0d') { + lwsl_err("%s: chunking failure F\n", __func__); + lwsl_hexdump_err(*buf, *len); + + return -1; + } + + wsi->chunk_parser = ELCP_TRAILER_LF; + break; + + case ELCP_TRAILER_LF: + if ((*buf)[0] != '\x0a') { + lwsl_err("%s: chunking failure F\n", __func__); + lwsl_hexdump_err(*buf, *len); + + return -1; + } + + (*buf)++; + (*len)--; + consumed++; + + lwsl_info("final chunk\n"); + goto completed; } (*buf)++; (*len)--; @@ -1462,19 +1382,17 @@ spin_chunks: else #endif { - struct lws *wsi_eff = lws_client_wsi_effective(wsi); - if ( #if defined(LWS_WITH_HTTP_PROXY) - !wsi_eff->protocol_bind_balance == - !!wsi_eff->http.proxy_clientside + !wsi->protocol_bind_balance == + !!wsi->http.proxy_clientside #else - !!wsi_eff->protocol_bind_balance + !!wsi->protocol_bind_balance #endif ) { - if (user_callback_handle_rxflow(wsi_eff->protocol->callback, - wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, - wsi_eff->user_space, *buf, n)) { + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi->user_space, *buf, n)) { lwsl_info("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n", __func__); diff --git a/lib/roles/http/parsers.c b/lib/roles/http/parsers.c index 91a26a622..94e8490bd 100644 --- a/lib/roles/http/parsers.c +++ b/lib/roles/http/parsers.c @@ -1412,7 +1412,7 @@ set_parsing_complete: return LPR_OK; forbid: - lwsl_notice(" forbidding on uri sanitation\n"); + lwsl_info(" forbidding on uri sanitation\n"); #if defined(LWS_WITH_SERVER) lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); #endif diff --git a/lib/roles/http/private-lib-roles-http.h b/lib/roles/http/private-lib-roles-http.h index 5f3de66ae..082da28dc 100644 --- a/lib/roles/http/private-lib-roles-http.h +++ b/lib/roles/http/private-lib-roles-http.h @@ -283,6 +283,8 @@ enum lws_chunk_parser { ELCP_CONTENT, ELCP_POST_CR, ELCP_POST_LF, + ELCP_TRAILER_CR, + ELCP_TRAILER_LF }; #endif diff --git a/lib/roles/raw-proxy/ops-raw-proxy.c b/lib/roles/raw-proxy/ops-raw-proxy.c index 606c5a56d..c99346546 100644 --- a/lib/roles/raw-proxy/ops-raw-proxy.c +++ b/lib/roles/raw-proxy/ops-raw-proxy.c @@ -111,7 +111,7 @@ try_pollout: } #if defined(LWS_WITH_CLIENT) - if (lws_client_socket_service(wsi, pollfd, NULL)) + if (lws_client_socket_service(wsi, pollfd)) return LWS_HPI_RET_WSI_ALREADY_DIED; #endif diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index 6bdcb8ec0..c56cf5246 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -989,7 +989,7 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi, return LWS_HPI_RET_PLEASE_CLOSE_ME; } - n = lws_client_socket_service(wsi, pollfd, NULL); + n = lws_client_socket_service(wsi, pollfd); if (n) return LWS_HPI_RET_WSI_ALREADY_DIED; #endif diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c index ec0443d4a..fa9aed751 100644 --- a/lib/tls/mbedtls/mbedtls-client.c +++ b/lib/tls/mbedtls/mbedtls-client.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2019 Andy Green + * Copyright (C) 2010 - 2020 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/lib/tls/mbedtls/wrapper/library/ssl_lib.c b/lib/tls/mbedtls/wrapper/library/ssl_lib.c index 825be826f..5e3e9edb8 100644 --- a/lib/tls/mbedtls/wrapper/library/ssl_lib.c +++ b/lib/tls/mbedtls/wrapper/library/ssl_lib.c @@ -1660,21 +1660,22 @@ _openssl_alpn_to_mbedtls(struct alpn_ctx *ac, char ***palpn_protos) /* find out how many entries he gave us */ - len = *p++; - while (p - ac->data < ac->len) { - if (len--) { - p++; - continue; - } - count++; + if (ac->len) { len = *p++; - if (!len) - break; + if (len) + count++; + while (p - ac->data < ac->len) { + if (len--) { + p++; + continue; + } + len = *p++; + if (!len) + break; + count++; + } } - if (!len) - count++; - if (!count) return; diff --git a/lib/tls/tls-network.c b/lib/tls/tls-network.c index 95261ebd2..c5ad8fb02 100644 --- a/lib/tls/tls-network.c +++ b/lib/tls/tls-network.c @@ -229,7 +229,7 @@ lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len) if (!comma) return 0; - while (*comma && len > 1) { + while (*comma && len > 2) { if (!plen && *comma == ' ') { comma++; continue; @@ -252,6 +252,8 @@ lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len) if (plen) *plen = lws_ptr_diff(os, plen + 1); + *os = 0; + return lws_ptr_diff(os, oos); } diff --git a/minimal-examples/http-client/minimal-http-client-multi/README.md b/minimal-examples/http-client/minimal-http-client-multi/README.md index 12b1701dd..784e5cbb0 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/README.md +++ b/minimal-examples/http-client/minimal-http-client-multi/README.md @@ -25,3 +25,6 @@ Option|Meaning --uv|Use libuv event loop if lws built for it --event|Use libevent event loop if lws built for it --ev|Use libev event loop if lws built for it +--post|POST to the server rather than GET +-c|Create n connections (n can be 1 .. 8) +--path |Force the URL path (should start with /) \ No newline at end of file diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index a144347b8..8c6580694 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -40,23 +40,33 @@ struct cliuser { int index; }; -static int completed, failed, numbered, stagger_idx; +static int completed, failed, numbered, stagger_idx, posting, count = COUNT; static lws_sorted_usec_list_t sul_stagger; static struct lws_client_connect_info i; static struct lws *client_wsi[COUNT]; -struct lws_context *context; +static char urlpath[64]; +static struct lws_context *context; + +/* we only need this for tracking POST emit state */ + +struct pss { + char body_part; +}; static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - int idx = (int)(long)lws_get_opaque_user_data(wsi); + char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - LWS_PRE - 1]; + int n, idx = (int)(long)lws_get_opaque_user_data(wsi); + struct pss *pss = (struct pss *)user; switch (reason) { case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: - lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: resp %u\n", - lws_http_client_http_response(wsi)); + lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: idx: %d, resp %u\n", + idx, lws_http_client_http_response(wsi)); break; /* because we are protocols[0] ... */ @@ -70,19 +80,21 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, /* chunks of chunked content, with header removed */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n", idx, (int)len); -#if 0 /* enable to dump the html */ - { - const char *p = in; - - while (len--) - if (*p < 0x7f) - putchar(*p++); - else - putchar('.'); - } -#endif + lwsl_hexdump_info(in, len); return 0; /* don't passthru */ + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + /* + * Tell lws we are going to send the body next... + */ + if (posting && !lws_http_is_redirected_to_get(wsi)) { + lwsl_user("%s: doing POST flow\n", __func__); + lws_client_http_body_pending(wsi, 1); + lws_callback_on_writable(wsi); + } else + lwsl_user("%s: doing GET flow\n", __func__); + break; + /* uninterpreted http content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { @@ -115,6 +127,65 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, } break; + case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: + if (!posting) + break; + if (lws_http_is_redirected_to_get(wsi)) + break; + lwsl_user("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: %p, part %d\n", wsi, pss->body_part); + n = LWS_WRITE_HTTP; + + /* + * For a small body like this, we could prepare it in memory and + * send it all at once. But to show how to handle, eg, + * arbitrary-sized file payloads, or huge form-data fields, the + * sending is done in multiple passes through the event loop. + */ + + switch (pss->body_part++) { + case 0: + if (lws_client_http_multipart(wsi, "text", NULL, NULL, + &p, end)) + return -1; + /* notice every usage of the boundary starts with -- */ + p += lws_snprintf(p, end - p, "my text field\xd\xa"); + break; + case 1: + if (lws_client_http_multipart(wsi, "file", "myfile.txt", + "text/plain", &p, end)) + return -1; + p += lws_snprintf(p, end - p, + "This is the contents of the " + "uploaded file.\xd\xa" + "\xd\xa"); + break; + case 2: + if (lws_client_http_multipart(wsi, NULL, NULL, NULL, + &p, end)) + return -1; + lws_client_http_body_pending(wsi, 0); + /* necessary to support H2, it means we will write no + * more on this stream */ + n = LWS_WRITE_HTTP_FINAL; + break; + + default: + /* + * We can get extra callbacks here, if nothing to do, + * then do nothing. + */ + return 0; + } + + if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n) + != lws_ptr_diff(p, start)) + return 1; + + if (n != LWS_WRITE_HTTP_FINAL) + lws_callback_on_writable(wsi); + + break; + default: break; } @@ -122,7 +193,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, return lws_callback_http_dummy(wsi, reason, user, in, len); finished: - if (++completed == COUNT) { + if (++completed == count) { if (!failed) lwsl_user("Done: all OK\n"); else @@ -140,7 +211,7 @@ finished: } static const struct lws_protocols protocols[] = { - { "http", callback_http, 0, 0, }, + { "http", callback_http, sizeof(struct pss), 0, }, { NULL, NULL, 0, 0 } }; @@ -205,14 +276,14 @@ lws_try_client_connection(struct lws_client_connect_info *i, int m) lws_snprintf(path, sizeof(path), "/%d.png", m + 1); i->path = path; } else - i->path = "/"; + i->path = urlpath; i->pwsi = &client_wsi[m]; i->opaque_user_data = (void *)(long)m; if (!lws_client_connect_via_info(i)) { failed++; - if (++completed == COUNT) { + if (++completed == count) { lwsl_user("Done: failed: %d\n", failed); lws_context_destroy(context); } @@ -233,11 +304,11 @@ stagger_cb(lws_sorted_usec_list_t *sul) */ lws_try_client_connection(&i, stagger_idx++); - if (stagger_idx == (int)LWS_ARRAY_SIZE(client_wsi)) + if (stagger_idx == count) return; next = 300 * LWS_US_PER_MS; - if (stagger_idx == (int)LWS_ARRAY_SIZE(client_wsi) - 1) + if (stagger_idx == count - 1) next += 700 * LWS_US_PER_MS; lws_sul_schedule(context, 0, &sul_stagger, stagger_cb, next); @@ -280,7 +351,7 @@ int main(int argc, const char **argv) lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http client [-s (staggered)] [-p (pipeline)]\n"); lwsl_user(" [--h1 (http/1 only)] [-l (localhost)] [-d ]\n"); - lwsl_user(" [-n (numbered)]\n"); + lwsl_user(" [-n (numbered)] [--post]\n"); info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; @@ -317,6 +388,13 @@ int main(int argc, const char **argv) LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; + if (lws_cmdline_option(argc, argv, "--post")) { + posting = 1; + i.method = "POST"; + i.ssl_connection |= LCCSCF_HTTP_MULTIPART_MIME; + } else + i.method = "GET"; + /* enables h1 or h2 connection sharing */ if (lws_cmdline_option(argc, argv, "-p")) i.ssl_connection |= LCCSCF_PIPELINE; @@ -325,13 +403,19 @@ int main(int argc, const char **argv) if (lws_cmdline_option(argc, argv, "--h1")) i.alpn = "http/1.1"; + strcpy(urlpath, "/"); + if (lws_cmdline_option(argc, argv, "-l")) { i.port = 7681; i.address = "localhost"; i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; + if (posting) + strcpy(urlpath, "/formtest"); } else { i.port = 443; - i.address = "warmcat.com"; + i.address = "libwebsockets.org"; + if (posting) + strcpy(urlpath, "/testserver/formtest"); } if (lws_cmdline_option(argc, argv, "-n")) @@ -343,12 +427,15 @@ int main(int argc, const char **argv) if ((p = lws_cmdline_option(argc, argv, "--port"))) i.port = atoi(p); - if ((p = lws_cmdline_option(argc, argv, "--server"))) - i.address = p; + if ((p = lws_cmdline_option(argc, argv, "--path"))) + lws_strncpy(urlpath, p, sizeof(urlpath)); + + if ((p = lws_cmdline_option(argc, argv, "-c"))) + if (atoi(p) <= COUNT && atoi(p)) + count = atoi(p); i.host = i.address; i.origin = i.address; - i.method = "GET"; i.protocol = protocols[0].name; if (!staggered) @@ -356,7 +443,7 @@ int main(int argc, const char **argv) * just pile on all the connections at once, testing the * pipeline queuing before the first is connected */ - for (m = 0; m < (int)LWS_ARRAY_SIZE(client_wsi); m++) + for (m = 0; m < count; m++) lws_try_client_connection(&i, m); else /* @@ -372,7 +459,7 @@ int main(int argc, const char **argv) lwsl_user("Duration: %lldms\n", (us() - start) / 1000); lws_context_destroy(context); - lwsl_user("Exiting with %d\n", failed || completed != COUNT); + lwsl_user("Exiting with %d\n", failed || completed != count); - return failed || completed != COUNT; + return failed || completed != count; } diff --git a/minimal-examples/http-client/minimal-http-client-multi/selftest.sh b/minimal-examples/http-client/minimal-http-client-multi/selftest.sh index 49d61a1b1..7a0a5580e 100755 --- a/minimal-examples/http-client/minimal-http-client-multi/selftest.sh +++ b/minimal-examples/http-client/minimal-http-client-multi/selftest.sh @@ -18,7 +18,7 @@ . $5/selftests-library.sh -COUNT_TESTS=16 +COUNT_TESTS=22 dotest $1 $2 warmcat dotest $1 $2 warmcat-pipe -p @@ -28,6 +28,12 @@ dotest $1 $2 warmcat-stag -s dotest $1 $2 warmcat-pipe-stag -p -s dotest $1 $2 warmcat-h1-stag --h1 -s dotest $1 $2 warmcat-h1-pipe-stag --h1 -p -s +dotest $1 $2 warmcat-post --post +dotest $1 $2 warmcat-post-pipe --post -p +dotest $1 $2 warmcat-post-pipe-stag --post -p -s +dotest $1 $2 warmcat-h1-post --post --h1 +dotest $1 $2 warmcat-h1-post-pipe --post --h1 -p +dotest $1 $2 warmcat-h1-post-pipe-stag --post --h1 -p -s spawn "" $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls dotest $1 $2 localhost -l