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

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.
This commit is contained in:
Andy Green 2020-01-30 13:19:11 +00:00
parent fddca26be0
commit ac1229f2f7
22 changed files with 523 additions and 336 deletions

58
lib/core-net/README.md Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}

View file

@ -1,7 +1,7 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
*
* 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__);

View file

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

View file

@ -283,6 +283,8 @@ enum lws_chunk_parser {
ELCP_CONTENT,
ELCP_POST_CR,
ELCP_POST_LF,
ELCP_TRAILER_CR,
ELCP_TRAILER_LF
};
#endif

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View file

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

View file

@ -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);
}

View file

@ -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<n>|Create n connections (n can be 1 .. 8)
--path <path>|Force the URL path (should start with /)

View file

@ -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 <logs>]\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;
}

View file

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