From 8a3241d5c46e8b8fac382f606147285e314f1f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 00:15:53 +0200 Subject: [PATCH 44/79] CMakeLists.txt: fix variable name --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97c63cb..895a6e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,10 @@ project (acid C) cmake_minimum_required (VERSION 2.8.5) # Moar warnings -if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC) +if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) # -Wunused-function is pretty annoying here, as everything is static set (CMAKE_C_FLAGS "-std=c99 -Wall -Wextra -Wno-unused-function") -endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC) +endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) # Version set (project_VERSION_MAJOR "0") From 4078c8845ce4549edb23e3138e45d32af8858ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 00:21:10 +0200 Subject: [PATCH 45/79] Relicense to 0BSD, update mail address I've come to the conclusion that copyright mostly just stands in the way of software development. In my jurisdiction I cannot give up my own copyright and 0BSD seems to be the closest thing to public domain. The updated mail address, also used in my author/committer lines, is shorter and looks nicer. People rarely interact anyway. --- CMakeLists.txt | 2 +- LICENSE | 5 ++--- README.adoc | 9 ++------- demo-json-rpc-server.c | 5 ++--- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 895a6e5..c78736a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endforeach (page) # CPack set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "A Continuous Integration Daemon") set (CPACK_PACKAGE_VENDOR "Premysl Janouch") -set (CPACK_PACKAGE_CONTACT "Přemysl Janouch
") set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR}) set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR}) diff --git a/LICENSE b/LICENSE index 8cc3add..01dc408 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,7 @@ -Copyright (c) 2015, Přemysl Janouch
Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. +purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF diff --git a/README.adoc b/README.adoc index e8f0404..2f80d73 100644 --- a/README.adoc +++ b/README.adoc @@ -61,10 +61,5 @@ Bitcoin donations: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9 License ------- -'acid' is written by Přemysl Janouch
* * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. + * purpose with or without fee is hereby granted. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF From b312c022ae28e0852cba7430465971a2a391f016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 00:21:32 +0200 Subject: [PATCH 46/79] Update README --- README.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index 2f80d73..80d708f 100644 --- a/README.adoc +++ b/README.adoc @@ -29,7 +29,7 @@ Build dependencies: CMake, pkg-config, help2man, libmagic, liberty (included), http-parser (included) + Runtime dependencies: libev, Jansson - $ git clone --recursive https://github.com/pjanouch/acid.git + $ git clone --recursive https://git.janouch.name/p/acid.git $ mkdir acid/build $ cd acid/build $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug @@ -53,11 +53,11 @@ TODO. The main application hasn't been written yet. Contributing and Support ------------------------ -Use this project's GitHub to report any bugs, request features, or submit pull -requests. If you want to discuss this project, or maybe just hang out with -the developer, feel free to join me at irc://irc.janouch.name, channel #dev. +Use https://git.janouch.name/p/acid to report any bugs, request features, +or submit pull requests. `git send-email` is tolerated. If you want to discuss +the project, feel free to join me at ircs://irc.janouch.name, channel #dev. -Bitcoin donations: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9 +Bitcoin donations are accepted at: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9 License ------- From 131debe985199e3d261a985b2055a86fc4bd3fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 00:40:10 +0200 Subject: [PATCH 47/79] Bump liberty --- .gitmodules | 2 +- demo-json-rpc-server.c | 106 ++++++++++++++++------------------------- liberty | 2 +- 3 files changed, 44 insertions(+), 66 deletions(-) diff --git a/.gitmodules b/.gitmodules index c8d5acf..1a41faf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "liberty"] path = liberty - url = git://github.com/pjanouch/liberty.git + url = https://git.janouch.name/p/liberty.git [submodule "http-parser"] path = http-parser url = https://github.com/joyent/http-parser.git diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 10c808e..0cfb13b 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -144,8 +144,7 @@ fcgi_muxer_send (struct fcgi_muxer *self, { hard_assert (len <= UINT16_MAX); - struct str message; - str_init (&message); + struct str message = str_make (); str_pack_u8 (&message, FCGI_VERSION_1); str_pack_u8 (&message, type); @@ -177,10 +176,9 @@ fcgi_request_init (struct fcgi_request *self) { memset (self, 0, sizeof *self); - str_map_init (&self->headers); - self->headers.free = free; + self->headers = str_map_make (free); - fcgi_nv_parser_init (&self->hdr_parser); + self->hdr_parser = fcgi_nv_parser_make (); self->hdr_parser.output = &self->headers; } @@ -275,17 +273,15 @@ static void fcgi_muxer_on_get_values (struct fcgi_muxer *self, const struct fcgi_parser *parser) { - struct str_map values; str_map_init (&values); values.free = free; - struct str_map response; str_map_init (&response); response.free = free; + struct str_map values = str_map_make (free); + struct str_map response = str_map_make (free); - struct fcgi_nv_parser nv_parser; - fcgi_nv_parser_init (&nv_parser); + struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make (); nv_parser.output = &values; fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len); - struct str_map_iter iter; - str_map_iter_init (&iter, &values); + struct str_map_iter iter = str_map_iter_make (&values); while (str_map_iter_next (&iter)) { const char *key = iter.link->key; @@ -297,8 +293,7 @@ fcgi_muxer_on_get_values str_map_set (&response, key, xstrdup ("1")); } - struct str content; - str_init (&content); + struct str content = str_make (); fcgi_nv_convert (&response, &content); fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id, content.str, content.len); @@ -312,8 +307,8 @@ static void fcgi_muxer_on_begin_request (struct fcgi_muxer *self, const struct fcgi_parser *parser) { - struct msg_unpacker unpacker; - msg_unpacker_init (&unpacker, parser->content.str, parser->content.len); + struct msg_unpacker unpacker = + msg_unpacker_make (parser->content.str, parser->content.len); uint16_t role; uint8_t flags; @@ -435,7 +430,7 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data) static void fcgi_muxer_init (struct fcgi_muxer *self) { - fcgi_parser_init (&self->parser); + self->parser = fcgi_parser_make (); self->parser.on_message = fcgi_muxer_on_message; self->parser.user_data = self; } @@ -554,8 +549,7 @@ static void ws_handler_close (struct ws_handler *self, enum ws_status close_code, const char *reason, size_t len) { - struct str payload; - str_init (&payload); + struct str payload = str_make (); str_pack_u16 (&payload, close_code); // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w str_append_data (&payload, reason, len); @@ -590,8 +584,7 @@ ws_handler_send (struct ws_handler *self, if (!soft_assert (self->state == WS_HANDLER_OPEN)) return; - struct str header; - str_init (&header); + struct str header = str_make (); str_pack_u8 (&header, 0x80 | (opcode & 0x0F)); if (len > UINT16_MAX) @@ -639,8 +632,8 @@ static bool ws_handler_on_protocol_close (struct ws_handler *self, const struct ws_parser *parser) { - struct msg_unpacker unpacker; - msg_unpacker_init (&unpacker, parser->input.str, parser->payload_len); + struct msg_unpacker unpacker = + msg_unpacker_make (parser->input.str, parser->payload_len); char *reason = NULL; uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED; @@ -775,21 +768,20 @@ ws_handler_init (struct ws_handler *self) http_parser_init (&self->hp, HTTP_REQUEST); self->hp.data = self; - str_init (&self->field); - str_init (&self->value); - str_map_init (&self->headers); - self->headers.free = free; + self->field = str_make (); + self->value = str_make (); + self->headers = str_map_make (free); self->headers.key_xfrm = tolower_ascii_strxfrm; - str_init (&self->url); + self->url = str_make (); ev_timer_init (&self->handshake_timeout_watcher, ws_handler_on_handshake_timeout, 0., 0.); self->handshake_timeout_watcher.data = self; - ws_parser_init (&self->parser); + self->parser = ws_parser_make (); self->parser.on_frame_header = ws_handler_on_frame_header; self->parser.on_frame = ws_handler_on_frame; self->parser.user_data = self; - str_init (&self->message_data); + self->message_data = str_make (); ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer, 0., 0.); @@ -927,8 +919,7 @@ ws_handler_http_responsev (struct ws_handler *self, { hard_assert (status != NULL); - struct str response; - str_init (&response); + struct str response = str_make (); str_append_printf (&response, "HTTP/1.1 %s\r\n", status); while (*fields) @@ -943,8 +934,7 @@ ws_handler_http_responsev (struct ws_handler *self, static void ws_handler_http_response (struct ws_handler *self, const char *status, ...) { - struct strv v; - strv_init (&v); + struct strv v = strv_make (); va_list ap; va_start (ap, status); @@ -1015,8 +1005,7 @@ ws_handler_finish_handshake (struct ws_handler *self) if (!key) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); - struct str tmp; - str_init (&tmp); + struct str tmp = str_make (); bool key_is_valid = base64_decode (key, false, &tmp) && tmp.len == 16; str_free (&tmp); if (!key_is_valid) @@ -1027,9 +1016,7 @@ ws_handler_finish_handshake (struct ws_handler *self) if (strcmp (version, "13")) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13", NULL); - struct strv fields; - strv_init (&fields); - + struct strv fields = strv_make (); strv_append_args (&fields, "Upgrade: websocket", "Connection: Upgrade", @@ -1181,7 +1168,7 @@ server_context_init (struct server_context *self) { memset (self, 0, sizeof *self); - str_map_init (&self->config); + self->config = str_map_make (NULL); simple_config_load_defaults (&self->config, g_config_table); ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.); self->quit_timeout_watcher.data = self; @@ -1260,9 +1247,7 @@ validate_json_rpc_content_type (const char *content_type) char *type = NULL; char *subtype = NULL; - struct str_map parameters; - str_map_init (¶meters); - parameters.free = free; + struct str_map parameters = str_map_make (free); parameters.key_xfrm = tolower_ascii_strxfrm; bool result = http_parse_media_type @@ -1503,8 +1488,7 @@ request_start (struct request *self, struct str_map *headers) } // Unable to serve the request - struct str response; - str_init (&response); + struct str response = str_make (); str_append (&response, "Status: 404 Not Found\n"); str_append (&response, "Content-Type: text/plain\n\n"); self->write_cb (self->user_data, response.str, response.len); @@ -1536,7 +1520,7 @@ request_handler_json_rpc_try_handle return false; struct str *buf = xcalloc (1, sizeof *buf); - str_init (buf); + *buf = str_make (); request->handler_data = buf; *continue_ = true; @@ -1554,8 +1538,7 @@ request_handler_json_rpc_push return true; } - struct str response; - str_init (&response); + struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", "application/json"); process_json_rpc (request->ctx, buf->str, buf->len, &response); @@ -1587,12 +1570,10 @@ static char * canonicalize_url_path (const char *path) { // XXX: this strips any slashes at the end - struct strv v; - strv_init (&v); + struct strv v = strv_make (); cstr_split (path, "/", true, &v); - struct strv canonical; - strv_init (&canonical); + struct strv canonical = strv_make (); // So that the joined path always begins with a slash strv_append (&canonical, ""); @@ -1672,8 +1653,7 @@ request_handler_static_try_handle FILE *fp = fopen (path, "rb"); if (!fp) { - struct str response; - str_init (&response); + struct str response = str_make (); str_append (&response, "Status: 404 Not Found\n"); str_append (&response, "Content-Type: text/plain\n\n"); str_append_printf (&response, @@ -1699,8 +1679,7 @@ request_handler_static_try_handle if (!mime_type) mime_type = xstrdup ("application/octet_stream"); - struct str response; - str_init (&response); + struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", mime_type); request->write_cb (request->user_data, response.str, response.len); @@ -1878,7 +1857,7 @@ client_init (EV_P_ struct client *self, int sock_fd) memset (self, 0, sizeof *self); self->ctx = ctx; - write_queue_init (&self->write_queue); + self->write_queue = write_queue_make (); set_blocking (sock_fd, false); self->socket_fd = sock_fd; @@ -2119,7 +2098,7 @@ client_scgi_create (EV_P_ int sock_fd) self->request.close_cb = client_scgi_close_cb; self->request.user_data = self; - scgi_parser_init (&self->parser); + self->parser = scgi_parser_make (); self->parser.on_headers_read = client_scgi_on_headers_read; self->parser.on_content = client_scgi_on_content; self->parser.user_data = self; @@ -2145,8 +2124,7 @@ client_ws_on_message (void *user_data, return false; } - struct str response; - str_init (&response); + struct str response = str_make (); process_json_rpc (self->client.ctx, data, len, &response); if (response.len) ws_handler_send (&self->handler, @@ -2410,9 +2388,9 @@ setup_listen_fds (struct server_context *ctx, struct error **e) .ai_flags = AI_PASSIVE, }; - struct strv ports_fcgi; strv_init (&ports_fcgi); - struct strv ports_scgi; strv_init (&ports_scgi); - struct strv ports_ws; strv_init (&ports_ws); + struct strv ports_fcgi = strv_make (); + struct strv ports_scgi = strv_make (); + struct strv ports_ws = strv_make (); get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi); get_ports_from_config (ctx, "port_scgi", &ports_scgi); @@ -2588,8 +2566,8 @@ parse_program_arguments (int argc, char **argv) { 0, NULL, NULL, 0, NULL } }; - struct opt_handler oh; - opt_handler_init (&oh, argc, argv, opts, NULL, "JSON-RPC 2.0 demo server."); + struct opt_handler oh = + opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server."); int c; while ((c = opt_handler_get (&oh)) != -1) diff --git a/liberty b/liberty index 1dcd259..8ffe20c 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit 1dcd259d0506b9e2de3715bdf07144b22f57903a +Subproject commit 8ffe20c0e83b52db1344fe91f57236be4c4cb504 From 8e986a604031aa6fce636a5e1cbb7b1933375406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 03:20:55 +0200 Subject: [PATCH 48/79] Remove .travis.yml We don't depend on any proprietary services no longer. I'll have to make my own replacements with blackjack and hookers. Until then, the file stays in the commit log as an example. --- .travis.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7395b90..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: c -notifications: - irc: - channels: "irc.janouch.name#dev" - use_notice: true - skip_join: true - template: - - "%{repository_name}#%{build_number} on %{branch}: %{message}" - - " %{compare_url}" - - " %{build_url}" - on_success: change - on_failure: always -compiler: - - clang - - gcc -before_install: - - sudo add-apt-repository ppa:ukplc-team/ppa -y - - sudo apt-get update -qq -install: - - sudo apt-get install -y help2man libjansson-dev libev-dev -before_script: - - mkdir build - - cd build -script: - - cmake .. -DCMAKE_INSTALL_PREFIX=/usr - - make - - cpack -G DEB From df340c13ed99bead31c54bbac003b06af82e7dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 05:59:55 +0200 Subject: [PATCH 49/79] Add the missing Date header --- demo-json-rpc-server.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 0cfb13b..cf4d374 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -925,6 +925,19 @@ ws_handler_http_responsev (struct ws_handler *self, while (*fields) str_append_printf (&response, "%s\r\n", *fields++); + time_t now = time (NULL); + struct tm ts; + gmtime_r (&now, &ts); + + // See RFC 7231, 7.1.1.2. Date + const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + str_append_printf (&response, + "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n", + dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900, + ts.tm_hour, ts.tm_min, ts.tm_sec); + str_append (&response, "Server: " PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n"); self->write_cb (self->user_data, response.str, response.len); From 711d73f481e7f0254acc3bca3e9dcf9764aaf583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sun, 24 Jun 2018 06:02:02 +0200 Subject: [PATCH 50/79] Fix text message UTF-8 validation --- demo-json-rpc-server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index cf4d374..6ae1742 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -711,7 +711,7 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser) return true; if (self->message_opcode == WS_OPCODE_TEXT - && !utf8_validate (self->parser.input.str, self->parser.input.len)) + && !utf8_validate (self->message_data.str, self->message_data.len)) { ws_handler_fail (self, WS_STATUS_INVALID_PAYLOAD_DATA); return false; From 329fc9b88fa419ef2c3a32ccc4414dec223bd69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Thu, 11 Oct 2018 21:03:34 +0200 Subject: [PATCH 51/79] Bump liberty Eliminates some fall-through warnings. --- liberty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liberty b/liberty index 8ffe20c..9494e8e 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit 8ffe20c0e83b52db1344fe91f57236be4c4cb504 +Subproject commit 9494e8e2affcd08b791d3ecf68985a8a3a310e55 From d182bcef3bbba9107cb808134d79ffe930be3e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Fri, 12 Oct 2018 19:59:17 +0200 Subject: [PATCH 52/79] More transient errors --- demo-json-rpc-server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 6ae1742..56b931d 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -2285,7 +2285,8 @@ on_client_available (EV_P_ ev_io *watcher, int revents) listener->create (EV_A_ sock_fd); else if (errno == EAGAIN) return; - else if (errno != EINTR && errno != ECONNABORTED) + else if (errno != EINTR && errno != EMFILE + && errno != ECONNRESET && errno != ECONNABORTED) break; } From fd17b4e5046b98b7be7bbc9133140cec2e24777d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Fri, 12 Oct 2018 23:07:06 +0200 Subject: [PATCH 53/79] Update code comments --- demo-json-rpc-server.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 56b931d..995e7e2 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -418,6 +418,7 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data) if (parser->type >= N_ELEMENTS (handlers) || !(handler = handlers[parser->type])) { + // Responding in this way even to application records, unspecified uint8_t content[8] = { parser->type }; fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id, content, sizeof content); @@ -1303,8 +1304,9 @@ json_rpc_handler_info_cmp (const void *first, const void *second) ((struct json_rpc_handler_info *) second)->method_name); } -// TODO: a method that queues up a ping over IRC: this has to be owned by the -// server context as a background job that removes itself upon completion. +// TODO: a method that sends a response after a certain number of seconds. +// This has to be owned by the server context as a background job that +// removes itself upon completion. static json_t * json_rpc_ping (struct server_context *ctx, json_t *params) @@ -1479,6 +1481,8 @@ request_finish (struct request *self) self->close_cb (self->user_data); } +/// Starts processing a request. Returns false if no further action is to be +/// done and the request should be finished. static bool request_start (struct request *self, struct str_map *headers) { @@ -1489,7 +1493,7 @@ request_start (struct request *self, struct str_map *headers) // // However that might cause some stuff to be done twice. // - // Another way we could get rid off the continue_ argument is via adding + // Another way we could get rid of the continue_ argument is via adding // some way of marking the request as finished from within the handler. bool continue_ = true; @@ -1551,6 +1555,9 @@ request_handler_json_rpc_push return true; } + // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the + // client hasn't been successful in transferring all of its data + struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", "application/json"); @@ -1845,6 +1852,8 @@ static void on_client_ready (EV_P_ ev_io *watcher, int revents) { struct client *client = watcher->data; + // XXX: although read and write are in a sequence, if we create response + // data, we'll still likely need to go back to the event loop. if (revents & EV_READ) if (!client_read_loop (EV_A_ client, watcher)) @@ -2488,6 +2497,9 @@ on_termination_signal (EV_P_ ev_signal *handle, int revents) (void) handle; (void) revents; + // TODO: consider quitting right away if already quitting; + // considering that this may already happen in timeout, it should be OK; + // see on_quit_timeout, just destroy all clients initiate_quit (ctx); } From 8b334e9c9187f4eb8bdbfde7644f82a66d30af00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Sat, 13 Oct 2018 04:08:09 +0200 Subject: [PATCH 54/79] Fix fcgi_muxer_send() Outgoing records were missing padding and the reserved field. --- demo-json-rpc-server.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 995e7e2..fa7f833 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -145,14 +145,18 @@ fcgi_muxer_send (struct fcgi_muxer *self, hard_assert (len <= UINT16_MAX); struct str message = str_make (); + static char zeroes[8]; + size_t padding = -len & 7; str_pack_u8 (&message, FCGI_VERSION_1); str_pack_u8 (&message, type); str_pack_u16 (&message, request_id); - str_pack_u16 (&message, len); // content length - str_pack_u8 (&message, 0); // padding length + str_pack_u16 (&message, len); // content length + str_pack_u8 (&message, padding); // padding length + str_pack_u8 (&message, 0); // reserved str_append_data (&message, data, len); + str_append_data (&message, zeroes, padding); // XXX: we should probably have another write_cb that assumes ownership self->write_cb (self->user_data, message.str, message.len); From 14ded260a0d8e364f2edb895e915c623e1ab7ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Mon, 15 Oct 2018 03:02:49 +0200 Subject: [PATCH 55/79] Clarify and degrade FastCGI multiplexing No need to support more than 255 concurrent requests on one connection. --- demo-json-rpc-server.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index fa7f833..aa56563 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -124,10 +124,6 @@ struct fcgi_muxer // TODO: bool quitting; that causes us to reject all requests? - /// Requests assigned to request IDs - // TODO: allocate this dynamically - struct fcgi_request *requests[1 << 16]; - void (*write_cb) (void *user_data, const void *data, size_t len); void (*close_cb) (void *user_data); @@ -136,6 +132,9 @@ struct fcgi_muxer void (*request_destroy_cb) (void *handler_data); void *user_data; ///< User data for callbacks + + /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID) + struct fcgi_request *requests[1 << 8]; }; static void @@ -284,18 +283,21 @@ fcgi_muxer_on_get_values nv_parser.output = &values; fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len); + const char *key = NULL; - struct str_map_iter iter = str_map_iter_make (&values); - while (str_map_iter_next (&iter)) - { - const char *key = iter.link->key; + // No real-world servers seem to actually use multiplexing + // or even issue this request, but we will implement it anyway + if (str_map_find (&values, (key = FCGI_MPXS_CONNS))) + str_map_set (&response, key, xstrdup ("1")); - // TODO: if (!strcmp (key, FCGI_MAX_CONNS)) - // TODO: if (!strcmp (key, FCGI_MAX_REQS)) + // It's not clear whether FCGI_MAX_REQS means concurrently over all + // connections or over just a single connection (multiplexed), though + // supposedly it's actually per /web server/. Supply the strictest limit. + if (str_map_find (&values, (key = FCGI_MAX_REQS))) + str_map_set (&response, key, + xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1)); - if (!strcmp (key, FCGI_MPXS_CONNS)) - str_map_set (&response, key, xstrdup ("1")); - } + // FCGI_MAX_CONNS would be basically infinity. We don't limit connections. struct str content = str_make (); fcgi_nv_convert (&response, &content); From 441c89f654d2f5d8d36c10a948d2ab5db87ffa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Mon, 15 Oct 2018 03:04:39 +0200 Subject: [PATCH 56/79] Handle FastCGI null request IDs better --- demo-json-rpc-server.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index aa56563..096d2a5 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -276,7 +276,14 @@ static void fcgi_muxer_on_get_values (struct fcgi_muxer *self, const struct fcgi_parser *parser) { - struct str_map values = str_map_make (free); + if (parser->request_id != FCGI_NULL_REQUEST_ID) + { + print_debug ("FastCGI: ignoring invalid %s message", + STRINGIFY (FCGI_GET_VALUES)); + return; + } + + struct str_map values = str_map_make (free); struct str_map response = str_map_make (free); struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make (); @@ -330,6 +337,13 @@ fcgi_muxer_on_begin_request return; } + struct fcgi_request *request = self->requests[parser->request_id]; + if (parser->request_id == FCGI_NULL_REQUEST_ID || request) + { + // TODO: fail + return; + } + // We can only act as a responder, reject everything else up front if (role != FCGI_RESPONDER) { @@ -338,10 +352,11 @@ fcgi_muxer_on_begin_request return; } - struct fcgi_request *request = self->requests[parser->request_id]; - if (request) + // TODO: also send OVERLOADED when shutting down? + if (parser->request_id >= N_ELEMENTS (self->requests)) { - // TODO: fail + fcgi_muxer_send_end_request (self, + parser->request_id, 0, FCGI_OVERLOADED); return; } @@ -359,21 +374,22 @@ fcgi_muxer_on_abort_request (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; - if (!request) + if (parser->request_id == FCGI_NULL_REQUEST_ID || !request) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_ABORT_REQUEST)); return; } - // TODO: abort the request: let it somehow produce FCGI_END_REQUEST + // TODO: abort the request: let it somehow produce FCGI_END_REQUEST, + // make sure to send an stdout EOF record } static void fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; - if (!request) + if (parser->request_id == FCGI_NULL_REQUEST_ID || !request) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_PARAMS)); @@ -388,7 +404,7 @@ static void fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; - if (!request) + if (parser->request_id == FCGI_NULL_REQUEST_ID || !request) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_STDIN)); From 267a9a561bdc252736a022f241b9de5458d66ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Mon, 15 Oct 2018 03:28:09 +0200 Subject: [PATCH 57/79] Eliminate some warnings --- demo-json-rpc-server.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 096d2a5..3698ade 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -769,7 +769,9 @@ ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents) static void ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) { + (void) revents; struct ws_handler *self = watcher->data; + // TODO: anything else to do here? Invalidate our state? if (self->close_cb) self->close_cb (self->user_data); @@ -778,6 +780,7 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) static void ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents) { + (void) revents; struct ws_handler *self = watcher->data; // TODO } @@ -1034,9 +1037,12 @@ ws_handler_finish_handshake (struct ws_handler *self) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); // Okay, we're finally past the basic HTTP/1.1 stuff - const char *key = str_map_find (&self->headers, SEC_WS_KEY); - const char *version = str_map_find (&self->headers, SEC_WS_VERSION); - const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL); + const char *key = str_map_find (&self->headers, SEC_WS_KEY); + const char *version = str_map_find (&self->headers, SEC_WS_VERSION); +/* + const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL); + const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS); +*/ if (!key) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); @@ -1063,7 +1069,7 @@ ws_handler_finish_handshake (struct ws_handler *self) xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key)); free (response_key); - // TODO: check and set Sec-Websocket-{Extensions,Protocol} + // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol} ws_handler_http_responsev (self, HTTP_101_SWITCHING_PROTOCOLS, fields.vector); From a14edb72e97084d8c5c16b7c5f37396091464103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Mon, 15 Oct 2018 05:07:57 +0200 Subject: [PATCH 58/79] Make Doxygen a bit more useful --- demo-json-rpc-server.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 3698ade..476187c 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -96,6 +96,8 @@ log_message_syslog (void *user_data, const char *quote, const char *fmt, } // --- FastCGI ----------------------------------------------------------------- +/// @defgroup FastCGI +/// @{ enum fcgi_request_state { @@ -470,7 +472,10 @@ fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len) fcgi_parser_push (&self->parser, data, len); } +/// @} // --- WebSockets -------------------------------------------------------------- +/// @defgroup WebSockets +/// @{ // WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP // handshake ourselves. Luckily it's not too much of a bother with http-parser. @@ -1169,6 +1174,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) return true; } +/// @} // --- Server ------------------------------------------------------------------ static struct simple_config_item g_config_table[] = @@ -1229,6 +1235,8 @@ server_context_free (struct server_context *self) } // --- JSON-RPC ---------------------------------------------------------------- +/// @defgroup JSON-RPC +/// @{ #define JSON_RPC_ERROR_TABLE(XX) \ XX (-32700, PARSE_ERROR, "Parse error") \ @@ -1451,7 +1459,10 @@ process_json_rpc (struct server_context *ctx, } } +/// @} // --- Requests ---------------------------------------------------------------- +/// @defgroup Requests +/// @{ struct request { @@ -1551,6 +1562,7 @@ request_push (struct request *self, const void *data, size_t len) return self->handler->push_cb (self, data, len); } +/// @} // --- Requests handlers ------------------------------------------------------- static bool From 7aff9c3475989278bf0c3bc69e6be499e78d59e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Mon, 15 Oct 2018 19:29:17 +0200 Subject: [PATCH 59/79] Improve documentation --- demo-json-rpc-server.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 476187c..9658e91 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -546,7 +546,7 @@ struct ws_handler // Actually, calling push() could work pretty fine for this. void (*on_close) (void *user_data, int close_code, const char *reason); - // Method callbacks: + // Virtual method callbacks: /// Write a chunk of data to the stream void (*write_cb) (void *user_data, const void *data, size_t len); @@ -1464,11 +1464,12 @@ process_json_rpc (struct server_context *ctx, /// @defgroup Requests /// @{ +/// A generic CGI request abstraction, writing data indirectly through callbacks struct request { struct server_context *ctx; ///< Server context - struct request_handler *handler; ///< Current request handler + struct request_handler *handler; ///< Assigned request handler void *handler_data; ///< User data for the handler /// Callback to write some CGI response data to the output @@ -1481,20 +1482,25 @@ struct request void *user_data; ///< User data argument for callbacks }; +/// An interface to detect and handle specific kinds of CGI requests. +/// The server walks through a list of them until it finds one that can serve +/// a particular request. If unsuccessful, the remote client gets a 404 +/// (the default handling). struct request_handler { LIST_HEADER (struct request_handler) - /// Install ourselves as the handler for the request if applicable. - /// Set @a continue_ to false if further processing should be stopped. + /// Install ourselves as the handler for the request, if applicable. + /// Sets @a continue_ to false if further processing should be stopped, + /// meaning the request has already been handled. bool (*try_handle) (struct request *request, struct str_map *headers, bool *continue_); /// Handle incoming data. - /// Return false if further processing should be stopped. + /// Returns false if there is no more processing to be done. bool (*push_cb) (struct request *request, const void *data, size_t len); - /// Destroy the handler + /// Destroy the handler's data stored in the request object void (*destroy_cb) (struct request *request); }; @@ -1792,6 +1798,8 @@ struct request_handler g_request_handler_static = // --- Client communication handlers ------------------------------------------- +/// A virtual class for client connections coming either from the web server +/// or directly from the end-client, depending on the protocol in use struct client { LIST_HEADER (struct client) @@ -1808,6 +1816,7 @@ struct client struct client_vtable *vtable; ///< Client behaviour }; +/// The concrete behaviour to serve a particular client's requests struct client_vtable { /// Attempt a graceful shutdown From dda22c2cd552d113db6afd789e38c5d436156d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Mon, 15 Oct 2018 20:38:48 +0200 Subject: [PATCH 60/79] Eliminate unnecessary user_data pointers The CONTAINER_OF macro can find the parent structure just as well. --- demo-json-rpc-server.c | 122 +++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 9658e91..baed884 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -44,6 +44,9 @@ enum { PIPE_READ, PIPE_WRITE }; +#define FIND_CONTAINER(name, pointer, type, member) \ + type *name = CONTAINER_OF (pointer, type, member) + // --- libev helpers ----------------------------------------------------------- static bool @@ -126,15 +129,13 @@ struct fcgi_muxer // TODO: bool quitting; that causes us to reject all requests? - void (*write_cb) (void *user_data, const void *data, size_t len); - void (*close_cb) (void *user_data); + void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len); + void (*close_cb) (struct fcgi_muxer *); - void *(*request_start_cb) (void *user_data, struct fcgi_request *request); + void *(*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *); void (*request_push_cb) (void *handler_data, const void *data, size_t len); void (*request_destroy_cb) (void *handler_data); - void *user_data; ///< User data for callbacks - /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID) struct fcgi_request *requests[1 << 8]; }; @@ -160,7 +161,7 @@ fcgi_muxer_send (struct fcgi_muxer *self, str_append_data (&message, zeroes, padding); // XXX: we should probably have another write_cb that assumes ownership - self->write_cb (self->user_data, message.str, message.len); + self->write_cb (self, message.str, message.len); str_free (&message); } @@ -210,8 +211,7 @@ fcgi_request_push_params { // TODO: probably check the state of the header parser // TODO: request_start() can return false, end the request here? - self->handler_data = self->muxer->request_start_cb - (self->muxer->user_data, self); + self->handler_data = self->muxer->request_start_cb (self->muxer, self); self->state = FCGI_REQUEST_STDIN; } } @@ -532,10 +532,10 @@ struct ws_handler // This may render "on_connected" unnecessary. /// Called after successfuly connecting (handshake complete) - bool (*on_connected) (void *user_data); + bool (*on_connected) (struct ws_handler *); /// Called upon reception of a single full message - bool (*on_message) (void *user_data, + bool (*on_message) (struct ws_handler *, enum ws_opcode type, const void *data, size_t len); /// The connection is about to close. @a close_code may, or may not, be one @@ -544,17 +544,15 @@ struct ws_handler // receive a notification about the connection being closed because of // an error (recv()) returns -1, and call on_close() in reaction. // Actually, calling push() could work pretty fine for this. - void (*on_close) (void *user_data, int close_code, const char *reason); + void (*on_close) (struct ws_handler *, int close_code, const char *reason); // Virtual method callbacks: /// Write a chunk of data to the stream - void (*write_cb) (void *user_data, const void *data, size_t len); + void (*write_cb) (struct ws_handler *, const void *data, size_t len); /// Close the connection - void (*close_cb) (void *user_data); - - void *user_data; ///< User data for callbacks + void (*close_cb) (struct ws_handler *); }; static void @@ -569,8 +567,8 @@ ws_handler_send_control (struct ws_handler *self, } uint8_t header[2] = { 0x80 | (opcode & 0x0F), len }; - self->write_cb (self->user_data, header, sizeof header); - self->write_cb (self->user_data, data, len); + self->write_cb (self, header, sizeof header); + self->write_cb (self, data, len); } static void @@ -585,7 +583,7 @@ ws_handler_close (struct ws_handler *self, // Close initiated by us; the reason is null-terminated within `payload' if (self->on_close) - self->on_close (self->user_data, close_code, payload.str + 2); + self->on_close (self, close_code, payload.str + 2); self->state = WS_HANDLER_CLOSING; str_free (&payload); @@ -628,8 +626,8 @@ ws_handler_send (struct ws_handler *self, else str_pack_u8 (&header, len); - self->write_cb (self->user_data, header.str, header.len); - self->write_cb (self->user_data, data, len); + self->write_cb (self, header.str, header.len); + self->write_cb (self, data, len); str_free (&header); } @@ -680,7 +678,7 @@ ws_handler_on_protocol_close ws_handler_send_control (self, WS_OPCODE_CLOSE, parser->input.str, parser->payload_len); if (self->on_close) - self->on_close (self->user_data, close_code, reason); + self->on_close (self, close_code, reason); } free (reason); @@ -747,7 +745,7 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser) bool result = true; if (self->on_message) - result = self->on_message (self->user_data, self->message_opcode, + result = self->on_message (self, self->message_opcode, self->message_data.str, self->message_data.len); str_reset (&self->message_data); return result; @@ -779,7 +777,7 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) // TODO: anything else to do here? Invalidate our state? if (self->close_cb) - self->close_cb (self->user_data); + self->close_cb (self); } static void @@ -971,7 +969,7 @@ ws_handler_http_responsev (struct ws_handler *self, str_append (&response, "Server: " PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n"); - self->write_cb (self->user_data, response.str, response.len); + self->write_cb (self, response.str, response.len); str_free (&response); } @@ -1110,8 +1108,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) if (self->state == WS_HANDLER_OPEN) { if (self->on_close) - self->on_close (self->user_data, - WS_STATUS_ABNORMAL_CLOSURE, ""); + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, ""); } else { @@ -1154,7 +1151,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) self->state = WS_HANDLER_OPEN; if (self->on_connected) - return self->on_connected (self->user_data); + return self->on_connected (self); return true; } @@ -1473,13 +1470,11 @@ struct request void *handler_data; ///< User data for the handler /// Callback to write some CGI response data to the output - void (*write_cb) (void *user_data, const void *data, size_t len); + void (*write_cb) (struct request *, const void *data, size_t len); /// Callback to close the connection. /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED. - void (*close_cb) (void *user_data); - - void *user_data; ///< User data argument for callbacks + void (*close_cb) (struct request *); }; /// An interface to detect and handle specific kinds of CGI requests. @@ -1523,7 +1518,7 @@ request_free (struct request *self) static void request_finish (struct request *self) { - self->close_cb (self->user_data); + self->close_cb (self); } /// Starts processing a request. Returns false if no further action is to be @@ -1553,7 +1548,7 @@ request_start (struct request *self, struct str_map *headers) struct str response = str_make (); str_append (&response, "Status: 404 Not Found\n"); str_append (&response, "Content-Type: text/plain\n\n"); - self->write_cb (self->user_data, response.str, response.len); + self->write_cb (self, response.str, response.len); str_free (&response); return false; } @@ -1608,7 +1603,7 @@ request_handler_json_rpc_push str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", "application/json"); process_json_rpc (request->ctx, buf->str, buf->len, &response); - request->write_cb (request->user_data, response.str, response.len); + request->write_cb (request, response.str, response.len); str_free (&response); return false; } @@ -1724,7 +1719,7 @@ request_handler_static_try_handle str_append (&response, "Content-Type: text/plain\n\n"); str_append_printf (&response, "File %s was not found on this server\n", suffix); - request->write_cb (request->user_data, response.str, response.len); + request->write_cb (request, response.str, response.len); str_free (&response); free (suffix); @@ -1748,17 +1743,17 @@ request_handler_static_try_handle struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", mime_type); - request->write_cb (request->user_data, response.str, response.len); + request->write_cb (request, response.str, response.len); str_free (&response); free (mime_type); // Write the chunk we've used to help us with magic detection; // obviously we have to do it after we've written the headers if (len) - request->write_cb (request->user_data, buf, len); + request->write_cb (request, buf, len); while ((len = fread (buf, 1, sizeof buf, fp))) - request->write_cb (request->user_data, buf, len); + request->write_cb (request, buf, len); fclose (fp); // TODO: this should rather not be returned all at once but in chunks; @@ -1962,28 +1957,29 @@ struct client_fcgi_request // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -client_fcgi_request_write_cb (void *user_data, const void *data, size_t len) +client_fcgi_request_write_cb (struct request *req, const void *data, size_t len) { - struct client_fcgi_request *request = user_data; - fcgi_request_write (request->fcgi_request, data, len); + FIND_CONTAINER (self, req, struct client_fcgi_request, request); + fcgi_request_write (self->fcgi_request, data, len); } static void -client_fcgi_request_close_cb (void *user_data) +client_fcgi_request_close_cb (struct request *req) { - struct client_fcgi_request *request = user_data; + FIND_CONTAINER (self, req, struct client_fcgi_request, request); // No more data to send, terminate the substream/request // XXX: this will most probably end up with client_fcgi_request_destroy(), // we might or might not need to defer this action - fcgi_request_finish (request->fcgi_request); + fcgi_request_finish (self->fcgi_request); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void * -client_fcgi_request_start (void *user_data, struct fcgi_request *fcgi_request) +client_fcgi_request_start + (struct fcgi_muxer *mux, struct fcgi_request *fcgi_request) { - struct client_fcgi *self = user_data; + FIND_CONTAINER (self, mux, struct client_fcgi, muxer); // TODO: what if the request is aborted by ; struct client_fcgi_request *request = xcalloc (1, sizeof *request); @@ -1992,7 +1988,6 @@ client_fcgi_request_start (void *user_data, struct fcgi_request *fcgi_request) request->request.ctx = self->client.ctx; request->request.write_cb = client_fcgi_request_write_cb; request->request.close_cb = client_fcgi_request_close_cb; - request->request.user_data = request; return request; } @@ -2014,16 +2009,16 @@ client_fcgi_request_destroy (void *handler_data) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -client_fcgi_write_cb (void *user_data, const void *data, size_t len) +client_fcgi_write_cb (struct fcgi_muxer *mux, const void *data, size_t len) { - struct client_fcgi *self = user_data; + FIND_CONTAINER (self, mux, struct client_fcgi, muxer); client_write (&self->client, data, len); } static void -client_fcgi_close_cb (void *user_data) +client_fcgi_close_cb (struct fcgi_muxer *mux) { - struct client_fcgi *self = user_data; + FIND_CONTAINER (self, mux, struct client_fcgi, muxer); // FIXME: we should probably call something like client_shutdown(), // which may have an argument whether we should really use close() client_destroy (&self->client); @@ -2076,7 +2071,6 @@ client_fcgi_create (EV_P_ int sock_fd) self->muxer.request_start_cb = client_fcgi_request_start; self->muxer.request_push_cb = client_fcgi_request_push; self->muxer.request_destroy_cb = client_fcgi_request_destroy; - self->muxer.user_data = self; return &self->client; } @@ -2090,17 +2084,17 @@ struct client_scgi }; static void -client_scgi_write_cb (void *user_data, const void *data, size_t len) +client_scgi_write_cb (struct request *req, const void *data, size_t len) { - struct client_scgi *self = user_data; + FIND_CONTAINER (self, req, struct client_scgi, request); client_write (&self->client, data, len); } static void -client_scgi_close_cb (void *user_data) +client_scgi_close_cb (struct request *req) { // NOTE: this rather really means "close me [the request]" - struct client_scgi *self = user_data; + FIND_CONTAINER (self, req, struct client_scgi, request); // FIXME: we should probably call something like client_shutdown(), // which may have an argument whether we should really use close() client_destroy (&self->client); @@ -2167,7 +2161,6 @@ client_scgi_create (EV_P_ int sock_fd) self->request.ctx = self->client.ctx; self->request.write_cb = client_scgi_write_cb; self->request.close_cb = client_scgi_close_cb; - self->request.user_data = self; self->parser = scgi_parser_make (); self->parser.on_headers_read = client_scgi_on_headers_read; @@ -2185,10 +2178,10 @@ struct client_ws }; static bool -client_ws_on_message (void *user_data, +client_ws_on_message (struct ws_handler *handler, enum ws_opcode type, const void *data, size_t len) { - struct client_ws *self = user_data; + FIND_CONTAINER (self, handler, struct client_ws, handler); if (type != WS_OPCODE_TEXT) { ws_handler_fail (&self->handler, WS_STATUS_UNSUPPORTED_DATA); @@ -2205,16 +2198,16 @@ client_ws_on_message (void *user_data, } static void -client_ws_write_cb (void *user_data, const void *data, size_t len) +client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len) { - struct client *client = user_data; - client_write (client, data, len); + FIND_CONTAINER (self, handler, struct client_ws, handler); + client_write (&self->client, data, len); } static void -client_ws_close_cb (void *user_data) +client_ws_close_cb (struct ws_handler *handler) { - struct client_ws *self = user_data; + FIND_CONTAINER (self, handler, struct client_ws, handler); // FIXME: we should probably call something like client_shutdown(), // which may have an argument whether we should really use close() client_destroy (&self->client); @@ -2261,7 +2254,6 @@ client_ws_create (EV_P_ int sock_fd) self->handler.on_message = client_ws_on_message; self->handler.write_cb = client_ws_write_cb; self->handler.close_cb = client_ws_close_cb; - self->handler.user_data = self; // One mebibyte seems to be a reasonable value self->handler.max_payload_len = 1 << 10; From e9530f450e64947850da0ed7596c8611cf445efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Tue, 16 Oct 2018 00:18:13 +0200 Subject: [PATCH 61/79] Call ws_handler_start() --- demo-json-rpc-server.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index baed884..2c487f5 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -2257,6 +2257,8 @@ client_ws_create (EV_P_ int sock_fd) // One mebibyte seems to be a reasonable value self->handler.max_payload_len = 1 << 10; + + ws_handler_start (&self->handler); return &self->client; } From 7cefdd496f8e45a5d4ee54b7c6cfc5283ae32d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Tue, 16 Oct 2018 04:05:42 +0200 Subject: [PATCH 62/79] Cleanup --- demo-json-rpc-server.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 2c487f5..4ec7e4c 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -2218,21 +2218,21 @@ client_ws_close_cb (struct ws_handler *handler) static void client_ws_shutdown (struct client *client) { - struct client_ws *self = (struct client_ws *) client; + FIND_CONTAINER (self, client, struct client_ws, client); ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0); } static void client_ws_destroy (struct client *client) { - struct client_ws *self = (struct client_ws *) client; + FIND_CONTAINER (self, client, struct client_ws, client); ws_handler_free (&self->handler); } static bool client_ws_push (struct client *client, const void *data, size_t len) { - struct client_ws *self = (struct client_ws *) client; + FIND_CONTAINER (self, client, struct client_ws, client); return ws_handler_push (&self->handler, data, len); } From 7f6db9d39f7a65243992b3a01eacedb4b2be279e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Tue, 16 Oct 2018 04:09:59 +0200 Subject: [PATCH 63/79] Improve WebSocket shutdown --- demo-json-rpc-server.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 4ec7e4c..4bf1fcc 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -540,10 +540,6 @@ struct ws_handler /// The connection is about to close. @a close_code may, or may not, be one /// of enum ws_status. The @a reason is never NULL. - // TODO; also note that ideally, the handler should (be able to) first - // receive a notification about the connection being closed because of - // an error (recv()) returns -1, and call on_close() in reaction. - // Actually, calling push() could work pretty fine for this. void (*on_close) (struct ws_handler *, int close_code, const char *reason); // Virtual method callbacks: @@ -551,8 +547,9 @@ struct ws_handler /// Write a chunk of data to the stream void (*write_cb) (struct ws_handler *, const void *data, size_t len); - /// Close the connection - void (*close_cb) (struct ws_handler *); + /// Close the connection. If @a half_close is false, you are allowed to + /// destroy the handler directly from within the callback. + void (*close_cb) (struct ws_handler *, bool half_close); }; static void @@ -777,7 +774,7 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) // TODO: anything else to do here? Invalidate our state? if (self->close_cb) - self->close_cb (self); + self->close_cb (self, false /* half_close */); } static void @@ -2205,12 +2202,13 @@ client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len) } static void -client_ws_close_cb (struct ws_handler *handler) +client_ws_close_cb (struct ws_handler *handler, bool half_close) { FIND_CONTAINER (self, handler, struct client_ws, handler); - // FIXME: we should probably call something like client_shutdown(), - // which may have an argument whether we should really use close() - client_destroy (&self->client); + if (half_close) + ; // FIXME: we should probably call something like client_shutdown() + else + client_destroy (&self->client); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2219,7 +2217,10 @@ static void client_ws_shutdown (struct client *client) { FIND_CONTAINER (self, client, struct client_ws, client); - ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0); + if (self->handler.state == WS_HANDLER_CONNECTING) + ; // TODO: abort the connection immediately + else if (self->handler.state == WS_HANDLER_OPEN) + ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0); } static void From 7d922352ead78b92247d0848572a7e9c50ae1075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Tue, 16 Oct 2018 04:12:37 +0200 Subject: [PATCH 64/79] Rename client_vtable::destroy to finalize Matches a similar concept from garbage-collected languages. --- demo-json-rpc-server.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 4bf1fcc..f241030 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -1814,9 +1814,8 @@ struct client_vtable /// Attempt a graceful shutdown void (*shutdown) (struct client *client); - /// Do any additional cleanup - // TODO: rename to "finalize" or "cleanup"? - void (*destroy) (struct client *client); + /// Do any additional cleanup for the concrete class before destruction + void (*finalize) (struct client *client); /// Process incoming data; "len == 0" means EOF bool (*push) (struct client *client, const void *data, size_t len); @@ -1848,7 +1847,7 @@ client_destroy (struct client *self) ctx->n_clients--; // First uninitialize the higher-level implementation - self->vtable->destroy (self); + self->vtable->finalize (self); ev_io_stop (EV_DEFAULT_ &self->read_watcher); ev_io_stop (EV_DEFAULT_ &self->write_watcher); @@ -2034,7 +2033,7 @@ client_fcgi_shutdown (struct client *client) } static void -client_fcgi_destroy (struct client *client) +client_fcgi_finalize (struct client *client) { struct client_fcgi *self = (struct client_fcgi *) client; fcgi_muxer_free (&self->muxer); @@ -2051,7 +2050,7 @@ client_fcgi_push (struct client *client, const void *data, size_t len) static struct client_vtable client_fcgi_vtable = { .shutdown = client_fcgi_shutdown, - .destroy = client_fcgi_destroy, + .finalize = client_fcgi_finalize, .push = client_fcgi_push, }; @@ -2118,7 +2117,7 @@ client_scgi_on_content (void *user_data, const void *data, size_t len) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -client_scgi_destroy (struct client *client) +client_scgi_finalize (struct client *client) { struct client_scgi *self = (struct client_scgi *) client; request_free (&self->request); @@ -2143,8 +2142,8 @@ client_scgi_push (struct client *client, const void *data, size_t len) static struct client_vtable client_scgi_vtable = { - .destroy = client_scgi_destroy, - .push = client_scgi_push, + .finalize = client_scgi_finalize, + .push = client_scgi_push, }; static struct client * @@ -2224,7 +2223,7 @@ client_ws_shutdown (struct client *client) } static void -client_ws_destroy (struct client *client) +client_ws_finalize (struct client *client) { FIND_CONTAINER (self, client, struct client_ws, client); ws_handler_free (&self->handler); @@ -2240,7 +2239,7 @@ client_ws_push (struct client *client, const void *data, size_t len) static struct client_vtable client_ws_vtable = { .shutdown = client_ws_shutdown, - .destroy = client_ws_destroy, + .finalize = client_ws_finalize, .push = client_ws_push, }; From 4c54bc42b9fdb83d439dc974953f06f5def3ae5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Tue, 16 Oct 2018 04:33:33 +0200 Subject: [PATCH 65/79] Clean up and better document client_vtable --- demo-json-rpc-server.c | 65 +++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index f241030..d461691 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -1811,14 +1811,19 @@ struct client /// The concrete behaviour to serve a particular client's requests struct client_vtable { - /// Attempt a graceful shutdown + /// Process incoming data; "len == 0" means EOF. + /// If the method returns false, the client is destroyed by caller. + bool (*push) (struct client *client, const void *data, size_t len); + + // TODO: optional push_error() to inform about network I/O errors + + /// Attempt a graceful shutdown: make any appropriate steps before + /// the client connection times out and gets torn down by force. + /// The client is allowed to destroy itself immediately. void (*shutdown) (struct client *client); /// Do any additional cleanup for the concrete class before destruction void (*finalize) (struct client *client); - - /// Process incoming data; "len == 0" means EOF - bool (*push) (struct client *client, const void *data, size_t len); }; static void @@ -2022,6 +2027,14 @@ client_fcgi_close_cb (struct fcgi_muxer *mux) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static bool +client_fcgi_push (struct client *client, const void *data, size_t len) +{ + struct client_fcgi *self = (struct client_fcgi *) client; + fcgi_muxer_push (&self->muxer, data, len); + return true; +} + static void client_fcgi_shutdown (struct client *client) { @@ -2039,19 +2052,11 @@ client_fcgi_finalize (struct client *client) fcgi_muxer_free (&self->muxer); } -static bool -client_fcgi_push (struct client *client, const void *data, size_t len) -{ - struct client_fcgi *self = (struct client_fcgi *) client; - fcgi_muxer_push (&self->muxer, data, len); - return true; -} - static struct client_vtable client_fcgi_vtable = { + .push = client_fcgi_push, .shutdown = client_fcgi_shutdown, .finalize = client_fcgi_finalize, - .push = client_fcgi_push, }; static struct client * @@ -2116,14 +2121,6 @@ client_scgi_on_content (void *user_data, const void *data, size_t len) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -client_scgi_finalize (struct client *client) -{ - struct client_scgi *self = (struct client_scgi *) client; - request_free (&self->request); - scgi_parser_free (&self->parser); -} - static bool client_scgi_push (struct client *client, const void *data, size_t len) { @@ -2140,10 +2137,18 @@ client_scgi_push (struct client *client, const void *data, size_t len) return false; } +static void +client_scgi_finalize (struct client *client) +{ + struct client_scgi *self = (struct client_scgi *) client; + request_free (&self->request); + scgi_parser_free (&self->parser); +} + static struct client_vtable client_scgi_vtable = { - .finalize = client_scgi_finalize, .push = client_scgi_push, + .finalize = client_scgi_finalize, }; static struct client * @@ -2212,6 +2217,13 @@ client_ws_close_cb (struct ws_handler *handler, bool half_close) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static bool +client_ws_push (struct client *client, const void *data, size_t len) +{ + FIND_CONTAINER (self, client, struct client_ws, client); + return ws_handler_push (&self->handler, data, len); +} + static void client_ws_shutdown (struct client *client) { @@ -2229,18 +2241,11 @@ client_ws_finalize (struct client *client) ws_handler_free (&self->handler); } -static bool -client_ws_push (struct client *client, const void *data, size_t len) -{ - FIND_CONTAINER (self, client, struct client_ws, client); - return ws_handler_push (&self->handler, data, len); -} - static struct client_vtable client_ws_vtable = { + .push = client_ws_push, .shutdown = client_ws_shutdown, .finalize = client_ws_finalize, - .push = client_ws_push, }; static struct client * From 272145ace2d7d4bd0342cce1890410d20d861e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 02:21:19 +0200 Subject: [PATCH 66/79] Clarify EOF behaviour --- demo-json-rpc-server.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index d461691..c886364 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -413,6 +413,7 @@ fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser) return; } + // At the end of the stream, a zero-length record is received fcgi_request_push_stdin (request, parser->content.str, parser->content.len); } @@ -1092,7 +1093,7 @@ ws_handler_start (struct ws_handler *self) ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher); } -/// Push data to the WebSocket handler; "len == 0" means EOF +/// Push data to the WebSocket handler. "len == 0" means EOF. static bool ws_handler_push (struct ws_handler *self, const void *data, size_t len) { @@ -1488,7 +1489,7 @@ struct request_handler bool (*try_handle) (struct request *request, struct str_map *headers, bool *continue_); - /// Handle incoming data. + /// Handle incoming data. "len == 0" means EOF. /// Returns false if there is no more processing to be done. bool (*push_cb) (struct request *request, const void *data, size_t len); @@ -1768,10 +1769,10 @@ request_handler_static_push { (void) request; (void) data; - (void) len; - // Ignoring all content; we shouldn't receive any (GET) - return false; + // Aborting on content; we shouldn't receive any (GET) + // FIXME: there should at least be some indication of this happening + return len == 0; } static void From 83363e6383a7589d2a691ca1f988acb38ccea5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 02:57:08 +0200 Subject: [PATCH 67/79] FastCGI: make it work at least in theory --- demo-json-rpc-server.c | 104 +++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index c886364..a915b3d 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -123,18 +123,32 @@ struct fcgi_request void *handler_data; ///< Handler data }; +/// Handles a single FastCGI connection, de/multiplexing requests and responses struct fcgi_muxer { struct fcgi_parser parser; ///< FastCGI message parser + uint32_t active_requests; ///< The number of active requests + bool in_shutdown; ///< Rejecting new requests - // TODO: bool quitting; that causes us to reject all requests? + // Virtual method callbacks: + /// Write data to the underlying transport void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len); + + /// Close the underlying transport + // TODO: consider half-close and the subsequent handling void (*close_cb) (struct fcgi_muxer *); - void *(*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *); - void (*request_push_cb) (void *handler_data, const void *data, size_t len); - void (*request_destroy_cb) (void *handler_data); + /// Start processing a request. Return false if no further action is + /// to be done and the request should be finished. + bool (*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *); + + /// Handle incoming data. "len == 0" means EOF. + void (*request_push_cb) + (struct fcgi_request *, const void *data, size_t len); + + /// Destroy the handler's data stored in the request object + void (*request_destroy_cb) (struct fcgi_request *); /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID) struct fcgi_request *requests[1 << 8]; @@ -177,22 +191,27 @@ fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -fcgi_request_init (struct fcgi_request *self) +static struct fcgi_request * +fcgi_request_new (void) { - memset (self, 0, sizeof *self); + struct fcgi_request *self = xcalloc (1, sizeof *self); self->headers = str_map_make (free); self->hdr_parser = fcgi_nv_parser_make (); self->hdr_parser.output = &self->headers; + return self; } static void -fcgi_request_free (struct fcgi_request *self) +fcgi_request_destroy (struct fcgi_request *self) { + // TODO: consider the case where it hasn't been started yet + self->muxer->request_destroy_cb (self); + str_map_free (&self->headers); fcgi_nv_parser_free (&self->hdr_parser); + free (self); } static void @@ -211,7 +230,7 @@ fcgi_request_push_params { // TODO: probably check the state of the header parser // TODO: request_start() can return false, end the request here? - self->handler_data = self->muxer->request_start_cb (self->muxer, self); + (void) self->muxer->request_start_cb (self->muxer, self); self->state = FCGI_REQUEST_STDIN; } } @@ -226,7 +245,7 @@ fcgi_request_push_stdin return; } - self->muxer->request_push_cb (self->handler_data, data, len); + self->muxer->request_push_cb (self, data, len); } static void @@ -266,7 +285,21 @@ fcgi_request_write (struct fcgi_request *self, const void *data, size_t len) static void fcgi_request_finish (struct fcgi_request *self) { - // TODO: flush(), end_request(), delete self, muxer->request_destroy_cb()? + fcgi_request_flush (self); + fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0); + + fcgi_muxer_send_end_request (self->muxer, self->request_id, + 0 /* TODO app_status, although ignored */, + FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */); + + if (!(self->flags & FCGI_KEEP_CONN)) + { + // TODO: tear down (shut down) the connection + } + + self->muxer->active_requests--; + self->muxer->requests[self->request_id] = NULL; + fcgi_request_destroy (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -354,21 +387,21 @@ fcgi_muxer_on_begin_request return; } - // TODO: also send OVERLOADED when shutting down? - if (parser->request_id >= N_ELEMENTS (self->requests)) + if (parser->request_id >= N_ELEMENTS (self->requests) + || self->in_shutdown) { fcgi_muxer_send_end_request (self, parser->request_id, 0, FCGI_OVERLOADED); return; } - request = xcalloc (1, sizeof *request); - fcgi_request_init (request); + request = fcgi_request_new (); request->muxer = self; request->request_id = parser->request_id; request->flags = flags; self->requests[parser->request_id] = request; + self->active_requests++; } static void @@ -464,6 +497,17 @@ fcgi_muxer_init (struct fcgi_muxer *self) static void fcgi_muxer_free (struct fcgi_muxer *self) { + for (size_t i = 0; i < N_ELEMENTS (self->requests); i++) + { + if (!self->active_requests) + break; + if (self->requests[i]) + { + fcgi_request_destroy (self->requests[i]); + self->active_requests--; + } + } + fcgi_parser_free (&self->parser); } @@ -1977,33 +2021,35 @@ client_fcgi_request_close_cb (struct request *req) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void * +static bool client_fcgi_request_start (struct fcgi_muxer *mux, struct fcgi_request *fcgi_request) { FIND_CONTAINER (self, mux, struct client_fcgi, muxer); - // TODO: what if the request is aborted by ; - struct client_fcgi_request *request = xcalloc (1, sizeof *request); + struct client_fcgi_request *request = + fcgi_request->handler_data = xcalloc (1, sizeof *request); request->fcgi_request = fcgi_request; request_init (&request->request); request->request.ctx = self->client.ctx; request->request.write_cb = client_fcgi_request_write_cb; request->request.close_cb = client_fcgi_request_close_cb; - return request; + + return request_start (&request->request, &fcgi_request->headers); } static void -client_fcgi_request_push (void *handler_data, const void *data, size_t len) +client_fcgi_request_push + (struct fcgi_request *req, const void *data, size_t len) { - struct client_fcgi_request *request = handler_data; + struct client_fcgi_request *request = req->handler_data; request_push (&request->request, data, len); } static void -client_fcgi_request_destroy (void *handler_data) +client_fcgi_request_destroy (struct fcgi_request *req) { - struct client_fcgi_request *request = handler_data; + struct client_fcgi_request *request = req->handler_data; request_free (&request->request); free (request); } @@ -2031,7 +2077,7 @@ client_fcgi_close_cb (struct fcgi_muxer *mux) static bool client_fcgi_push (struct client *client, const void *data, size_t len) { - struct client_fcgi *self = (struct client_fcgi *) client; + FIND_CONTAINER (self, client, struct client_fcgi, client); fcgi_muxer_push (&self->muxer, data, len); return true; } @@ -2039,17 +2085,17 @@ client_fcgi_push (struct client *client, const void *data, size_t len) static void client_fcgi_shutdown (struct client *client) { - struct client_fcgi *self = (struct client_fcgi *) client; + FIND_CONTAINER (self, client, struct client_fcgi, client); + self->muxer.in_shutdown = true; - // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything, - // and start sending out FCGI_OVERLOADED to all incoming requests. The - // FastCGI specification isn't very clear about what we should do. + // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything? + // The FastCGI specification isn't very clear about what we should do. } static void client_fcgi_finalize (struct client *client) { - struct client_fcgi *self = (struct client_fcgi *) client; + FIND_CONTAINER (self, client, struct client_fcgi, client); fcgi_muxer_free (&self->muxer); } From 1d638c9170c2f785000aa1e7fff30974ae9be3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 03:53:07 +0200 Subject: [PATCH 68/79] Say "finalize" instead of "destroy" where appropriate - _make() returns a struct directly - _init() initializes over a pointer - _free() deinitializes over a pointer - _new() is like _init() but also allocates - _destroy() is like _free() but also deallocates Finalization is a matching concept in garbage-collected languages. --- demo-json-rpc-server.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index a915b3d..8ad1c36 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -148,7 +148,7 @@ struct fcgi_muxer (struct fcgi_request *, const void *data, size_t len); /// Destroy the handler's data stored in the request object - void (*request_destroy_cb) (struct fcgi_request *); + void (*request_finalize_cb) (struct fcgi_request *); /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID) struct fcgi_request *requests[1 << 8]; @@ -207,7 +207,7 @@ static void fcgi_request_destroy (struct fcgi_request *self) { // TODO: consider the case where it hasn't been started yet - self->muxer->request_destroy_cb (self); + self->muxer->request_finalize_cb (self); str_map_free (&self->headers); fcgi_nv_parser_free (&self->hdr_parser); @@ -1538,7 +1538,7 @@ struct request_handler bool (*push_cb) (struct request *request, const void *data, size_t len); /// Destroy the handler's data stored in the request object - void (*destroy_cb) (struct request *request); + void (*finalize_cb) (struct request *request); }; static void @@ -1551,7 +1551,7 @@ static void request_free (struct request *self) { if (self->handler) - self->handler->destroy_cb (self); + self->handler->finalize_cb (self); } /// This function is only intended to be run from asynchronous event handlers @@ -1651,7 +1651,7 @@ request_handler_json_rpc_push } static void -request_handler_json_rpc_destroy (struct request *request) +request_handler_json_rpc_finalize (struct request *request) { struct str *buf = request->handler_data; str_free (buf); @@ -1662,9 +1662,9 @@ request_handler_json_rpc_destroy (struct request *request) struct request_handler g_request_handler_json_rpc = { - .try_handle = request_handler_json_rpc_try_handle, - .push_cb = request_handler_json_rpc_push, - .destroy_cb = request_handler_json_rpc_destroy, + .try_handle = request_handler_json_rpc_try_handle, + .push_cb = request_handler_json_rpc_push, + .finalize_cb = request_handler_json_rpc_finalize, }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1820,7 +1820,7 @@ request_handler_static_push } static void -request_handler_static_destroy (struct request *request) +request_handler_static_finalize (struct request *request) { (void) request; // Nothing to dispose of this far @@ -1828,9 +1828,9 @@ request_handler_static_destroy (struct request *request) struct request_handler g_request_handler_static = { - .try_handle = request_handler_static_try_handle, - .push_cb = request_handler_static_push, - .destroy_cb = request_handler_static_destroy, + .try_handle = request_handler_static_try_handle, + .push_cb = request_handler_static_push, + .finalize_cb = request_handler_static_finalize, }; // --- Client communication handlers ------------------------------------------- @@ -2047,7 +2047,7 @@ client_fcgi_request_push } static void -client_fcgi_request_destroy (struct fcgi_request *req) +client_fcgi_request_finalize (struct fcgi_request *req) { struct client_fcgi_request *request = req->handler_data; request_free (&request->request); @@ -2114,11 +2114,11 @@ client_fcgi_create (EV_P_ int sock_fd) self->client.vtable = &client_fcgi_vtable; fcgi_muxer_init (&self->muxer); - self->muxer.write_cb = client_fcgi_write_cb; - self->muxer.close_cb = client_fcgi_close_cb; - self->muxer.request_start_cb = client_fcgi_request_start; - self->muxer.request_push_cb = client_fcgi_request_push; - self->muxer.request_destroy_cb = client_fcgi_request_destroy; + self->muxer.write_cb = client_fcgi_write_cb; + self->muxer.close_cb = client_fcgi_close_cb; + self->muxer.request_start_cb = client_fcgi_request_start; + self->muxer.request_push_cb = client_fcgi_request_push; + self->muxer.request_finalize_cb = client_fcgi_request_finalize; return &self->client; } From 13892fcd0e2f95b8033b92838996b0b700905652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 05:16:17 +0200 Subject: [PATCH 69/79] Clean up client de/allocation --- demo-json-rpc-server.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 8ad1c36..ce50c0f 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -1871,12 +1871,6 @@ struct client_vtable void (*finalize) (struct client *client); }; -static void -client_free (struct client *self) -{ - write_queue_free (&self->write_queue); -} - static void client_write (struct client *self, const void *data, size_t len) { @@ -1902,7 +1896,7 @@ client_destroy (struct client *self) ev_io_stop (EV_DEFAULT_ &self->read_watcher); ev_io_stop (EV_DEFAULT_ &self->write_watcher); xclose (self->socket_fd); - client_free (self); + write_queue_free (&self->write_queue); free (self); try_finish_quit (ctx); @@ -1962,12 +1956,14 @@ close: client_destroy (client); } -static void -client_init (EV_P_ struct client *self, int sock_fd) +/// Create a new instance of a subclass with the given size. +/// The superclass is assumed to be the first member of the structure. +static void * +client_new (EV_P_ size_t size, int sock_fd) { struct server_context *ctx = ev_userdata (loop); + struct client *self = xcalloc (1, size); - memset (self, 0, sizeof *self); self->ctx = ctx; self->write_queue = write_queue_make (); @@ -1984,6 +1980,7 @@ client_init (EV_P_ struct client *self, int sock_fd) LIST_PREPEND (ctx->clients, self); ctx->n_clients++; + return self; } // --- FastCGI client handler -------------------------------------------------- @@ -2109,8 +2106,7 @@ static struct client_vtable client_fcgi_vtable = static struct client * client_fcgi_create (EV_P_ int sock_fd) { - struct client_fcgi *self = xcalloc (1, sizeof *self); - client_init (EV_A_ &self->client, sock_fd); + struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_fcgi_vtable; fcgi_muxer_init (&self->muxer); @@ -2201,8 +2197,7 @@ static struct client_vtable client_scgi_vtable = static struct client * client_scgi_create (EV_P_ int sock_fd) { - struct client_scgi *self = xcalloc (1, sizeof *self); - client_init (EV_A_ &self->client, sock_fd); + struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_scgi_vtable; request_init (&self->request); @@ -2298,8 +2293,7 @@ static struct client_vtable client_ws_vtable = static struct client * client_ws_create (EV_P_ int sock_fd) { - struct client_ws *self = xcalloc (1, sizeof *self); - client_init (EV_A_ &self->client, sock_fd); + struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_ws_vtable; ws_handler_init (&self->handler); From efd500ca3cf4dfdc635ccd71ea8baf5a0d3c5bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 06:08:11 +0200 Subject: [PATCH 70/79] Accelerated daemon quitting --- demo-json-rpc-server.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index ce50c0f..cde6ad0 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -2586,10 +2586,14 @@ on_termination_signal (EV_P_ ev_signal *handle, int revents) (void) handle; (void) revents; - // TODO: consider quitting right away if already quitting; - // considering that this may already happen in timeout, it should be OK; - // see on_quit_timeout, just destroy all clients - initiate_quit (ctx); + if (ctx->quitting) + { + // Double C-c from the terminal accelerates the process + LIST_FOR_EACH (struct client, iter, ctx->clients) + client_destroy (iter); + } + else + initiate_quit (ctx); } static void From a3ec0942f8063509a00695d6e702a7f914caa7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 08:40:18 +0200 Subject: [PATCH 71/79] Implement basic connection teardown I finally understand the codebase again. It's rather complicated. --- demo-json-rpc-server.c | 238 +++++++++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 92 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index cde6ad0..563d11c 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -47,10 +47,10 @@ enum { PIPE_READ, PIPE_WRITE }; #define FIND_CONTAINER(name, pointer, type, member) \ type *name = CONTAINER_OF (pointer, type, member) -// --- libev helpers ----------------------------------------------------------- +// --- Utilities --------------------------------------------------------------- static bool -flush_queue (struct write_queue *queue, ev_io *watcher) +flush_queue (struct write_queue *queue, int fd) { struct iovec vec[queue->len], *vec_iter = vec; LIST_FOR_EACH (struct write_req, iter, queue->head) @@ -58,24 +58,17 @@ flush_queue (struct write_queue *queue, ev_io *watcher) ssize_t written; again: - written = writev (watcher->fd, vec, N_ELEMENTS (vec)); - if (written < 0) + if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0) { - if (errno == EAGAIN) - goto skip; - if (errno == EINTR) - goto again; - return false; + write_queue_processed (queue, written); + return true; } + if (errno == EINTR) + goto again; + if (errno == EAGAIN) + return true; - write_queue_processed (queue, written); - -skip: - if (write_queue_is_empty (queue)) - ev_io_stop (EV_DEFAULT_ watcher); - else - ev_io_start (EV_DEFAULT_ watcher); - return true; + return false; } // --- Logging ----------------------------------------------------------------- @@ -135,16 +128,17 @@ struct fcgi_muxer /// Write data to the underlying transport void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len); - /// Close the underlying transport - // TODO: consider half-close and the subsequent handling + /// Close the underlying transport. You are allowed to destroy the muxer + /// directly from within the callback. void (*close_cb) (struct fcgi_muxer *); /// Start processing a request. Return false if no further action is /// to be done and the request should be finished. - bool (*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *); + bool (*request_start_cb) (struct fcgi_request *); - /// Handle incoming data. "len == 0" means EOF. - void (*request_push_cb) + /// Handle incoming data. "len == 0" means EOF. Returns false if + /// the underlying transport should be closed, this being the last request. + bool (*request_push_cb) (struct fcgi_request *, const void *data, size_t len); /// Destroy the handler's data stored in the request object @@ -230,7 +224,7 @@ fcgi_request_push_params { // TODO: probably check the state of the header parser // TODO: request_start() can return false, end the request here? - (void) self->muxer->request_start_cb (self->muxer, self); + (void) self->muxer->request_start_cb (self); self->state = FCGI_REQUEST_STDIN; } } @@ -282,7 +276,9 @@ fcgi_request_write (struct fcgi_request *self, const void *data, size_t len) } } -static void +/// Mark the request as done. Returns false if the underlying transport +/// should be closed, this being the last request. +static bool fcgi_request_finish (struct fcgi_request *self) { fcgi_request_flush (self); @@ -292,14 +288,26 @@ fcgi_request_finish (struct fcgi_request *self) 0 /* TODO app_status, although ignored */, FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */); - if (!(self->flags & FCGI_KEEP_CONN)) - { - // TODO: tear down (shut down) the connection - } + bool should_close = !(self->flags & FCGI_KEEP_CONN); self->muxer->active_requests--; self->muxer->requests[self->request_id] = NULL; fcgi_request_destroy (self); + + // TODO: tear down (shut down) the connection. This is called from: + // + // 1. client_fcgi_request_push <- request_push_cb + // <- fcgi_request_push_stdin <- fcgi_muxer_on_stdin + // <- fcgi_muxer_on_message <- fcgi_parser_push <- fcgi_muxer_push + // <- client_fcgi_push <- client_read_loop + // => in this case no close_cb may be called + // -> need to pass a false boolean aaall the way up, + // then client_fcgi_finalize eventually cleans up the rest + // + // 2. client_fcgi_request_close_cb <- request_finish + // => our direct caller must call fcgi_muxer::close_cb + // -> not very nice to delegate it there + return !should_close; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -418,6 +426,7 @@ fcgi_muxer_on_abort_request // TODO: abort the request: let it somehow produce FCGI_END_REQUEST, // make sure to send an stdout EOF record + // TODO: and if that was not a FCGI_KEEP_CONN request, close the transport } static void @@ -1841,23 +1850,24 @@ struct client { LIST_HEADER (struct client) - // XXX: do we really need this here? - struct server_context *ctx; ///< Server context + struct client_vtable *vtable; ///< Client behaviour - int socket_fd; ///< The TCP socket + int socket_fd; ///< The network socket + bool received_eof; ///< Whether EOF has been received yet + bool closing; ///< Whether we're just flushing buffers + bool half_closed; ///< Transport half-closed while closing struct write_queue write_queue; ///< Write queue + ev_timer flush_timeout_watcher; ///< Write queue flush timer ev_io read_watcher; ///< The socket can be read from ev_io write_watcher; ///< The socket can be written to - - struct client_vtable *vtable; ///< Client behaviour }; /// The concrete behaviour to serve a particular client's requests struct client_vtable { /// Process incoming data; "len == 0" means EOF. - /// If the method returns false, the client is destroyed by caller. + /// If the method returns false, client_close() is called by the caller. bool (*push) (struct client *client, const void *data, size_t len); // TODO: optional push_error() to inform about network I/O errors @@ -1885,8 +1895,8 @@ client_write (struct client *self, const void *data, size_t len) static void client_destroy (struct client *self) { - struct server_context *ctx = self->ctx; - + // XXX: this codebase halfway pretends there could be other contexts + struct server_context *ctx = ev_userdata (EV_DEFAULT); LIST_UNLINK (ctx->clients, self); ctx->n_clients--; @@ -1897,63 +1907,111 @@ client_destroy (struct client *self) ev_io_stop (EV_DEFAULT_ &self->write_watcher); xclose (self->socket_fd); write_queue_free (&self->write_queue); + ev_timer_stop (EV_DEFAULT_ &self->flush_timeout_watcher); free (self); try_finish_quit (ctx); } +/// Try to cleanly close the connection, waiting for the remote client to close +/// its own side of the connection as a sign that it has processed all the data +/// it wanted to. The client implementation will not receive any further data. +/// May directly call client_destroy(). +static void +client_close (struct client *self) +{ + if (self->closing) + return; + + self->closing = true; + ev_timer_start (EV_DEFAULT_ &self->flush_timeout_watcher); + ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE); + + // We assume the remote client doesn't want our data if it half-closes + if (self->received_eof) + client_destroy (self); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool client_read_loop (EV_P_ struct client *client, ev_io *watcher) { char buf[8192]; - while (true) + ssize_t n_read; +again: + while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0) { - ssize_t n_read = recv (watcher->fd, buf, sizeof buf, 0); - if (n_read >= 0) + if (!n_read) { - if (!client->vtable->push (client, buf, n_read)) - return false; - if (!n_read) - break; + // Don't deliver the EOF condition repeatedly + ev_io_stop (EV_A_ watcher); + client->received_eof = true; } - else if (errno == EAGAIN) - return true; - else if (errno != EINTR) + if (!client->closing + && !client->vtable->push (client, buf, n_read)) + { + client_close (client); return false; + } + if (!n_read) + return true; } + if (errno == EINTR) + goto again; + if (errno == EAGAIN) + return true; - // Don't receive the EOF condition repeatedly - ev_io_stop (EV_A_ watcher); - - // We can probably still write, so let's just return - // XXX: if there's nothing to be written, shouldn't we close the connection? - return true; + client_destroy (client); + return false; } static void -on_client_ready (EV_P_ ev_io *watcher, int revents) +on_client_readable (EV_P_ ev_io *watcher, int revents) { struct client *client = watcher->data; - // XXX: although read and write are in a sequence, if we create response - // data, we'll still likely need to go back to the event loop. + (void) revents; - if (revents & EV_READ) - if (!client_read_loop (EV_A_ client, watcher)) - goto close; - if (revents & EV_WRITE) - // TODO: add "closing link" functionality -> automatic shutdown - // (half-close) once we manage to flush the write buffer, - // which is logically followed by waiting for an EOF from the client - // TODO: some sort of "on_buffers_flushed" callback for streaming huge - // chunks of external (or generated) data. - if (!flush_queue (&client->write_queue, watcher)) - goto close; - return; + if (client_read_loop (EV_A_ client, watcher) + && client->closing && client->received_eof) + client_destroy (client); +} -close: - client_destroy (client); +static void +on_client_writable (EV_P_ ev_io *watcher, int revents) +{ + struct client *client = watcher->data; + (void) loop; + (void) revents; + + // TODO: some sort of "on_buffers_flushed" callback for streaming huge + // chunks of external (or generated) data. That will need to be + // forwarded to "struct request_handler". + if (!flush_queue (&client->write_queue, watcher->fd)) + { + client_destroy (client); + return; + } + if (!write_queue_is_empty (&client->write_queue)) + return; + + ev_io_stop (EV_A_ watcher); + if (client->closing && !client->half_closed) + { + if (!shutdown (client->socket_fd, SHUT_WR)) + client->half_closed = true; + else + client_destroy (client); + } +} + +static void +on_client_timeout (EV_P_ ev_timer *watcher, int revents) +{ + (void) loop; + (void) revents; + + client_destroy (watcher->data); } /// Create a new instance of a subclass with the given size. @@ -1964,14 +2022,15 @@ client_new (EV_P_ size_t size, int sock_fd) struct server_context *ctx = ev_userdata (loop); struct client *self = xcalloc (1, size); - self->ctx = ctx; self->write_queue = write_queue_make (); + ev_timer_init (&self->flush_timeout_watcher, on_client_timeout, 5., 0.); + self->flush_timeout_watcher.data = self; set_blocking (sock_fd, false); self->socket_fd = sock_fd; - ev_io_init (&self->read_watcher, on_client_ready, sock_fd, EV_READ); - ev_io_init (&self->write_watcher, on_client_ready, sock_fd, EV_WRITE); + ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ); + ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE); self->read_watcher.data = self; self->write_watcher.data = self; @@ -2010,37 +2069,36 @@ static void client_fcgi_request_close_cb (struct request *req) { FIND_CONTAINER (self, req, struct client_fcgi_request, request); - // No more data to send, terminate the substream/request - // XXX: this will most probably end up with client_fcgi_request_destroy(), - // we might or might not need to defer this action - fcgi_request_finish (self->fcgi_request); + struct fcgi_muxer *muxer = self->fcgi_request->muxer; + // No more data to send, terminate the substream/request, + // and also the transport if the client didn't specifically ask to keep it + if (!fcgi_request_finish (self->fcgi_request)) + muxer->close_cb (muxer); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool -client_fcgi_request_start - (struct fcgi_muxer *mux, struct fcgi_request *fcgi_request) +client_fcgi_request_start (struct fcgi_request *fcgi_request) { - FIND_CONTAINER (self, mux, struct client_fcgi, muxer); - struct client_fcgi_request *request = fcgi_request->handler_data = xcalloc (1, sizeof *request); request->fcgi_request = fcgi_request; request_init (&request->request); - request->request.ctx = self->client.ctx; + request->request.ctx = ev_userdata (EV_DEFAULT); request->request.write_cb = client_fcgi_request_write_cb; request->request.close_cb = client_fcgi_request_close_cb; return request_start (&request->request, &fcgi_request->headers); } -static void +static bool client_fcgi_request_push (struct fcgi_request *req, const void *data, size_t len) { struct client_fcgi_request *request = req->handler_data; - request_push (&request->request, data, len); + return request_push (&request->request, data, len) + || fcgi_request_finish (req); } static void @@ -2064,9 +2122,7 @@ static void client_fcgi_close_cb (struct fcgi_muxer *mux) { FIND_CONTAINER (self, mux, struct client_fcgi, muxer); - // FIXME: we should probably call something like client_shutdown(), - // which may have an argument whether we should really use close() - client_destroy (&self->client); + client_close (&self->client); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2137,11 +2193,9 @@ client_scgi_write_cb (struct request *req, const void *data, size_t len) static void client_scgi_close_cb (struct request *req) { - // NOTE: this rather really means "close me [the request]" FIND_CONTAINER (self, req, struct client_scgi, request); - // FIXME: we should probably call something like client_shutdown(), - // which may have an argument whether we should really use close() - client_destroy (&self->client); + // NOTE: this rather really means "close me [the request]" + client_close (&self->client); } static bool @@ -2201,7 +2255,6 @@ client_scgi_create (EV_P_ int sock_fd) self->client.vtable = &client_scgi_vtable; request_init (&self->request); - self->request.ctx = self->client.ctx; self->request.write_cb = client_scgi_write_cb; self->request.close_cb = client_scgi_close_cb; @@ -2231,8 +2284,9 @@ client_ws_on_message (struct ws_handler *handler, return false; } + struct server_context *ctx = ev_userdata (EV_DEFAULT); struct str response = str_make (); - process_json_rpc (self->client.ctx, data, len, &response); + process_json_rpc (ctx, data, len, &response); if (response.len) ws_handler_send (&self->handler, WS_OPCODE_TEXT, response.str, response.len); From cf56921c4e1959bb3962b4f99ee4a1e7665c57d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 23:26:11 +0200 Subject: [PATCH 72/79] Allow WebSockets to micromanage shutdowns They have their reasons, mostly event-related. --- demo-json-rpc-server.c | 59 +++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 563d11c..2b99419 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -1854,10 +1854,11 @@ struct client int socket_fd; ///< The network socket bool received_eof; ///< Whether EOF has been received yet - bool closing; ///< Whether we're just flushing buffers - bool half_closed; ///< Transport half-closed while closing + bool flushing; ///< No more data to write, send FIN + bool closing; ///< No more data to read or write + bool half_closed; ///< Conn. half-closed while flushing struct write_queue write_queue; ///< Write queue - ev_timer flush_timeout_watcher; ///< Write queue flush timer + ev_timer close_timeout_watcher; ///< Write queue flush timer ev_io read_watcher; ///< The socket can be read from ev_io write_watcher; ///< The socket can be written to @@ -1881,17 +1882,6 @@ struct client_vtable void (*finalize) (struct client *client); }; -static void -client_write (struct client *self, const void *data, size_t len) -{ - struct write_req *req = xcalloc (1, sizeof *req); - req->data.iov_base = memcpy (xmalloc (len), data, len); - req->data.iov_len = len; - - write_queue_add (&self->write_queue, req); - ev_io_start (EV_DEFAULT_ &self->write_watcher); -} - static void client_destroy (struct client *self) { @@ -1907,12 +1897,36 @@ client_destroy (struct client *self) ev_io_stop (EV_DEFAULT_ &self->write_watcher); xclose (self->socket_fd); write_queue_free (&self->write_queue); - ev_timer_stop (EV_DEFAULT_ &self->flush_timeout_watcher); + ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher); free (self); try_finish_quit (ctx); } +static void +client_write (struct client *self, const void *data, size_t len) +{ + if (!soft_assert (!self->flushing)) + return; + + struct write_req *req = xcalloc (1, sizeof *req); + req->data.iov_base = memcpy (xmalloc (len), data, len); + req->data.iov_len = len; + + write_queue_add (&self->write_queue, req); + ev_io_start (EV_DEFAULT_ &self->write_watcher); +} + +/// Half-close the connection from our side once the write_queue is flushed. +/// It is the caller's responsibility to destroy the connection upon EOF. +// XXX: or we might change on_client_readable to do it anyway, seems safe +static void +client_shutdown (struct client *self) +{ + self->flushing = true; + ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE); +} + /// Try to cleanly close the connection, waiting for the remote client to close /// its own side of the connection as a sign that it has processed all the data /// it wanted to. The client implementation will not receive any further data. @@ -1924,8 +1938,8 @@ client_close (struct client *self) return; self->closing = true; - ev_timer_start (EV_DEFAULT_ &self->flush_timeout_watcher); - ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE); + ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher); + client_shutdown (self); // We assume the remote client doesn't want our data if it half-closes if (self->received_eof) @@ -1996,7 +2010,7 @@ on_client_writable (EV_P_ ev_io *watcher, int revents) return; ev_io_stop (EV_A_ watcher); - if (client->closing && !client->half_closed) + if (client->flushing && !client->half_closed) { if (!shutdown (client->socket_fd, SHUT_WR)) client->half_closed = true; @@ -2023,8 +2037,8 @@ client_new (EV_P_ size_t size, int sock_fd) struct client *self = xcalloc (1, size); self->write_queue = write_queue_make (); - ev_timer_init (&self->flush_timeout_watcher, on_client_timeout, 5., 0.); - self->flush_timeout_watcher.data = self; + ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.); + self->close_timeout_watcher.data = self; set_blocking (sock_fd, false); self->socket_fd = sock_fd; @@ -2305,10 +2319,7 @@ static void client_ws_close_cb (struct ws_handler *handler, bool half_close) { FIND_CONTAINER (self, handler, struct client_ws, handler); - if (half_close) - ; // FIXME: we should probably call something like client_shutdown() - else - client_destroy (&self->client); + (half_close ? client_shutdown : client_destroy) (&self->client); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 253e35e1e43b8c7f8ea8de7b94556a4ef6cf0d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 23:34:59 +0200 Subject: [PATCH 73/79] Wrap request::write_cb in a function --- demo-json-rpc-server.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 2b99419..9552146 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -1563,6 +1563,13 @@ request_free (struct request *self) self->handler->finalize_cb (self); } +/// Write request CGI response data, intended for use by request handlers +static void +request_write (struct request *self, const void *data, size_t len) +{ + self->write_cb (self, data, len); +} + /// This function is only intended to be run from asynchronous event handlers /// such as timers, not as a direct result of starting the request or receiving /// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED. @@ -1599,7 +1606,7 @@ request_start (struct request *self, struct str_map *headers) struct str response = str_make (); str_append (&response, "Status: 404 Not Found\n"); str_append (&response, "Content-Type: text/plain\n\n"); - self->write_cb (self, response.str, response.len); + request_write (self, response.str, response.len); str_free (&response); return false; } @@ -1654,7 +1661,7 @@ request_handler_json_rpc_push str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", "application/json"); process_json_rpc (request->ctx, buf->str, buf->len, &response); - request->write_cb (request, response.str, response.len); + request_write (request, response.str, response.len); str_free (&response); return false; } @@ -1770,7 +1777,7 @@ request_handler_static_try_handle str_append (&response, "Content-Type: text/plain\n\n"); str_append_printf (&response, "File %s was not found on this server\n", suffix); - request->write_cb (request, response.str, response.len); + request_write (request, response.str, response.len); str_free (&response); free (suffix); @@ -1794,17 +1801,17 @@ request_handler_static_try_handle struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", mime_type); - request->write_cb (request, response.str, response.len); + request_write (request, response.str, response.len); str_free (&response); free (mime_type); // Write the chunk we've used to help us with magic detection; // obviously we have to do it after we've written the headers if (len) - request->write_cb (request, buf, len); + request_write (request, buf, len); while ((len = fread (buf, 1, sizeof buf, fp))) - request->write_cb (request, buf, len); + request_write (request, buf, len); fclose (fp); // TODO: this should rather not be returned all at once but in chunks; From 580f0a0c5960d830f49c4cddbfdf869c1d262a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Wed, 17 Oct 2018 23:56:11 +0200 Subject: [PATCH 74/79] Synthesize EOF events in SCGI --- demo-json-rpc-server.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 9552146..1ae1bad 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -1544,6 +1544,8 @@ struct request_handler /// Handle incoming data. "len == 0" means EOF. /// Returns false if there is no more processing to be done. + // FIXME: the EOF may or may not be delivered when request is cut short, + // we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch bool (*push_cb) (struct request *request, const void *data, size_t len); /// Destroy the handler's data stored in the request object @@ -1655,7 +1657,8 @@ request_handler_json_rpc_push } // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the - // client hasn't been successful in transferring all of its data + // client hasn't been successful in transferring all of its data. + // See also comment on request_handler::push_cb. struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); @@ -2202,6 +2205,7 @@ struct client_scgi struct client client; ///< Parent class struct scgi_parser parser; ///< SCGI stream parser struct request request; ///< Request (only one per connection) + unsigned long remaining_content; ///< Length of input data to be seen }; static void @@ -2223,6 +2227,12 @@ static bool client_scgi_on_headers_read (void *user_data) { struct client_scgi *self = user_data; + const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH"); + if (!cl || !xstrtoul (&self->remaining_content, cl, 10)) + { + print_debug ("SCGI request with invalid or missing CONTENT_LENGTH"); + return false; + } return request_start (&self->request, &self->parser.headers); } @@ -2230,11 +2240,19 @@ static bool client_scgi_on_content (void *user_data, const void *data, size_t len) { struct client_scgi *self = user_data; + if (len > self->remaining_content) + { + print_debug ("SCGI request got more data than CONTENT_LENGTH"); + return false; + } + // We're in a slight disagreement with the specification since + // this tries to write output before it has read all the input + if (!request_push (&self->request, data, len)) + return false; - // XXX: do we have to count CONTENT_LENGTH and supply our own EOF? - // If we do produce our own EOF, we should probably make sure we don't - // send it twice in a row. - return request_push (&self->request, data, len); + // Signalise end of input to the request handler + return (self->remaining_content -= len) != 0 + || request_push (&self->request, NULL, 0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 62945cceb3b2f9fae0fd39842448ebfeba378fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Thu, 18 Oct 2018 02:55:55 +0200 Subject: [PATCH 75/79] Finish the WebSocket backend Of course, everything so far hasn't been tested much. --- demo-json-rpc-server.c | 275 ++++++++++++++++++++++++----------------- 1 file changed, 164 insertions(+), 111 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 1ae1bad..60a7173 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -540,9 +540,9 @@ enum ws_handler_state { WS_HANDLER_CONNECTING, ///< Parsing HTTP WS_HANDLER_OPEN, ///< Parsing WebSockets frames - WS_HANDLER_CLOSING, ///< Closing the connection - WS_HANDLER_ALMOST_DEAD, ///< Closing connection after failure - WS_HANDLER_CLOSED ///< Dead + WS_HANDLER_CLOSING, ///< Partial closure by us + WS_HANDLER_FLUSHING, ///< Just waiting for client EOF + WS_HANDLER_CLOSED ///< Dead, both sides closed }; struct ws_handler @@ -584,6 +584,7 @@ struct ws_handler // TODO: void (*on_handshake) (protocols) that will allow the user // to choose any sub-protocol, if the client has provided any. // This may render "on_connected" unnecessary. + // Should also enable failing the handshake. /// Called after successfuly connecting (handshake complete) bool (*on_connected) (struct ws_handler *); @@ -626,36 +627,41 @@ static void ws_handler_close (struct ws_handler *self, enum ws_status close_code, const char *reason, size_t len) { + hard_assert (self->state == WS_HANDLER_OPEN); + struct str payload = str_make (); str_pack_u16 (&payload, close_code); // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w str_append_data (&payload, reason, len); ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len); - - // Close initiated by us; the reason is null-terminated within `payload' - if (self->on_close) - self->on_close (self, close_code, payload.str + 2); + self->close_cb (self, true /* half_close */); self->state = WS_HANDLER_CLOSING; str_free (&payload); } -static void -ws_handler_fail (struct ws_handler *self, enum ws_status close_code) +static bool +ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code) { - ws_handler_close (self, close_code, NULL, 0); - self->state = WS_HANDLER_ALMOST_DEAD; + hard_assert (self->state == WS_HANDLER_OPEN + || self->state == WS_HANDLER_CLOSING); - // TODO: set the close timer, ignore all further incoming input (either set - // some flag for the case that we're in the middle of ws_handler_push(), - // and/or add a mechanism to stop the caller from polling the socket for - // reads). - // TODO: make sure we don't send pings after the close + if (self->state == WS_HANDLER_OPEN) + ws_handler_close (self, close_code, NULL, 0); + + self->state = WS_HANDLER_FLUSHING; + if (self->on_close) + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, ""); + + ev_timer_stop (EV_DEFAULT_ &self->ping_timer); + ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.); + ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher); + return false; } // TODO: add support for fragmented responses static void -ws_handler_send (struct ws_handler *self, +ws_handler_send_frame (struct ws_handler *self, enum ws_opcode opcode, const void *data, size_t len) { if (!soft_assert (self->state == WS_HANDLER_OPEN)) @@ -697,24 +703,26 @@ ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser) || (!ws_is_control_frame (parser->opcode) && (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT)) || parser->payload_len >= 0x8000000000000000ULL) - ws_handler_fail (self, WS_STATUS_PROTOCOL_ERROR); - else if (parser->payload_len > self->max_payload_len) - ws_handler_fail (self, WS_STATUS_MESSAGE_TOO_BIG); - else - return true; - return false; + return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR); + + if (parser->payload_len > self->max_payload_len + || (self->expecting_continuation && + self->message_data.len + parser->payload_len > self->max_payload_len)) + return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG); + return true; } static bool -ws_handler_on_protocol_close +ws_handler_on_control_close (struct ws_handler *self, const struct ws_parser *parser) { + hard_assert (self->state == WS_HANDLER_OPEN + || self->state == WS_HANDLER_CLOSING); struct msg_unpacker unpacker = msg_unpacker_make (parser->input.str, parser->payload_len); char *reason = NULL; uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED; - if (parser->payload_len >= 2) { (void) msg_unpacker_u16 (&unpacker, &close_code); @@ -723,17 +731,29 @@ ws_handler_on_protocol_close else reason = xstrdup (""); - if (self->state != WS_HANDLER_CLOSING) + if (close_code < 1000 || close_code > 4999) + // XXX: invalid close code: maybe we should fail the connection instead + close_code = WS_STATUS_PROTOCOL_ERROR; + + if (self->state == WS_HANDLER_OPEN) { // Close initiated by the client + // FIXME: not sending the potentially different close_code ws_handler_send_control (self, WS_OPCODE_CLOSE, parser->input.str, parser->payload_len); + + self->state = WS_HANDLER_FLUSHING; if (self->on_close) self->on_close (self, close_code, reason); } + else + self->state = WS_HANDLER_FLUSHING; free (reason); - self->state = WS_HANDLER_ALMOST_DEAD; + + ev_timer_stop (EV_DEFAULT_ &self->ping_timer); + ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.); + ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher); return true; } @@ -744,21 +764,18 @@ ws_handler_on_control_frame switch (parser->opcode) { case WS_OPCODE_CLOSE: - return ws_handler_on_protocol_close (self, parser); + return ws_handler_on_control_close (self, parser); case WS_OPCODE_PING: ws_handler_send_control (self, WS_OPCODE_PONG, parser->input.str, parser->payload_len); break; case WS_OPCODE_PONG: - // XXX: maybe we should check the payload + // TODO: check the payload self->received_pong = true; break; default: // Unknown control frame - ws_handler_fail (self, WS_STATUS_PROTOCOL_ERROR); - // FIXME: we shouldn't close the connection right away; - // also check other places - return false; + return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR); } return true; } @@ -769,29 +786,19 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser) struct ws_handler *self = user_data; if (ws_is_control_frame (parser->opcode)) return ws_handler_on_control_frame (self, parser); - - // TODO: do this rather in "on_frame_header" - if (self->message_data.len + parser->payload_len > self->max_payload_len) - { - ws_handler_fail (self, WS_STATUS_MESSAGE_TOO_BIG); - return false; - } - if (!self->expecting_continuation) self->message_opcode = parser->opcode; str_append_data (&self->message_data, parser->input.str, parser->payload_len); - self->expecting_continuation = !parser->is_fin; - - if (!parser->is_fin) + if ((self->expecting_continuation = !parser->is_fin)) return true; if (self->message_opcode == WS_OPCODE_TEXT && !utf8_validate (self->message_data.str, self->message_data.len)) { - ws_handler_fail (self, WS_STATUS_INVALID_PAYLOAD_DATA); - return false; + return ws_handler_fail_connection + (self, WS_STATUS_INVALID_PAYLOAD_DATA); } bool result = true; @@ -799,6 +806,8 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser) result = self->on_message (self, self->message_opcode, self->message_data.str, self->message_data.len); str_reset (&self->message_data); + // TODO: if (!result), either replace this with a state check, + // or make sure to change the state return result; } @@ -810,11 +819,10 @@ ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents) struct ws_handler *self = watcher->data; if (!self->received_pong) - { - // TODO: close/fail the connection? - } + ws_handler_fail_connection (self, 4000); else { + // TODO: be an annoying server and send a nonce in the data ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0); ev_timer_again (EV_A_ watcher); } @@ -823,20 +831,38 @@ ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents) static void ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) { + (void) loop; (void) revents; struct ws_handler *self = watcher->data; - // TODO: anything else to do here? Invalidate our state? - if (self->close_cb) - self->close_cb (self, false /* half_close */); + hard_assert (self->state == WS_HANDLER_OPEN + || self->state == WS_HANDLER_CLOSING); + + if (self->state == WS_HANDLER_CLOSING + && self->on_close) + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout"); + + self->state = WS_HANDLER_CLOSED; + self->close_cb (self, false /* half_close */); } static void ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents) { + (void) loop; (void) revents; struct ws_handler *self = watcher->data; - // TODO + + // XXX: this is a no-op, since this currently doesn't even call shutdown + // immediately but postpones it until later + self->close_cb (self, true /* half_close */); + self->state = WS_HANDLER_FLUSHING; + + if (self->on_close) + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout"); + + self->state = WS_HANDLER_CLOSED; + self->close_cb (self, false /* half_close */); } static void @@ -991,6 +1017,7 @@ ws_handler_on_url (http_parser *parser, const char *at, size_t len) #define HTTP_400_BAD_REQUEST "400 Bad Request" #define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed" #define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed" +#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required" #define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported" static void @@ -1024,43 +1051,47 @@ ws_handler_http_responsev (struct ws_handler *self, str_free (&response); } -static void -ws_handler_http_response (struct ws_handler *self, const char *status, ...) +static bool +ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...) { - struct strv v = strv_make (); - va_list ap; va_start (ap, status); const char *s; + struct strv v = strv_make (); while ((s = va_arg (ap, const char *))) strv_append (&v, s); va_end (ap); - ws_handler_http_responsev (self, status, v.vector); strv_free (&v); + + self->close_cb (self, true /* half_close */); + self->state = WS_HANDLER_FLUSHING; + + if (self->on_close) + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status); + return false; } -#define FAIL_HANDSHAKE(status, ...) \ - BLOCK_START \ - self->state = WS_HANDLER_ALMOST_DEAD; \ - ws_handler_http_response (self, (status), __VA_ARGS__); \ - return false; \ - BLOCK_END +#define FAIL_HANDSHAKE(...) \ + return ws_handler_fail_handshake (self, __VA_ARGS__, NULL) static bool ws_handler_finish_handshake (struct ws_handler *self) { - // XXX: we probably shouldn't use 505 to reject the minor version but w/e - if (self->hp.http_major != 1 || self->hp.http_minor < 1) - FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED, NULL); if (self->hp.method != HTTP_GET) - FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET", NULL); + FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET"); + + // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP + // is going to happen and 2.x is entirely incompatible + // XXX: we probably shouldn't use 505 to reject the minor version but w/e + if (self->hp.http_major != 1 || self->hp.http_minor != 1) + FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED); // Your expectations are way too high if (str_map_find (&self->headers, "Expect")) - FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED, NULL); + FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED); // Reject URLs specifying the schema and host; we're not parsing that // TODO: actually do parse this and let our user decide if it matches @@ -1068,11 +1099,11 @@ ws_handler_finish_handshake (struct ws_handler *self) if (http_parser_parse_url (self->url.str, self->url.len, false, &url) || (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT)) || !str_map_find (&self->headers, "Host")) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); const char *connection = str_map_find (&self->headers, "Connection"); if (!connection || strcasecmp_ascii (connection, "Upgrade")) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); // Check if we can actually upgrade the protocol to WebSockets const char *upgrade = str_map_find (&self->headers, "Upgrade"); @@ -1088,7 +1119,8 @@ ws_handler_finish_handshake (struct ws_handler *self) http_protocol_destroy (iter); } if (!can_upgrade) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); + FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED, + "Upgrade: websocket", SEC_WS_VERSION ": 13"); // Okay, we're finally past the basic HTTP/1.1 stuff const char *key = str_map_find (&self->headers, SEC_WS_KEY); @@ -1098,19 +1130,17 @@ ws_handler_finish_handshake (struct ws_handler *self) const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS); */ - if (!key) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); + if (!version) + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); + if (strcmp (version, "13")) + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13"); struct str tmp = str_make (); - bool key_is_valid = base64_decode (key, false, &tmp) && tmp.len == 16; + bool key_is_valid = key + && base64_decode (key, false, &tmp) && tmp.len == 16; str_free (&tmp); if (!key_is_valid) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); - - if (!version) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); - if (strcmp (version, "13")) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13", NULL); + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); struct strv fields = strv_make (); strv_append_args (&fields, @@ -1130,6 +1160,7 @@ ws_handler_finish_handshake (struct ws_handler *self) strv_free (&fields); + self->state = WS_HANDLER_OPEN; ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer, self->ping_interval, 0); ev_timer_start (EV_DEFAULT_ &self->ping_timer); @@ -1141,40 +1172,62 @@ ws_handler_finish_handshake (struct ws_handler *self) static void ws_handler_start (struct ws_handler *self) { + hard_assert (self->state == WS_HANDLER_CONNECTING); + ev_timer_set (&self->handshake_timeout_watcher, self->handshake_timeout, 0.); ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher); } +// The client should normally never close the connection, assume that it's +// either received an EOF from our side, or that it doesn't care about our data +// anymore, having called close() already +static bool +ws_handler_push_eof (struct ws_handler *self) +{ + switch (self->state) + { + case WS_HANDLER_CONNECTING: + ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); + + self->state = WS_HANDLER_FLUSHING; + if (self->on_close) + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF"); + break; + case WS_HANDLER_OPEN: + ev_timer_stop (EV_DEFAULT_ &self->ping_timer); + // Fall-through + case WS_HANDLER_CLOSING: + self->state = WS_HANDLER_CLOSED; + if (self->on_close) + self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, ""); + // Fall-through + case WS_HANDLER_FLUSHING: + ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher); + break; + default: + soft_assert(self->state != WS_HANDLER_CLOSED); + } + self->state = WS_HANDLER_CLOSED; + return false; +} + /// Push data to the WebSocket handler. "len == 0" means EOF. +/// You are expected to close the connection and dispose of the handler +/// when the function returns false. static bool ws_handler_push (struct ws_handler *self, const void *data, size_t len) { - // TODO: make sure all timers are stopped appropriately - if (!len) - { - ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); + return ws_handler_push_eof (self); - if (self->state == WS_HANDLER_OPEN) - { - if (self->on_close) - self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, ""); - } - else - { - // TODO: anything to do besides just closing the connection? - } - - self->state = WS_HANDLER_CLOSED; - return false; - } - - if (self->state == WS_HANDLER_ALMOST_DEAD) + if (self->state == WS_HANDLER_FLUSHING) // We're waiting for an EOF from the client, must not process data return true; + if (self->state != WS_HANDLER_CONNECTING) - return ws_parser_push (&self->parser, data, len); + return soft_assert (self->state != WS_HANDLER_CLOSED) + && ws_parser_push (&self->parser, data, len); // The handshake hasn't been done yet, process HTTP headers static const http_parser_settings http_settings = @@ -1185,8 +1238,8 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) .on_url = ws_handler_on_url, }; - size_t n_parsed = http_parser_execute (&self->hp, - &http_settings, data, len); + size_t n_parsed = + http_parser_execute (&self->hp, &http_settings, data, len); if (self->hp.upgrade) { @@ -1195,12 +1248,10 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) // The handshake hasn't been finished, yet there is more data // to be processed after the headers already if (len - n_parsed) - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); if (!ws_handler_finish_handshake (self)) return false; - - self->state = WS_HANDLER_OPEN; if (self->on_connected) return self->on_connected (self); return true; @@ -1217,7 +1268,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) print_debug ("WS handshake failed: %s", http_errno_description (err)); - FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL); + FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); } return true; } @@ -2319,15 +2370,15 @@ client_ws_on_message (struct ws_handler *handler, FIND_CONTAINER (self, handler, struct client_ws, handler); if (type != WS_OPCODE_TEXT) { - ws_handler_fail (&self->handler, WS_STATUS_UNSUPPORTED_DATA); - return false; + return ws_handler_fail_connection + (&self->handler, WS_STATUS_UNSUPPORTED_DATA); } struct server_context *ctx = ev_userdata (EV_DEFAULT); struct str response = str_make (); process_json_rpc (ctx, data, len, &response); if (response.len) - ws_handler_send (&self->handler, + ws_handler_send_frame (&self->handler, WS_OPCODE_TEXT, response.str, response.len); str_free (&response); return true; @@ -2353,6 +2404,7 @@ static bool client_ws_push (struct client *client, const void *data, size_t len) { FIND_CONTAINER (self, client, struct client_ws, client); + // client_close() will correctly destroy the client on EOF return ws_handler_push (&self->handler, data, len); } @@ -2361,7 +2413,8 @@ client_ws_shutdown (struct client *client) { FIND_CONTAINER (self, client, struct client_ws, client); if (self->handler.state == WS_HANDLER_CONNECTING) - ; // TODO: abort the connection immediately + // No on_close, no problem + client_destroy (&self->client); else if (self->handler.state == WS_HANDLER_OPEN) ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0); } From d883f4cc71a1f9db5cc4cec2bc60df6cba1f8833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Thu, 18 Oct 2018 04:13:59 +0200 Subject: [PATCH 76/79] Finish the FastCGI backend Bump liberty, also fixing SCGI. --- demo-json-rpc-server.c | 154 ++++++++++++++++++++--------------------- liberty | 2 +- 2 files changed, 75 insertions(+), 81 deletions(-) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 60a7173..50e203f 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -194,6 +194,8 @@ fcgi_request_new (void) self->hdr_parser = fcgi_nv_parser_make (); self->hdr_parser.output = &self->headers; + + self->output_buffer = str_make (); return self; } @@ -208,40 +210,6 @@ fcgi_request_destroy (struct fcgi_request *self) free (self); } -static void -fcgi_request_push_params - (struct fcgi_request *self, const void *data, size_t len) -{ - if (self->state != FCGI_REQUEST_PARAMS) - { - // TODO: probably reject the request - return; - } - - if (len) - fcgi_nv_parser_push (&self->hdr_parser, data, len); - else - { - // TODO: probably check the state of the header parser - // TODO: request_start() can return false, end the request here? - (void) self->muxer->request_start_cb (self); - self->state = FCGI_REQUEST_STDIN; - } -} - -static void -fcgi_request_push_stdin - (struct fcgi_request *self, const void *data, size_t len) -{ - if (self->state != FCGI_REQUEST_STDIN) - { - // TODO: probably reject the request - return; - } - - self->muxer->request_push_cb (self, data, len); -} - static void fcgi_request_flush (struct fcgi_request *self) { @@ -294,36 +262,62 @@ fcgi_request_finish (struct fcgi_request *self) self->muxer->requests[self->request_id] = NULL; fcgi_request_destroy (self); - // TODO: tear down (shut down) the connection. This is called from: - // - // 1. client_fcgi_request_push <- request_push_cb - // <- fcgi_request_push_stdin <- fcgi_muxer_on_stdin - // <- fcgi_muxer_on_message <- fcgi_parser_push <- fcgi_muxer_push - // <- client_fcgi_push <- client_read_loop - // => in this case no close_cb may be called - // -> need to pass a false boolean aaall the way up, - // then client_fcgi_finalize eventually cleans up the rest - // - // 2. client_fcgi_request_close_cb <- request_finish - // => our direct caller must call fcgi_muxer::close_cb - // -> not very nice to delegate it there return !should_close; } +static bool +fcgi_request_push_params + (struct fcgi_request *self, const void *data, size_t len) +{ + if (self->state != FCGI_REQUEST_PARAMS) + { + print_debug ("FastCGI: expected %s, got %s", + STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS)); + return false; + } + + if (len) + fcgi_nv_parser_push (&self->hdr_parser, data, len); + else + { + if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN) + print_debug ("FastCGI: request headers seem to be cut off"); + + self->state = FCGI_REQUEST_STDIN; + if (!self->muxer->request_start_cb (self)) + return fcgi_request_finish (self); + } + return true; +} + +static bool +fcgi_request_push_stdin + (struct fcgi_request *self, const void *data, size_t len) +{ + if (self->state != FCGI_REQUEST_STDIN) + { + print_debug ("FastCGI: expected %s, got %s", + STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN)); + return false; + } + + return self->muxer->request_push_cb (self, data, len); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -typedef void (*fcgi_muxer_handler_fn) +typedef bool (*fcgi_muxer_handler_fn) (struct fcgi_muxer *, const struct fcgi_parser *); -static void +static bool fcgi_muxer_on_get_values (struct fcgi_muxer *self, const struct fcgi_parser *parser) { if (parser->request_id != FCGI_NULL_REQUEST_ID) { - print_debug ("FastCGI: ignoring invalid %s message", + print_debug ("FastCGI: invalid %s message", STRINGIFY (FCGI_GET_VALUES)); - return; + return false; } struct str_map values = str_map_make (free); @@ -357,9 +351,10 @@ fcgi_muxer_on_get_values str_map_free (&values); str_map_free (&response); + return true; } -static void +static bool fcgi_muxer_on_begin_request (struct fcgi_muxer *self, const struct fcgi_parser *parser) { @@ -375,16 +370,17 @@ fcgi_muxer_on_begin_request if (!success) { - print_debug ("FastCGI: ignoring invalid %s message", + print_debug ("FastCGI: invalid %s message", STRINGIFY (FCGI_BEGIN_REQUEST)); - return; + return false; } struct fcgi_request *request = self->requests[parser->request_id]; if (parser->request_id == FCGI_NULL_REQUEST_ID || request) { - // TODO: fail - return; + print_debug ("FastCGI: unusable request ID in %s message", + STRINGIFY (FCGI_BEGIN_REQUEST)); + return false; } // We can only act as a responder, reject everything else up front @@ -392,7 +388,7 @@ fcgi_muxer_on_begin_request { fcgi_muxer_send_end_request (self, parser->request_id, 0, FCGI_UNKNOWN_ROLE); - return; + return true; } if (parser->request_id >= N_ELEMENTS (self->requests) @@ -400,7 +396,7 @@ fcgi_muxer_on_begin_request { fcgi_muxer_send_end_request (self, parser->request_id, 0, FCGI_OVERLOADED); - return; + return true; } request = fcgi_request_new (); @@ -410,9 +406,10 @@ fcgi_muxer_on_begin_request self->requests[parser->request_id] = request; self->active_requests++; + return true; } -static void +static bool fcgi_muxer_on_abort_request (struct fcgi_muxer *self, const struct fcgi_parser *parser) { @@ -421,15 +418,13 @@ fcgi_muxer_on_abort_request { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_ABORT_REQUEST)); - return; + return true; // We might have just rejected it } - // TODO: abort the request: let it somehow produce FCGI_END_REQUEST, - // make sure to send an stdout EOF record - // TODO: and if that was not a FCGI_KEEP_CONN request, close the transport + return fcgi_request_finish (request); } -static void +static bool fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; @@ -437,14 +432,15 @@ fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_PARAMS)); - return; + return true; // We might have just rejected it } - fcgi_request_push_params (request, + // This may immediately finish and delete the request, but that's fine + return fcgi_request_push_params (request, parser->content.str, parser->content.len); } -static void +static bool fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; @@ -452,15 +448,15 @@ fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_STDIN)); - return; + return true; // We might have just rejected it } // At the end of the stream, a zero-length record is received - fcgi_request_push_stdin (request, + return fcgi_request_push_stdin (request, parser->content.str, parser->content.len); } -static void +static bool fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data) { struct fcgi_muxer *self = user_data; @@ -468,8 +464,7 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data) if (parser->version != FCGI_VERSION_1) { print_debug ("FastCGI: unsupported version %d", parser->version); - // TODO: also return false to stop processing on protocol error? - return; + return false; } static const fcgi_muxer_handler_fn handlers[] = @@ -489,10 +484,10 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data) uint8_t content[8] = { parser->type }; fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id, content, sizeof content); - return; + return true; } - handler (self, parser); + return handler (self, parser); } static void @@ -520,10 +515,10 @@ fcgi_muxer_free (struct fcgi_muxer *self) fcgi_parser_free (&self->parser); } -static void +static bool fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len) { - fcgi_parser_push (&self->parser, data, len); + return fcgi_parser_push (&self->parser, data, len); } /// @} @@ -1574,7 +1569,7 @@ struct request /// Callback to write some CGI response data to the output void (*write_cb) (struct request *, const void *data, size_t len); - /// Callback to close the connection. + /// Callback to close the CGI response, simulates end of program execution. /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED. void (*close_cb) (struct request *); }; @@ -1967,7 +1962,7 @@ client_destroy (struct client *self) static void client_write (struct client *self, const void *data, size_t len) { - if (!soft_assert (!self->flushing)) + if (!soft_assert (!self->flushing) || len == 0) return; struct write_req *req = xcalloc (1, sizeof *req); @@ -2206,8 +2201,7 @@ static bool client_fcgi_push (struct client *client, const void *data, size_t len) { FIND_CONTAINER (self, client, struct client_fcgi, client); - fcgi_muxer_push (&self->muxer, data, len); - return true; + return fcgi_muxer_push (&self->muxer, data, len); } static void diff --git a/liberty b/liberty index 9494e8e..bca7167 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit 9494e8e2affcd08b791d3ecf68985a8a3a310e55 +Subproject commit bca7167d037d857448cb18243425d7c61de3bdd5 From 8d664355681302a08b671723699e3b3720a7703a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Thu, 18 Oct 2018 06:41:12 +0200 Subject: [PATCH 77/79] Remember to set the server context in SCGI requests --- demo-json-rpc-server.c | 1 + 1 file changed, 1 insertion(+) diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c index 50e203f..e210788 100644 --- a/demo-json-rpc-server.c +++ b/demo-json-rpc-server.c @@ -2339,6 +2339,7 @@ client_scgi_create (EV_P_ int sock_fd) self->client.vtable = &client_scgi_vtable; request_init (&self->request); + self->request.ctx = ev_userdata (EV_DEFAULT); self->request.write_cb = client_scgi_write_cb; self->request.close_cb = client_scgi_close_cb; From 6e152ae37c4075ff2a39298d450bf58258ceffe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Thu, 18 Oct 2018 07:17:06 +0200
Subject: [PATCH 78/79] More debugging information for static file serving
---
demo-json-rpc-server.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index e210788..34cd378 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1642,6 +1642,15 @@ request_start (struct request *self, struct str_map *headers)
// Another way we could get rid of the continue_ argument is via adding
// some way of marking the request as finished from within the handler.
+ if (g_debug_mode)
+ {
+ struct str_map_iter iter = str_map_iter_make (headers);
+ const char *value;
+ while ((value = str_map_iter_next (&iter)))
+ print_debug ("%s: %s", iter.link->key, value);
+ print_debug ("--");
+ }
+
bool continue_ = true;
LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers)
if (handler->try_handle (self, headers, &continue_))
@@ -1800,15 +1809,19 @@ request_handler_static_try_handle
return false;
}
+ // TODO: implement HEAD, we don't get that for free;
+ // probably implies adding Content-Length
const char *method = str_map_find (headers, "REQUEST_METHOD");
if (!method || strcmp (method, "GET"))
return false;
// TODO: look at
Date: Thu, 18 Oct 2018 07:27:55 +0200
Subject: [PATCH 79/79] demo-json-rpc-server -> json-rpc-test-server
---
CMakeLists.txt | 6 +++---
demo-json-rpc-server.c => json-rpc-test-server.c | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
rename demo-json-rpc-server.c => json-rpc-test-server.c (99%)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c78736a..f6d9b00 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,9 +36,9 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h
include_directories (${PROJECT_BINARY_DIR})
# Build the executables
-add_executable (demo-json-rpc-server
- demo-json-rpc-server.c http-parser/http_parser.c)
-target_link_libraries (demo-json-rpc-server ${project_libraries})
+add_executable (json-rpc-test-server
+ json-rpc-test-server.c http-parser/http_parser.c)
+target_link_libraries (json-rpc-test-server ${project_libraries})
# The files to be installed
include (GNUInstallDirs)
diff --git a/demo-json-rpc-server.c b/json-rpc-test-server.c
similarity index 99%
rename from demo-json-rpc-server.c
rename to json-rpc-test-server.c
index 34cd378..ac15f0a 100644
--- a/demo-json-rpc-server.c
+++ b/json-rpc-test-server.c
@@ -1,5 +1,5 @@
/*
- * demo-json-rpc-server.c: JSON-RPC 2.0 demo server
+ * json-rpc-test-server.c: JSON-RPC 2.0 demo server
*
* Copyright (c) 2015 - 2018, Přemysl Janouch
*