remove need for filepath buffer on http file serve
This gets rid of the stack buffer while serving files, and the
PATH_MAX char array that used to hold the filepath in the wsi.
It holds an extra file descriptor open while serving the file,
however it attempts to stuff the socket with as much of the
file as it can take. For files of a few KB, that typically
completes (without blocking) in the call to
libwebsockets_serve_http_file() and then closes the file
descriptor before returning.
Signed-off-by: Andy Green <andy.green@linaro.org>
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 7836f90..2ae6b56 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -178,6 +178,11 @@
wsi->u.ws.close_reason = reason;
+ if (wsi->mode == LWS_CONNMODE_HTTP_SERVING && wsi->u.http.fd) {
+ close(wsi->u.http.fd);
+ wsi->u.http.fd = 0;
+ }
+
#ifndef LWS_NO_EXTENSIONS
/*
* are his extensions okay with him closing? Eg he might be a mux
@@ -653,10 +658,8 @@
else
n = LWS_CALLBACK_SERVER_WRITEABLE;
- user_callback_handle_rxflow(wsi->protocol->callback, context,
+ return user_callback_handle_rxflow(wsi->protocol->callback, context,
wsi, (enum libwebsocket_callback_reasons) n, wsi->user_space, NULL, 0);
-
- return 0;
}
@@ -1419,8 +1422,11 @@
int n;
n = callback_function(context, wsi, reason, user, in, len);
- if (n < 0)
+ if (n) {
+ libwebsocket_close_and_free_session(context, wsi,
+ LWS_CLOSE_STATUS_NOSTATUS);
return n;
+ }
_libwebsocket_rx_flow_control(wsi);
diff --git a/lib/output.c b/lib/output.c
index 879d52b..3b19f40 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -480,6 +480,39 @@
return lws_issue_raw_ext_access(wsi, buf - pre, len + pre + post);
}
+int libwebsockets_serve_http_file_fragment(struct libwebsocket_context *context,
+ struct libwebsocket *wsi)
+{
+ int ret = 0;
+ int n;
+
+ while (!lws_send_pipe_choked(wsi)) {
+ n = read(wsi->u.http.fd, context->service_buffer, sizeof(context->service_buffer));
+ if (n > 0) {
+ libwebsocket_write(wsi, context->service_buffer, n, LWS_WRITE_HTTP);
+ wsi->u.http.filepos += n;
+ }
+
+ if (n < 0) {
+ libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS);
+ return 1;
+ }
+
+ if (n < sizeof(context->service_buffer) || wsi->u.http.filepos == wsi->u.http.filelen) {
+ wsi->state = WSI_STATE_HTTP;
+
+ if (wsi->protocol->callback)
+ ret = user_callback_handle_rxflow(wsi->protocol->callback, context, wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space,
+ NULL, 0);
+ return ret;
+ }
+ }
+
+ lwsl_notice("choked before able to send whole file (post)\n");
+ libwebsocket_callback_on_writable(context, wsi);
+
+ return ret;
+}
/**
* libwebsockets_serve_http_file() - Send a file back to the client using http
@@ -491,138 +524,56 @@
* This function is intended to be called from the callback in response
* to http requests from the client. It allows the callback to issue
* local files down the http link in a single step.
+ *
+ * Returning <0 indicates error and the wsi should be closed. Returning
+ * >0 indicates the file was completely sent and the wsi should be closed.
+ * ==0 indicates the file transfer is started and needs more service later,
+ * the wsi should be left alone.
*/
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[1400];
- char *p = buf;
- int n, m;
+ unsigned char *p = context->service_buffer;
+ int ret = 0;
- strncpy(wsi->u.http.filepath, file, sizeof wsi->u.http.filepath);
- wsi->u.http.filepath[sizeof(wsi->u.http.filepath) - 1] = '\0';
-
+ wsi->u.http.fd = open(file, O_RDONLY
#ifdef WIN32
- fd = open(wsi->u.http.filepath, O_RDONLY | _O_BINARY);
-#else
- fd = open(wsi->u.http.filepath, O_RDONLY);
+ | _O_BINARY
#endif
- if (fd < 1) {
- p += sprintf(p, "HTTP/1.0 400 Bad\x0d\x0a"
+ );
+
+ if (wsi->u.http.fd < 1) {
+ p += sprintf((char *)p, "HTTP/1.0 400 Bad\x0d\x0a"
"Server: libwebsockets\x0d\x0a"
"\x0d\x0a"
);
- libwebsocket_write(wsi, (unsigned char *)buf, p - buf,
- LWS_WRITE_HTTP);
+ wsi->u.http.fd = 0;
+ libwebsocket_write(wsi, context->service_buffer,
+ p - context->service_buffer, LWS_WRITE_HTTP);
return -1;
}
- fstat(fd, &stat_buf);
+ fstat(wsi->u.http.fd, &stat_buf);
wsi->u.http.filelen = stat_buf.st_size;
- p += sprintf(p, "HTTP/1.0 200 OK\x0d\x0a"
+ p += sprintf((char *)p, "HTTP/1.0 200 OK\x0d\x0a"
"Server: libwebsockets\x0d\x0a"
"Content-Type: %s\x0d\x0a"
"Content-Length: %u\x0d\x0a"
"\x0d\x0a", content_type,
(unsigned int)stat_buf.st_size);
- n = libwebsocket_write(wsi, (unsigned char *)buf, p - buf, LWS_WRITE_HTTP);
- if (n) {
- close(fd);
- return n;
- }
+ ret = libwebsocket_write(wsi, context->service_buffer,
+ p - context->service_buffer, LWS_WRITE_HTTP);
+ if (ret)
+ return -1;
wsi->u.http.filepos = 0;
wsi->state = WSI_STATE_HTTP_ISSUING_FILE;
- while (!lws_send_pipe_choked(wsi)) {
-
- n = read(fd, buf, sizeof buf);
- if (n > 0) {
- wsi->u.http.filepos += n;
- m = libwebsocket_write(wsi, (unsigned char *)buf, n, LWS_WRITE_HTTP);
- if (m) {
- close(fd);
- return m;
- }
- }
-
- if (n < 0) {
- close(fd);
- return -1;
- }
-
- if (n < sizeof(buf) || wsi->u.http.filepos == wsi->u.http.filelen) {
- /* oh, we were able to finish here! */
- wsi->state = WSI_STATE_HTTP;
- close(fd);
-
- if (wsi->protocol->callback(context, wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space,
- wsi->u.http.filepath, wsi->u.http.filepos)) {
- lwsl_info("closing connecton after file_completion returned nonzero\n");
- libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS);
- }
-
- return 0;
- }
- }
-
- /* we choked, no worries schedule service for the rest of it */
-
- libwebsocket_callback_on_writable(context, wsi);
-
- close(fd);
-
- return 0;
+ return libwebsockets_serve_http_file_fragment(context, wsi);
}
-int libwebsockets_serve_http_file_fragment(struct libwebsocket_context *context,
- struct libwebsocket *wsi)
-{
- int fd;
- int ret = 0;
- char buf[1400];
- int n;
-
-#ifdef WIN32
- fd = open(wsi->u.http.filepath, O_RDONLY | _O_BINARY);
-#else
- fd = open(wsi->u.http.filepath, O_RDONLY);
-#endif
- if (fd < 1)
- return -1;
-
- lseek(fd, wsi->u.http.filepos, SEEK_SET);
-
- while (!lws_send_pipe_choked(wsi)) {
- n = read(fd, buf, sizeof buf);
- if (n > 0) {
- libwebsocket_write(wsi, (unsigned char *)buf, n, LWS_WRITE_HTTP);
- wsi->u.http.filepos += n;
- }
-
- if (n < 0) {
- close(fd);
- return -1;
- }
-
- if (n < sizeof(buf) || wsi->u.http.filepos == wsi->u.http.filelen) {
- wsi->state = WSI_STATE_HTTP;
- close(fd);
- return 0;
- }
- }
-
- libwebsocket_callback_on_writable(context, wsi);
-
- close(fd);
-
- return ret;
-}
-
-
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 7f461ef..8c7b23a 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -256,6 +256,13 @@
unsigned int options;
unsigned long last_timeout_check_s;
+ /*
+ * usable by anything in the service code, but only if the scope
+ * does not last longer than the service action (since next service
+ * of any socket can likewise use it and overwrite)
+ */
+ unsigned char service_buffer[4096];
+
int started_with_parent;
int fd_random;
@@ -303,7 +310,7 @@
*/
struct _lws_http_mode_related {
- char filepath[PATH_MAX];
+ int fd;
unsigned long filepos;
unsigned long filelen;
};
diff --git a/lib/server.c b/lib/server.c
index c1b6cd8..a7be274 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -189,14 +189,9 @@
if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE)
break;
- if (libwebsockets_serve_http_file_fragment(context, wsi) < 0)
+ if (libwebsockets_serve_http_file_fragment(context, wsi)) /* nonzero for completion or error */
libwebsocket_close_and_free_session(context, wsi,
LWS_CLOSE_STATUS_NOSTATUS);
- else
- if (wsi->state == WSI_STATE_HTTP && wsi->protocol->callback)
- if (user_callback_handle_rxflow(wsi->protocol->callback, context, wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space,
- wsi->u.http.filepath, wsi->u.http.filepos))
- libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS);
break;
case LWS_CONNMODE_SERVER_LISTENER: