static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
	      void *in, size_t len)
{
	uint8_t buf[LWS_PRE + 256], *start = &buf[LWS_PRE], *p = start,
		*end = &buf[sizeof(buf) - 1];
	const char *val;
	int n;

	switch (reason) {
	case LWS_CALLBACK_HTTP:

		if (!lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI))
			/* not a GET */
			break;
		if (strcmp((const char *)in, "/form1"))
			/* not our form URL */
			break;

		/* we just dump the decoded things to the log */

		for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
			val = lws_get_urlarg_by_name(wsi, param_names[n],
					(char *)buf, sizeof(buf));
			if (!val)
				lwsl_user("%s: undefined\n", param_names[n]);
			else
				lwsl_user("%s: (len %d) '%s'\n", param_names[n],
					  (int)strlen((const char *)buf),buf);
		}

		/*
		 * Our response is to redirect to a static page.  We could
		 * have generated a dynamic html page here instead.
		 */

		if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
				      (unsigned char *)"after-form1.html",
				      16, &p, end) < 0)
			return -1;
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static int
callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct pss *pss = (struct pss *)user;
	int n;

	switch (reason) {

	case LWS_CALLBACK_PROTOCOL_INIT:
		goto try;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
			 in ? (char *)in : "(null)");
		client_wsi = NULL;
		lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
				lws_get_protocol(wsi), LWS_CALLBACK_USER, 1);
		break;

	/* --- client callbacks --- */

	case LWS_CALLBACK_CLIENT_ESTABLISHED:
		lwsl_user("%s: established\n", __func__);
		lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC);
		break;

	case LWS_CALLBACK_CLIENT_WRITEABLE:
		if (pss->send_a_ping) {
			uint8_t ping[LWS_PRE + 125];
			int m;

			pss->send_a_ping = 0;
			n = 0;
			if (!zero_length_ping)
				n = lws_snprintf((char *)ping + LWS_PRE, 125,
					"ping body!");

			lwsl_user("Sending PING %d...\n", n);

			m = lws_write(wsi, ping + LWS_PRE, n, LWS_WRITE_PING);
			if (m < n) {
				lwsl_err("sending ping failed: %d\n", m);

				return -1;
			}
			
			lws_callback_on_writable(wsi);
		}
		break;

	case LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL:
		client_wsi = NULL;
		lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
					       lws_get_protocol(wsi),
					       LWS_CALLBACK_USER, 1);
		break;

	case LWS_CALLBACK_CLIENT_RECEIVE_PONG:
		lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE_PONG\n");
		lwsl_hexdump_notice(in, len);
		break;

	case LWS_CALLBACK_TIMER:
		/* we want to send a ws PING every few seconds */
		pss->send_a_ping = 1;
		lws_callback_on_writable(wsi);
		lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC);
		break;

	/* rate-limited client connect retries */

	case LWS_CALLBACK_USER:
		lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
try:
		if (connect_client())
			lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
						       lws_get_protocol(wsi),
						       LWS_CALLBACK_USER, 1);
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}

static const struct lws_protocols protocols[] = {
	{
		"lws-ping-test",
		callback_minimal_broker,
		sizeof(struct pss),
		0,
	},
	{ NULL, NULL, 0, 0 }
};

static void
sigint_handler(int sig)
{
	interrupted = 1;
}

int main(int argc, const char **argv)
{
	struct lws_context_creation_info info;
	const char *p;
	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
			/* for LLL_ verbosity above NOTICE to be built into lws,
			 * lws must have been configured and built with
			 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
			/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
			/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
			/* | LLL_DEBUG */;

	signal(SIGINT, sigint_handler);

	if ((p = lws_cmdline_option(argc, argv, "-d")))
		logs = atoi(p);

	lws_set_log_level(logs, NULL);
	lwsl_user("LWS minimal ws client PING\n");

	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
	info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
	info.protocols = protocols;
#if defined(LWS_WITH_MBEDTLS)
	/*
	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
	 * CA to trust explicitly.
	 */
	info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
#endif

	if (lws_cmdline_option(argc, argv, "-z"))
		zero_length_ping = 1;

	if ((p = lws_cmdline_option(argc, argv, "--protocol")))
		pro = p;

	if ((p = lws_cmdline_option(argc, argv, "--server"))) {
		server_address = p;
		pro = "lws-minimal";
		ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
	}

	if ((p = lws_cmdline_option(argc, argv, "--port")))
		port = atoi(p);

	/*
	 * since we know this lws context is only ever going to be used with
	 * one client wsis / fds / sockets at a time, let lws know it doesn't
	 * have to use the default allocations for fd tables up to ulimit -n.
	 * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
	 * will use.
	 */
	info.fd_limit_per_thread = 1 + 1 + 1;

	context = lws_create_context(&info);
	if (!context) {
		lwsl_err("lws init failed\n");
		return 1;
	}

	while (n >= 0 && !interrupted)
		n = lws_service(context, 1000);

	lws_context_destroy(context);
	lwsl_user("Completed\n");

	return 0;
}
static int
callback_dynamic_http(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct pss *pss = (struct pss *)user;
	uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
		*end = &buf[sizeof(buf) - LWS_PRE - 1];
	time_t t;
	int n;

	switch (reason) {
	case LWS_CALLBACK_HTTP:

		/* in contains the url part after our mountpoint /dyn, if any */
		lws_snprintf(pss->path, sizeof(pss->path), "%s", (const char *)in);

		/*
		 * prepare and write http headers... with regards to content-
		 * length, there are three approaches:
		 *
		 *  - http/1.0 or connection:close: no need, but no pipelining
		 *  - http/1.1 or connected:keep-alive
		 *     (keep-alive is default for 1.1): content-length required
		 *  - http/2: no need, LWS_WRITE_HTTP_FINAL closes the stream
		 *
		 * giving the api below LWS_ILLEGAL_HTTP_CONTENT_LEN instead of
		 * a content length forces the connection response headers to
		 * send back "connection: close", disabling keep-alive.
		 *
		 * If you know the final content-length, it's always OK to give
		 * it and keep-alive can work then if otherwise possible.  But
		 * often you don't know it and avoiding having to compute it
		 * at header-time makes life easier at the server.
		 */
		if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
				"text/html",
				LWS_ILLEGAL_HTTP_CONTENT_LEN, /* no content len */
				&p, end))
			return 1;
		if (lws_finalize_write_http_header(wsi, start, &p, end))
			return 1;

		pss->times = 0;
		pss->budget = atoi((char *)in + 1);
		pss->content_lines = 0;
		if (!pss->budget)
			pss->budget = 10;

		/* write the body separately */
		lws_callback_on_writable(wsi);

		return 0;

	case LWS_CALLBACK_HTTP_WRITEABLE:

		if (!pss || pss->times > pss->budget)
			break;

		/*
		 * We send a large reply in pieces of around 2KB each.
		 *
		 * For http/1, it's possible to send a large buffer at once,
		 * but lws will malloc() up a temp buffer to hold any data
		 * that the kernel didn't accept in one go.  This is expensive
		 * in memory and cpu, so it's better to stage the creation of
		 * the data to be sent each time.
		 *
		 * For http/2, large data frames would block the whole
		 * connection, not just the stream and are not allowed.  Lws
		 * will call back on writable when the stream both has transmit
		 * credit and the round-robin fair access for sibling streams
		 * allows it.
		 *
		 * For http/2, we must send the last part with
		 * LWS_WRITE_HTTP_FINAL to close the stream representing
		 * this transaction.
		 */
		n = LWS_WRITE_HTTP;
		if (pss->times == pss->budget)
			n = LWS_WRITE_HTTP_FINAL;

		if (!pss->times) {
			/*
			 * the first time, we print some html title
			 */
			t = time(NULL);
			/*
			 * to work with http/2, we must take care about LWS_PRE
			 * valid behind the buffer we will send.
			 */
			p += lws_snprintf((char *)p, end - p, "<html>"
				"<img src=\"/libwebsockets.org-logo.png\">"
				"<br>Dynamic content for '%s' from mountpoint."
				"<br>Time: %s<br><br>"
				"</html>", pss->path, ctime(&t));
		} else {
			/*
			 * after the first time, we create bulk content.
			 *
			 * Again we take care about LWS_PRE valid behind the
			 * buffer we will send.
			 */

			while (lws_ptr_diff(end, p) > 80)
				p += lws_snprintf((char *)p, end - p,
					"%d.%d: this is some content... ",
					pss->times, pss->content_lines++);

			p += lws_snprintf((char *)p, end - p, "<br><br>");
		}

		pss->times++;
		if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n) !=
				lws_ptr_diff(p, start))
			return 1;

		/*
		 * HTTP/1.0 no keepalive: close network connection
		 * HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction
		 * HTTP/2: stream ended, parent connection remains up
		 */
		if (n == LWS_WRITE_HTTP_FINAL) {
		    if (lws_http_transaction_completed(wsi))
			return -1;
		} else
			lws_callback_on_writable(wsi);

		return 0;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason,
	      void *user, void *in, size_t len)
{
	switch (reason) {

	/* because we are protocols[0] ... */
	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
			 in ? (char *)in : "(null)");
		client_wsi = NULL;
		break;

	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
		status = lws_http_client_http_response(wsi);
		lwsl_user("Connected with server response: %d\n", status);
		break;

	/* chunks of chunked content, with header removed */
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
		lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
#if 0  /* enable to dump the html */
		{
			const char *p = in;

			while (len--)
				if (*p < 0x7f)
					putchar(*p++);
				else
					putchar('.');
		}
#endif
		return 0; /* don't passthru */

	/* uninterpreted http content */
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
		{
			char buffer[1024 + LWS_PRE];
			char *px = buffer + LWS_PRE;
			int lenx = sizeof(buffer) - LWS_PRE;

			if (lws_http_client_read(wsi, &px, &lenx) < 0)
				return -1;
		}
		return 0; /* don't passthru */

	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
		lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
		client_wsi = NULL;
		bad = status != 200;
		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
		break;

	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
		client_wsi = NULL;
		bad = status != 200;
		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
	      void *in, size_t len)
{
	struct pss *pss = (struct pss *)user;
	uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
		*p = start, *end = &buf[sizeof(buf) - 1];
	int n;

	switch (reason) {
	case LWS_CALLBACK_HTTP:

		/*
		 * Manually report that our form target URL exists
		 *
		 * you can also do this by adding a mount for the form URL
		 * to the protocol with type LWSMPRO_CALLBACK, then no need
		 * to trap LWS_CALLBACK_HTTP.
		 */

		if (!strcmp((const char *)in, "/form1"))
			/* assertively allow it to exist in the URL space */
			return 0;

		/* default to 404-ing the URL if not mounted */
		break;

	case LWS_CALLBACK_HTTP_BODY:

		/* create the POST argument parser if not already existing */

		if (!pss->spa) {
			pss->spa = lws_spa_create(wsi, param_names,
					LWS_ARRAY_SIZE(param_names), 1024,
					NULL, NULL); /* no file upload */
			if (!pss->spa)
				return -1;
		}

		/* let it parse the POST data */

		if (lws_spa_process(pss->spa, in, (int)len))
			return -1;
		break;

	case LWS_CALLBACK_HTTP_BODY_COMPLETION:

		/* inform the spa no more payload data coming */

		lwsl_user("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
		lws_spa_finalize(pss->spa);

		/* we just dump the decoded things to the log */

		for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
			if (!lws_spa_get_string(pss->spa, n))
				lwsl_user("%s: undefined\n", param_names[n]);
			else
				lwsl_user("%s: (len %d) '%s'\n",
				    param_names[n],
				    lws_spa_get_length(pss->spa, n),
				    lws_spa_get_string(pss->spa, n));
		}

		/*
		 * Our response is to redirect to a static page.  We could
		 * have generated a dynamic html page here instead.
		 */

		if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
				      (unsigned char *)"after-form1.html",
				      16, &p, end) < 0)
			return -1;
		break;

	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
		/* called when our wsi user_space is going to be destroyed */
		if (pss->spa) {
			lws_spa_destroy(pss->spa);
			pss->spa = NULL;
		}
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
Example #6
0
static int
callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct per_vhost_data__minimal *vhd =
			(struct per_vhost_data__minimal *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	const struct msg *pmsg;
	void *retval;
	int n, m, r = 0;

	switch (reason) {

	/* --- protocol lifecycle callbacks --- */

	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__minimal));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);

		vhd->ring = lws_ring_create(sizeof(struct msg), 8,
					    __minimal_destroy_message);
		if (!vhd->ring)
			return 1;

		pthread_mutex_init(&vhd->lock_ring, NULL);

		/* start the content-creating threads */

		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
			if (pthread_create(&vhd->pthread_spam[n], NULL,
					   thread_spam, vhd)) {
				lwsl_err("thread creation failed\n");
				r = 1;
				goto init_fail;
			}

		if (connect_client(vhd))
			lws_timed_callback_vh_protocol(vhd->vhost,
					vhd->protocol, LWS_CALLBACK_USER, 1);
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
init_fail:
		vhd->finished = 1;
		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
			if (vhd->pthread_spam[n])
				pthread_join(vhd->pthread_spam[n], &retval);

		if (vhd->ring)
			lws_ring_destroy(vhd->ring);

		pthread_mutex_destroy(&vhd->lock_ring);

		return r;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
			 in ? (char *)in : "(null)");
		vhd->client_wsi = NULL;
		lws_timed_callback_vh_protocol(vhd->vhost,
				vhd->protocol, LWS_CALLBACK_USER, 1);
		break;

	/* --- client callbacks --- */

	case LWS_CALLBACK_CLIENT_ESTABLISHED:
		lwsl_user("%s: established\n", __func__);
		vhd->established = 1;
		break;

	case LWS_CALLBACK_CLIENT_WRITEABLE:
		pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
		pmsg = lws_ring_get_element(vhd->ring, &vhd->tail);
		if (!pmsg)
			goto skip;

		/* notice we allowed for LWS_PRE in the payload already */
		m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
			      pmsg->len, LWS_WRITE_TEXT);
		if (m < (int)pmsg->len) {
			pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock */
			lwsl_err("ERROR %d writing to ws socket\n", m);
			return -1;
		}

		lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1);

		/* more to do for us? */
		if (lws_ring_get_element(vhd->ring, &vhd->tail))
			/* come back as soon as we can write more */
			lws_callback_on_writable(wsi);

skip:
		pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
		break;

	case LWS_CALLBACK_CLIENT_CLOSED:
		vhd->client_wsi = NULL;
		vhd->established = 0;
		lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
					       LWS_CALLBACK_USER, 1);
		break;

	case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
		/*
		 * When the "spam" threads add a message to the ringbuffer,
		 * they create this event in the lws service thread context
		 * using lws_cancel_service().
		 *
		 * We respond by scheduling a writable callback for the
		 * connected client, if any.
		 */
		if (vhd && vhd->client_wsi && vhd->established)
			lws_callback_on_writable(vhd->client_wsi);
		break;

	/* rate-limited client connect retries */

	case LWS_CALLBACK_USER:
		lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
		if (connect_client(vhd))
			lws_timed_callback_vh_protocol(vhd->vhost,
						vhd->protocol,
						LWS_CALLBACK_USER, 1);
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static int
callback_minimal_spam(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct pss *pss = (struct pss *)user;
	uint8_t ping[LWS_PRE + 125];
	int n, m;

	switch (reason) {

	case LWS_CALLBACK_PROTOCOL_INIT:
		for (n = 0; n < concurrent; n++) {
			clients[n].index = n;
			connect_client(n);
		}
		break;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		errors++;
		lwsl_err("CLIENT_CONNECTION_ERROR: %s (try %d, est %d, closed %d, err %d)\n",
			 in ? (char *)in : "(null)", tries, est, closed, errors);
		for (n = 0; n < concurrent; n++) {
			if (clients[n].wsi == wsi) {
				clients[n].wsi = NULL;
				clients[n].state = CLIENT_IDLE;
				connect_client(n);
				break;
			}
		}
		if (tries == closed + errors)
			interrupted = 1;
		break;

	/* --- client callbacks --- */

	case LWS_CALLBACK_CLIENT_ESTABLISHED:
		lwsl_user("%s: established (try %d, est %d, closed %d, err %d)\n",
				__func__, tries, est, closed, errors);
		est++;
		pss->conn = conn++;
		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_CLIENT_CLOSED:
		closed++;
		if (tries == closed + errors)
			interrupted = 1;
		if (tries == limit) {
			lwsl_user("%s: leaving CLOSED (try %d, est %d, sent %d, closed %d, err %d)\n",
					__func__, tries, est, sent, closed, errors);
			break;
		}

		for (n = 0; n < concurrent; n++) {
			if (clients[n].wsi == wsi) {
				connect_client(n);
				lwsl_user("%s: reopening (try %d, est %d, closed %d, err %d)\n",
						__func__, tries, est, closed, errors);
				break;
			}
		}
		if (n == concurrent)
			lwsl_user("CLOSED: can't find client wsi\n");
		break;

	case LWS_CALLBACK_CLIENT_WRITEABLE:
		n = lws_snprintf((char *)ping + LWS_PRE, sizeof(ping) - LWS_PRE,
					  "hello %d", pss->conn);

		m = lws_write(wsi, ping + LWS_PRE, n, LWS_WRITE_TEXT);
		if (m < n) {
			lwsl_err("sending ping failed: %d\n", m);

			return -1;
		}
		lws_set_timeout(wsi, 1, LWS_TO_KILL_ASYNC);
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason,
	      void *user, void *in, size_t len)
{
	char val[32];
	int n;

	switch (reason) {

	/* because we are protocols[0] ... */
	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
			 in ? (char *)in : "(null)");
		client_wsi = NULL;
		break;

	case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
	{
		 unsigned char **p = (unsigned char **)in, *end = (*p) + len;

		 /*
		  * How to send a custom header in the request to the server
		  */

		 if (lws_add_http_header_by_name(wsi,
				 (const unsigned char *)"dnt",
				 (const unsigned char *)"1", 1, p, end))
			 return -1;
		break;
	}

	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
		status = lws_http_client_http_response(wsi);
		lwsl_user("Connected with server response: %d\n", status);

		/*
		 * How to query custom headers (http 1.x only at the momemnt)
		 *
		 * warmcat.com sends a custom header "test-custom-header" for
		 * testing, it has the fixed value "hello".
		 */

		n = lws_hdr_custom_length(wsi, "test-custom-header:", 19);
		if (n < 0)
			lwsl_notice("%s: Can't find test-custom-header\n",
				    __func__);
		else {
			if (lws_hdr_custom_copy(wsi, val, sizeof(val),
						"test-custom-header:", 19) < 0)
				lwsl_notice("%s: custom header too long\n",
					    __func__);
			else
				lwsl_notice("%s: custom header: '%s'\n",
						__func__, val);
		}
		break;

	/* chunks of chunked content, with header removed */
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
		lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
#if 0  /* enable to dump the html */
		{
			const char *p = in;

			while (len--)
				if (*p < 0x7f)
					putchar(*p++);
				else
					putchar('.');
		}
#endif
		return 0; /* don't passthru */

	/* uninterpreted http content */
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
		{
			char buffer[1024 + LWS_PRE];
			char *px = buffer + LWS_PRE;
			int lenx = sizeof(buffer) - LWS_PRE;

			if (lws_http_client_read(wsi, &px, &lenx) < 0)
				return -1;
		}
		return 0; /* don't passthru */

	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
		lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
		client_wsi = NULL;
		bad = status != 200;
		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
		break;

	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
		client_wsi = NULL;
		bad = status != 200;
		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}