diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 7836f901..2ae6b561 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 879d52b7..3b19f400 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; + 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 7f461ef3..8c7b23a7 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 c1b6cd8a..a7be2745 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 9ccf859b..e00dc598 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 191de063..46a59176 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,