diff --git a/minimal-examples/http-server/README.md b/minimal-examples/http-server/README.md index 7854e14c..7e964c55 100644 --- a/minimal-examples/http-server/README.md +++ b/minimal-examples/http-server/README.md @@ -2,6 +2,7 @@ ---|--- minimal-http-server-dynamic|Serves both static and dynamically generated http content minimal-http-server-form-get|Process a GET form +minimal-http-server-form-post-file|Process a multipart POST form with file transfer minimal-http-server-form-post|Process a POST form (no file transfer) 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 diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-form-post-file/CMakeLists.txt new file mode 100644 index 00000000..ae83283d --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-form-post-file/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-http-server-form-post-file) +set(SRCS minimal-http-server-form-post-file.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_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} 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-form-post-file/README.md b/minimal-examples/http-server/minimal-http-server-form-post-file/README.md new file mode 100644 index 00000000..d0fce2c0 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-form-post-file/README.md @@ -0,0 +1,23 @@ +# lws minimal http server form POST file + +## build + +``` + $ cmake . && make +``` + +## usage + +``` + $ ./lws-minimal-http-server-form-post-file +[2018/03/29 09:58:30:8800] USER: LWS minimal http server POST file | visit http://localhost:7681 +[2018/03/29 09:58:30:8800] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off +[2018/03/29 09:58:45:3284] USER: file_upload_cb: upload done, written 2729 to wss-over-h2.png +[2018/03/29 09:58:45:3284] USER: text1: (len 3) 'xxx' +[2018/03/29 09:58:45:3284] USER: send: (len 6) 'Submit' +``` + +Visit http://localhost:7681, select a file to upload and submit the form. + +The file is uploaded and saved in the cwd, the form parameters are dumped to the log and +you are redirected to a different page. diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c b/minimal-examples/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c new file mode 100644 index 00000000..d335af7f --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c @@ -0,0 +1,263 @@ +/* + * lws-minimal-http-server-form-post-file + * + * 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 performs POST with a couple + * of parameters and a file upload, all in multipart (mime) form mode. + * It saves the uploaded file in the current directory, dumps the parameters to + * the console log and redirects to another page. + */ + +#include +#include +#include +#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 { + struct lws_spa *spa; /* lws helper decodes multipart form */ + char filename[128]; /* the filename of the uploaded file */ + unsigned long long file_length; /* the amount of bytes uploaded */ + int fd; /* fd on file being saved */ +}; + +static int interrupted; + +static const char * const param_names[] = { + "text1", + "send", +}; + +enum enum_param_names { + EPN_TEXT1, + EPN_SEND, +}; + +static int +file_upload_cb(void *data, const char *name, const char *filename, + char *buf, int len, enum lws_spa_fileupload_states state) +{ + struct pss *pss = (struct pss *)data; + int n; + + switch (state) { + case LWS_UFS_OPEN: + /* take a copy of the provided filename */ + lws_strncpy(pss->filename, filename, sizeof(pss->filename) - 1); + /* remove any scary things like .. */ + lws_filename_purify_inplace(pss->filename); + /* open a file of that name for write in the cwd */ + pss->fd = open(pss->filename, O_CREAT | O_TRUNC | O_RDWR, 0600); + if (pss->fd == LWS_INVALID_FILE) { + lwsl_notice("Failed to open output file %s\n", + pss->filename); + return 1; + } + break; + case LWS_UFS_FINAL_CONTENT: + case LWS_UFS_CONTENT: + if (len) { + pss->file_length += len; + + n = write(pss->fd, buf, len); + if (n < len) { + lwsl_notice("Problem writing file %d\n", errno); + } + } + if (state == LWS_UFS_CONTENT) + /* wasn't the last part of the file */ + break; + + /* the file upload is completed */ + + lwsl_user("%s: upload done, written %lld to %s\n", __func__, + pss->file_length, pss->filename); + + close(pss->fd); + pss->fd = LWS_INVALID_FILE; + break; + } + + return 0; +} + +static int +callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + uint8_t buf[LWS_PRE + 256], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - 1]; + struct pss *pss = (struct pss *)user; + int n; + + switch (reason) { + case LWS_CALLBACK_HTTP: + + /* + * Manually report that our form target URL exists + * + * you can also do this by adding a mount for the form URL + * to the protocol with type LWSMPRO_CALLBACK, then no need + * to trap LWS_CALLBACK_HTTP. + */ + + if (!strcmp((const char *)in, "/form1")) + /* assertively allow it to exist in the URL space */ + return 0; + + /* default to 404-ing the URL if not mounted */ + break; + + case LWS_CALLBACK_HTTP_BODY: + + /* create the POST argument parser if not already existing */ + + if (!pss->spa) { + pss->spa = lws_spa_create(wsi, param_names, + ARRAY_SIZE(param_names), 1024, + file_upload_cb, pss); + if (!pss->spa) + return -1; + } + + /* let it parse the POST data */ + + if (lws_spa_process(pss->spa, in, (int)len)) + return -1; + break; + + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + + /* inform the spa no more payload data coming */ + + lws_spa_finalize(pss->spa); + + /* we just dump the decoded things to the log */ + + for (n = 0; n < (int)ARRAY_SIZE(param_names); n++) { + if (!lws_spa_get_string(pss->spa, n)) + lwsl_user("%s: undefined\n", param_names[n]); + else + lwsl_user("%s: (len %d) '%s'\n", + param_names[n], + lws_spa_get_length(pss->spa, n), + lws_spa_get_string(pss->spa, n)); + } + + /* + * Our response is to redirect to a static page. We could + * have generated a dynamic html page here instead. + */ + + if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, + (unsigned char *)"after-form1.html", + 16, &p, end)) + return -1; + + /* we could add more headers here */ + + if (lws_finalize_http_header(wsi, &p, end)) + return -1; + + n = lws_write(wsi, start, lws_ptr_diff(p, start), + LWS_WRITE_HTTP_HEADERS | + LWS_WRITE_H2_STREAM_END); + if (n < 0) + return -1; + + break; + + case LWS_CALLBACK_HTTP_DROP_PROTOCOL: + /* called when our wsi user_space is going to be destroyed */ + if (pss->spa) { + lws_spa_destroy(pss->spa); + pss->spa = NULL; + } + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static struct lws_protocols protocols[] = { + { "http", callback_http, sizeof(struct pss), 0 }, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +/* default mount serves the URL space from ./mount-origin */ + +static const struct lws_http_mount mount = { + /* .mount_next */ NULL, /* 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_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */, NULL); + + lwsl_user("LWS minimal http server POST file | 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-form-post-file/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/404.html new file mode 100644 index 00000000..9ad5a334 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-form-post-file/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-form-post-file/mount-origin/after-form1.html b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/after-form1.html new file mode 100644 index 00000000..066f63ac --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/after-form1.html @@ -0,0 +1,11 @@ + + + +
+ + Thanks for posting the form.
+
+ The file you uploaded should have been saved in the current directory. + + + diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico new file mode 100644 index 00000000..c0cc2e3d Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico differ diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/index.html new file mode 100644 index 00000000..9676a0ff --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/index.html @@ -0,0 +1,26 @@ + + + +
+ + Hello from the minimal http form POST file example. +

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

+ When you POST the form below, you will see the values of the
+ form parameters reported on the console log, and the file will + be uploaded and saved in the current working directory. +

+

+ Type some text:
+
+
+ Select a file to upload: +   +
+
+ +
+ + + diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/libwebsockets.org-logo.png b/minimal-examples/http-server/minimal-http-server-form-post-file/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-form-post-file/mount-origin/libwebsockets.org-logo.png differ