From c76c9195e639b24fb5290f2859aeb12ccea564a5 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Tue, 22 Jan 2013 07:20:08 +0800 Subject: [PATCH] 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 --- lib/libwebsockets.c | 14 ++-- lib/output.c | 161 +++++++++++++++----------------------------- lib/private-libwebsockets.h | 9 ++- lib/server.c | 7 +- libwebsockets-api-doc.html | 5 ++ test-server/test-server.c | 2 +- 6 files changed, 81 insertions(+), 117 deletions(-) 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 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, 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 @@ notify_action: 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 user_callback_handle_rxflow(callback_function callback_function, 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 @@ send_raw: 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 @@ send_raw: * 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; - - strncpy(wsi->u.http.filepath, file, sizeof wsi->u.http.filepath); - wsi->u.http.filepath[sizeof(wsi->u.http.filepath) - 1] = '\0'; + unsigned char *p = context->service_buffer; + int ret = 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; -} - -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; + return libwebsockets_serve_http_file_fragment(context, wsi); } - 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 @@ struct libwebsocket_context { 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 @@ enum pending_timeout { */ 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 @@ int lws_server_socket_service(struct libwebsocket_context *context, 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: diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html index 9ccf859..e00dc59 100644 --- a/libwebsockets-api-doc.html +++ b/libwebsockets-api-doc.html @@ -517,6 +517,11 @@ packet while not burdening the user code with any protocol knowledge. 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.


lws_frame_is_binary -

diff --git a/test-server/test-server.c b/test-server/test-server.c index 191de06..46a5917 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -118,7 +118,7 @@ static int callback_http(struct libwebsocket_context *context, sprintf(buf, LOCAL_RESOURCE_PATH"%s", whitelist[n].urlpath); if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype)) - lwsl_err("Failed to send HTTP file\n"); + return 1; /* through completion or error, close the socket */ /* * notice that the sending of the file completes asynchronously, -- 2.7.4