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