http service break into outer loop states
Previously we sat and looped to dump a file over http protocol. Actually that's a source of blocking to the other sockets being serviced. This patch breaks up the file service into a roundtrip around the poll() loop for each 512-byte packet. It doesn't make much difference if the server is idle, but if it's busy it makes sure everyone else is getting service while the file is sent. It doesn't try to optimize multiple users of the file or to keep the descriptor open, the point of this patch is to establish the breaking up of the file send action into the poll loop. On the user side, there are two differences: - context is now needed in the first argument to libwebsockets_serve_http_file() that's not too bad since we provide context in the callback. - file send is now asynchronous to the user code, you get a new callback coming in protocol 0 when it's done, LWS_CALLBACK_HTTP_FILE_COMPLETION libwebsockets-test-server is updated accordingly. Signed-off-by: Andy Green <andy.green@linaro.org>
This commit is contained in:
parent
a50dd1af40
commit
d280b6ecb3
7 changed files with 157 additions and 24 deletions
|
@ -549,6 +549,7 @@ libwebsocket_read(struct libwebsocket_context *context,
|
|||
size_t n;
|
||||
|
||||
switch (wsi->state) {
|
||||
case WSI_STATE_HTTP_ISSUING_FILE:
|
||||
case WSI_STATE_HTTP:
|
||||
wsi->state = WSI_STATE_HTTP_HEADERS;
|
||||
wsi->parser_state = WSI_TOKEN_NAME_PART;
|
||||
|
@ -692,6 +693,8 @@ libwebsocket_read(struct libwebsocket_context *context,
|
|||
goto bail;
|
||||
}
|
||||
|
||||
wsi->mode = LWS_CONNMODE_WS_SERVING;
|
||||
|
||||
lwsl_parser("accepted v%02d connection\n",
|
||||
wsi->ietf_spec_revision);
|
||||
|
||||
|
@ -717,6 +720,7 @@ libwebsocket_read(struct libwebsocket_context *context,
|
|||
|
||||
break;
|
||||
default:
|
||||
lwsl_err("libwebsocket_read: Unhandled state\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -770,7 +770,7 @@ libwebsocket_create_new_server_wsi(struct libwebsocket_context *context)
|
|||
|
||||
new_wsi->state = WSI_STATE_HTTP;
|
||||
new_wsi->name_buffer_pos = 0;
|
||||
new_wsi->mode = LWS_CONNMODE_WS_SERVING;
|
||||
new_wsi->mode = LWS_CONNMODE_HTTP_SERVING;
|
||||
|
||||
for (n = 0; n < WSI_TOKEN_COUNT; n++) {
|
||||
new_wsi->utf8_token[n].token = NULL;
|
||||
|
@ -1536,10 +1536,68 @@ libwebsocket_service_fd(struct libwebsocket_context *context,
|
|||
|
||||
wsi = wsi_from_fd(context, pollfd->fd);
|
||||
|
||||
if (wsi == NULL)
|
||||
if (wsi == NULL) {
|
||||
lwsl_debug("hm fd %d has NULL wsi\n", pollfd->fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (wsi->mode) {
|
||||
|
||||
case LWS_CONNMODE_HTTP_SERVING:
|
||||
|
||||
/* handle http headers coming in */
|
||||
|
||||
/* any incoming data ready? */
|
||||
|
||||
if (pollfd->revents & POLLIN) {
|
||||
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
if (wsi->ssl)
|
||||
len = SSL_read(wsi->ssl, buf, sizeof buf);
|
||||
else
|
||||
#endif
|
||||
len = recv(pollfd->fd, buf, sizeof buf, 0);
|
||||
|
||||
if (len < 0) {
|
||||
lwsl_debug("Socket read returned %d\n", len);
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
libwebsocket_close_and_free_session(context,
|
||||
wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
return 1;
|
||||
}
|
||||
if (!len) {
|
||||
libwebsocket_close_and_free_session(context, wsi,
|
||||
LWS_CLOSE_STATUS_NOSTATUS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = libwebsocket_read(context, wsi, buf, len);
|
||||
if (n < 0)
|
||||
/* we closed wsi */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* this handles POLLOUT for http serving fragments */
|
||||
|
||||
if (!(pollfd->revents & POLLOUT))
|
||||
break;
|
||||
|
||||
/* one shot */
|
||||
pollfd->events &= ~POLLOUT;
|
||||
|
||||
if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE)
|
||||
break;
|
||||
|
||||
if (libwebsockets_serve_http_file_fragment(context, wsi) < 0)
|
||||
libwebsocket_close_and_free_session(context, wsi,
|
||||
LWS_CLOSE_STATUS_NOSTATUS);
|
||||
else
|
||||
if (wsi->state == WSI_STATE_HTTP && wsi->protocol->callback)
|
||||
if (wsi->protocol->callback(context, wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space,
|
||||
wsi->filepath, wsi->filepos))
|
||||
libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
break;
|
||||
|
||||
case LWS_CONNMODE_SERVER_LISTENER:
|
||||
|
||||
/* pollin means a client has connected to us then */
|
||||
|
@ -2258,6 +2316,8 @@ libwebsocket_service(struct libwebsocket_context *context, int timeout_ms)
|
|||
if (n == 0) /* poll timeout */
|
||||
return 0;
|
||||
|
||||
|
||||
|
||||
if (n < 0) {
|
||||
/*
|
||||
lwsl_err("Listen Socket dead\n");
|
||||
|
|
|
@ -92,6 +92,7 @@ enum libwebsocket_callback_reasons {
|
|||
LWS_CALLBACK_CLIENT_WRITEABLE,
|
||||
LWS_CALLBACK_SERVER_WRITEABLE,
|
||||
LWS_CALLBACK_HTTP,
|
||||
LWS_CALLBACK_HTTP_FILE_COMPLETION,
|
||||
LWS_CALLBACK_BROADCAST,
|
||||
LWS_CALLBACK_FILTER_NETWORK_CONNECTION,
|
||||
LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
|
||||
|
@ -378,6 +379,9 @@ struct libwebsocket_extension;
|
|||
* total number of client connections allowed set
|
||||
* by MAX_CLIENTS.
|
||||
*
|
||||
* LWS_CALLBACK_HTTP_FILE_COMPLETION: a file requested to be send down
|
||||
* http link has completed.
|
||||
*
|
||||
* LWS_CALLBACK_CLIENT_WRITEABLE:
|
||||
* LWS_CALLBACK_SERVER_WRITEABLE: If you call
|
||||
* libwebsocket_callback_on_writable() on a connection, you will
|
||||
|
@ -730,8 +734,12 @@ libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, size_t len,
|
|||
enum libwebsocket_write_protocol protocol);
|
||||
|
||||
LWS_EXTERN int
|
||||
libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file,
|
||||
libwebsockets_serve_http_file(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, const char *file,
|
||||
const char *content_type);
|
||||
LWS_EXTERN int
|
||||
libwebsockets_serve_http_file_fragment(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi);
|
||||
|
||||
/* notice - you need the pre- and post- padding allocation for buf below */
|
||||
|
||||
|
|
|
@ -1988,19 +1988,22 @@ send_raw:
|
|||
* local files down the http link in a single step.
|
||||
*/
|
||||
|
||||
int libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file,
|
||||
int libwebsockets_serve_http_file(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, const char *file,
|
||||
const char *content_type)
|
||||
{
|
||||
int fd;
|
||||
struct stat stat_buf;
|
||||
char buf[512];
|
||||
char *p = buf;
|
||||
int n;
|
||||
|
||||
strncpy(wsi->filepath, file, sizeof wsi->filepath);
|
||||
wsi->filepath[sizeof(wsi->filepath) - 1] = '\0';
|
||||
|
||||
#ifdef WIN32
|
||||
fd = open(file, O_RDONLY | _O_BINARY);
|
||||
fd = open(wsi->filepath, O_RDONLY | _O_BINARY);
|
||||
#else
|
||||
fd = open(file, O_RDONLY);
|
||||
fd = open(wsi->filepath, O_RDONLY);
|
||||
#endif
|
||||
if (fd < 1) {
|
||||
p += sprintf(p, "HTTP/1.0 400 Bad\x0d\x0a"
|
||||
|
@ -2014,6 +2017,7 @@ int libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file,
|
|||
}
|
||||
|
||||
fstat(fd, &stat_buf);
|
||||
wsi->filelen = stat_buf.st_size;
|
||||
p += sprintf(p, "HTTP/1.0 200 OK\x0d\x0a"
|
||||
"Server: libwebsockets\x0d\x0a"
|
||||
"Content-Type: %s\x0d\x0a"
|
||||
|
@ -2023,20 +2027,53 @@ int libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file,
|
|||
|
||||
libwebsocket_write(wsi, (unsigned char *)buf, p - buf, LWS_WRITE_HTTP);
|
||||
|
||||
n = 1;
|
||||
while (n > 0) {
|
||||
n = read(fd, buf, 512);
|
||||
if (n <= 0)
|
||||
continue;
|
||||
libwebsocket_write(wsi, (unsigned char *)buf, n,
|
||||
LWS_WRITE_HTTP);
|
||||
}
|
||||
wsi->filepos = 0;
|
||||
libwebsocket_callback_on_writable(context, wsi);
|
||||
wsi->state = WSI_STATE_HTTP_ISSUING_FILE;
|
||||
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libwebsockets_serve_http_file_fragment(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi)
|
||||
{
|
||||
int fd;
|
||||
int ret = 0;
|
||||
char buf[512];
|
||||
int n;
|
||||
|
||||
#ifdef WIN32
|
||||
fd = open(wsi->filepath, O_RDONLY | _O_BINARY);
|
||||
#else
|
||||
fd = open(wsi->filepath, O_RDONLY);
|
||||
#endif
|
||||
if (fd < 1)
|
||||
return -1;
|
||||
|
||||
lseek(fd, wsi->filepos, SEEK_SET);
|
||||
|
||||
n = read(fd, buf, 512);
|
||||
if (n > 0) {
|
||||
libwebsocket_write(wsi, (unsigned char *)buf, n, LWS_WRITE_HTTP);
|
||||
wsi->filepos += n;
|
||||
}
|
||||
|
||||
if (n < 0)
|
||||
ret = -1;
|
||||
|
||||
if (n < 512 || wsi->filepos == wsi->filelen)
|
||||
wsi->state = WSI_STATE_HTTP;
|
||||
else
|
||||
if (!ret)
|
||||
libwebsocket_callback_on_writable(context, wsi);
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* libwebsockets_remaining_packet_payload() - Bytes to come before "overall"
|
||||
|
|
|
@ -203,6 +203,7 @@ enum lws_websocket_opcodes_07 {
|
|||
|
||||
enum lws_connection_states {
|
||||
WSI_STATE_HTTP,
|
||||
WSI_STATE_HTTP_ISSUING_FILE,
|
||||
WSI_STATE_HTTP_HEADERS,
|
||||
WSI_STATE_DEAD_SOCKET,
|
||||
WSI_STATE_ESTABLISHED,
|
||||
|
@ -245,6 +246,8 @@ enum lws_rx_parse_state {
|
|||
|
||||
|
||||
enum connection_mode {
|
||||
LWS_CONNMODE_HTTP_SERVING,
|
||||
|
||||
LWS_CONNMODE_WS_SERVING,
|
||||
LWS_CONNMODE_WS_CLIENT,
|
||||
|
||||
|
@ -385,6 +388,11 @@ struct libwebsocket {
|
|||
int use_ssl;
|
||||
#endif
|
||||
|
||||
/* http send file */
|
||||
char filepath[PATH_MAX];
|
||||
unsigned long filepos;
|
||||
unsigned long filelen;
|
||||
|
||||
void *user_space;
|
||||
};
|
||||
|
||||
|
|
|
@ -411,7 +411,8 @@ packet while not burdening the user code with any protocol knowledge.
|
|||
<h2>libwebsockets_serve_http_file - Send a file back to the client using http</h2>
|
||||
<i>int</i>
|
||||
<b>libwebsockets_serve_http_file</b>
|
||||
(<i>struct libwebsocket *</i> <b>wsi</b>,
|
||||
(<i>struct libwebsocket_context *</i> <b>context</b>,
|
||||
<i>struct libwebsocket *</i> <b>wsi</b>,
|
||||
<i>const char *</i> <b>file</b>,
|
||||
<i>const char *</i> <b>content_type</b>)
|
||||
<h3>Arguments</h3>
|
||||
|
@ -638,6 +639,11 @@ That's important because it uses a slot in the
|
|||
total number of client connections allowed set
|
||||
by MAX_CLIENTS.
|
||||
</blockquote>
|
||||
<h3>LWS_CALLBACK_HTTP_FILE_COMPLETION</h3>
|
||||
<blockquote>
|
||||
a file requested to be send down
|
||||
http link has completed.
|
||||
</blockquote>
|
||||
<h3>LWS_CALLBACK_SERVER_WRITEABLE</h3>
|
||||
<blockquote>
|
||||
If you call
|
||||
|
|
|
@ -85,10 +85,10 @@ static int callback_http(struct libwebsocket_context *context,
|
|||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP:
|
||||
fprintf(stderr, "serving HTTP URI %s\n", (char *)in);
|
||||
// fprintf(stderr, "serving HTTP URI %s\n", (char *)in);
|
||||
|
||||
if (in && strcmp(in, "/favicon.ico") == 0) {
|
||||
if (libwebsockets_serve_http_file(wsi,
|
||||
if (libwebsockets_serve_http_file(context, wsi,
|
||||
LOCAL_RESOURCE_PATH"/favicon.ico", "image/x-icon"))
|
||||
fprintf(stderr, "Failed to send favicon\n");
|
||||
break;
|
||||
|
@ -96,11 +96,21 @@ static int callback_http(struct libwebsocket_context *context,
|
|||
|
||||
/* send the script... when it runs it'll start websockets */
|
||||
|
||||
if (libwebsockets_serve_http_file(wsi,
|
||||
if (libwebsockets_serve_http_file(context, wsi,
|
||||
LOCAL_RESOURCE_PATH"/test.html", "text/html"))
|
||||
fprintf(stderr, "Failed to send HTTP file\n");
|
||||
|
||||
/* we are done with this http connection */
|
||||
/*
|
||||
* notice that the sending of the file completes asynchronously,
|
||||
* we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
|
||||
* it's done
|
||||
*/
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
|
||||
// fprintf(stderr, "LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
|
||||
/* kill the connection after we sent one file */
|
||||
return 1;
|
||||
|
||||
/*
|
||||
|
@ -116,8 +126,8 @@ static int callback_http(struct libwebsocket_context *context,
|
|||
libwebsockets_get_peer_addresses((int)(long)user, client_name,
|
||||
sizeof(client_name), client_ip, sizeof(client_ip));
|
||||
|
||||
fprintf(stderr, "Received network connect from %s (%s)\n",
|
||||
client_name, client_ip);
|
||||
// fprintf(stderr, "Received network connect from %s (%s)\n",
|
||||
// client_name, client_ip);
|
||||
|
||||
/* if we returned non-zero from here, we kill the connection */
|
||||
break;
|
||||
|
@ -573,6 +583,7 @@ int main(int argc, char **argv)
|
|||
if (n < 0)
|
||||
continue;
|
||||
|
||||
|
||||
if (n)
|
||||
for (n = 0; n < count_pollfds; n++)
|
||||
if (pollfds[n].revents)
|
||||
|
@ -584,13 +595,12 @@ int main(int argc, char **argv)
|
|||
if (libwebsocket_service_fd(context,
|
||||
&pollfds[n]) < 0)
|
||||
goto done;
|
||||
|
||||
#else
|
||||
n = libwebsocket_service(context, 50);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
#else /* !LWS_NO_FORK */
|
||||
|
||||
/*
|
||||
* This example shows how to work with the forked websocket service loop
|
||||
|
|
Loading…
Add table
Reference in a new issue