diff --git a/lib/header.c b/lib/header.c index c9f3af3e..4c6fabc8 100644 --- a/lib/header.c +++ b/lib/header.c @@ -79,6 +79,25 @@ int lws_finalize_http_header(struct lws *wsi, unsigned char **p, return 0; } +int +lws_finalize_write_http_header(struct lws *wsi, unsigned char *start, + unsigned char **pp, unsigned char *end) +{ + unsigned char *p; + int len; + + if (lws_finalize_http_header(wsi, pp, end)) + return 1; + + p = *pp; + len = lws_ptr_diff(p, start); + + if (lws_write(wsi, start, len, LWS_WRITE_HTTP_HEADERS) != len) + return 1; + + return 0; +} + int lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, const unsigned char *value, int length, @@ -117,6 +136,23 @@ int lws_add_http_header_content_length(struct lws *wsi, return 0; } +int +lws_add_http_common_headers(struct lws *wsi, unsigned int code, + const char *content_type, lws_filepos_t content_len, + unsigned char **p, unsigned char *end) +{ + if (lws_add_http_header_status(wsi, code, p, end)) + return 1; + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)content_type, + strlen(content_type), p, end)) + return 1; + if (lws_add_http_header_content_length(wsi, content_len, p, end)) + return 1; + + return 0; +} + STORE_IN_ROM static const char * const err400[] = { "Bad Request", "Unauthorized", diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 5035cfad..fca470e8 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -4131,6 +4131,49 @@ lws_add_http_header_content_length(struct lws *wsi, LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_finalize_http_header(struct lws *wsi, unsigned char **p, unsigned char *end); + +/** + * lws_finalize_write_http_header() - Helper finializing and writing http headers + * + * \param wsi: the connection to check + * \param start: pointer to the start of headers in the buffer, eg &buf[LWS_PRE] + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Terminates the headers correctly accoring to the protocol in use (h1 / h2) + * and writes the headers. Returns nonzero for error. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_finalize_write_http_header(struct lws *wsi, unsigned char *start, + unsigned char **p, unsigned char *end); + +/** + * lws_add_http_common_headers() - Helper preparing common http headers + * + * \param wsi: the connection to check + * \param code: an HTTP code like 200, 404 etc (see enum http_status) + * \param content_type: the content type, like "text/html" + * \param content_len: the content length, in bytes + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Adds the initial response code, so should be called first. + * + * Code may additionally take OR'd flags: + * + * LWSAHH_FLAG_NO_SERVER_NAME: don't apply server name header this time + * + * This helper just calls public apis to simplify adding headers that are + * commonly needed. If it doesn't fit your case, or you want to add additional + * headers just call the public apis directly yourself for what you want. + * + * It does not call lws_finalize_http_header(), to allow you to add further + * headers after calling this. You will need to call that yourself at the end. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_add_http_common_headers(struct lws *wsi, unsigned int code, + const char *content_type, lws_filepos_t content_len, + unsigned char **p, unsigned char *end); ///@} /** \defgroup form-parsing Form Parsing diff --git a/lib/server/server.c b/lib/server/server.c index 0b8710b6..25d46073 100644 --- a/lib/server/server.c +++ b/lib/server/server.c @@ -1474,7 +1474,9 @@ lws_http_action(struct lws *wsi) wsi->cache_revalidate = hit->cache_revalidate; wsi->cache_intermediaries = hit->cache_intermediaries; - n = lws_http_serve(wsi, s, hit->origin, hit); + n = 1; + if (hit->origin_protocol == LWSMPRO_FILE) + n = lws_http_serve(wsi, s, hit->origin, hit); if (n) { /* * lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); @@ -2795,7 +2797,7 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, wsi->http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, file, vpath, &fflags); if (!wsi->http.fop_fd) { - lwsl_err("Unable to open '%s': errno %d\n", file, errno); + lwsl_err("Unable to open: '%s': errno %d\n", file, errno); return -1; } diff --git a/minimal-examples/http-server/README.md b/minimal-examples/http-server/README.md index 9a72f66e..59535d8e 100644 --- a/minimal-examples/http-server/README.md +++ b/minimal-examples/http-server/README.md @@ -4,3 +4,4 @@ minimal-http-server|Serves a directory over http/1 or http/2, custom 404 handler minimal-http-server-libuv|Same as minimal-http-server but libuv event loop minimal-http-server-multivhost|Same as minimal-http-server but three different vhosts minimal-http-server-smp|Multiple service threads +minimal-http-server-dynamic|Serves both static and dynamically generated http content diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-dynamic/CMakeLists.txt new file mode 100644 index 00000000..2943e46f --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-dynamic/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckSymbolExists) + +set(SAMP lws-minimal-http-server-dynamic) +set(SRCS minimal-http-server-dynamic.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_SYMBOL_EXISTS(${reqconfig} libwebsockets.h HAS) + if (NOT DEFINED HAS) + set(HAS 0) + endif() + if ((HAS AND ${_val}) OR (NOT HAS AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/README.md b/minimal-examples/http-server/minimal-http-server-dynamic/README.md new file mode 100644 index 00000000..9666d028 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-dynamic/README.md @@ -0,0 +1,20 @@ +# lws minimal http server dynamic content + +## build + +``` + $ cmake . && make +``` + +## usage + +``` + $ ./lws-minimal-http-server-dynamic +[2018/03/20 10:24:24:7099] USER: LWS minimal http server dynamic | visit http://localhost:7681 +[2018/03/20 10:24:24:7099] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off +``` + +Visit http://localhost:7681, which is all static content. + +Click on the link to /dyn/anything, this opens a new tab with dynamicly-produced content. + diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c b/minimal-examples/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c new file mode 100644 index 00000000..cf541f5c --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c @@ -0,0 +1,184 @@ +/* + * lws-minimal-http-server-dynamic + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal http server that can produce dynamic http + * content as well as static content. + * + * To keep it simple, it serves the static stuff from the subdirectory + * "./mount-origin" of the directory it was started in. + * + * You can change that by changing mount.origin below. + */ + +#include +#include +#include +#include + +/* + * Unlike ws, http is a stateless protocol. This pss only exists for the + * duration of a single http transaction. With http/1.1 keep-alive and http/2, + * that is unrelated to (shorter than) the lifetime of the network connection. + */ +struct pss { + char str[128]; + int len; +}; + +static int interrupted; + +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 + 256], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - 1]; + time_t t; + + switch (reason) { + case LWS_CALLBACK_HTTP: + + /* in contains the url part after our mountpoint /dyn, if any */ + + t = time(NULL); + pss->len = lws_snprintf(pss->str, sizeof(pss->str), + "" + "" + "
Dynamic content for '%s' from mountpoint." + "
Time: %s" + "", (const char *)in, ctime(&t)); + + /* prepare and write http headers */ + if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK, + "text/html", pss->len, &p, end)) + return 1; + if (lws_finalize_write_http_header(wsi, start, &p, end)) + return 1; + + /* write the body separately */ + lws_callback_on_writable(wsi); + + return 0; + + case LWS_CALLBACK_HTTP_WRITEABLE: + + if (!pss || !pss->len) + break; + + /* + * Use LWS_WRITE_HTTP for intermediate writes, on http/2 + * lws uses this to understand to end the stream with this + * frame + */ + if (lws_write(wsi, (uint8_t *)pss->str, pss->len, + LWS_WRITE_HTTP_FINAL) != pss->len) + 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 (lws_http_transaction_completed(wsi)) + return -1; + + return 0; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static struct lws_protocols protocols[] = { + { "http", callback_dynamic_http, sizeof(struct pss), 0 }, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +/* override the default mount for /dyn in the URL space */ + +static const struct lws_http_mount mount_dyn = { + /* .mount_next */ NULL, /* linked-list "next" */ + /* .mountpoint */ "/dyn", /* mountpoint URL */ + /* .origin */ NULL, /* protocol */ + /* .def */ NULL, + /* .protocol */ "http", + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ + /* .mountpoint_len */ 4, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +/* default mount serves the URL space from ./mount-origin */ + +static const struct lws_http_mount mount = { + /* .mount_next */ &mount_dyn, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ "./mount-origin", /* serve from dir */ + /* .def */ "index.html", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = 7681; + info.protocols = protocols; + info.mounts = &mount; + + lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_USER + /* | LLL_INFO */ /* | LLL_DEBUG */, NULL); + + lwsl_user("LWS minimal http server dynamic | visit http://localhost:7681\n"); + + 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); + + return 0; +} diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/404.html new file mode 100644 index 00000000..1f7ae66e --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/404.html @@ -0,0 +1,9 @@ + + + +
+

404

+ Sorry, that file doesn't exist. + + + diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico new file mode 100644 index 00000000..c0cc2e3d Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico differ diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/index.html new file mode 100644 index 00000000..f05216bc --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/index.html @@ -0,0 +1,16 @@ + + + +
+ + Hello from the minimal http server dynamic content example. +

+ This is a static page served from ./mount-origin/index.html. +

+ Stuff down /dyn in the URL space is generated dynamically
+ by the callback. For example, click on + /dyn/anything to + see dynamic content. + + + diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/libwebsockets.org-logo.png b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/libwebsockets.org-logo.png new file mode 100644 index 00000000..2060a10c Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/libwebsockets.org-logo.png differ