diff --git a/README.md b/README.md index 7b91eb97..825850e5 100644 --- a/README.md +++ b/README.md @@ -601,10 +601,10 @@ There are 5 types of response that you can create - we will describe them here t * _file_response(**const std::string&** filename, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ Uses the `filename` passed in construction as pointer to a file on disk. The body of the HTTP response will be set using the content of the file. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. * _basic_auth_fail_response(**const std::string&** content, **const std::string&** realm = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response in return to a failure during basic authentication. It allows to specify a `content` string as a message to send back to the client. The `realm` parameter should contain your realm of authentication (if any). The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. * _digest_auth_fail_response(**const std::string&** content, **const std::string&** realm = `""`, **const std::string&** opaque = `""`, **bool** reload_nonce = `false`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response in return to a failure during digest authentication. It allows to specify a `content` string as a message to send back to the client. The `realm` parameter should contain your realm of authentication (if any). The `opaque` represents a value that gets passed to the client and expected to be passed again to the server as-is. This value can be a hexadecimal or base64 string. The `reload_nonce` parameter tells the server to reload the nonce (you should use the value returned by the `check_digest_auth` method on the `http_request`. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. -* _deferred_response(**ssize_t(*cycle_callback_ptr)(char*, size_t)** cycle_callback, **const std::string&** content = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response that obtains additional content from a callback executed in a deferred way. It leaves the client in pending state (returning a `100 CONTINUE` message) and suspends the connection. Besides the callback, optionally, you can provide a `content` parameter that sets the initial message sent immediately to the client. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. To use `deferred_response` you need to have the `deferred` option active on your webserver (enabled by default). +* _deferred_response(**ssize_t(*cycle_callback_ptr)(shared_ptr<T>, char*, size_t)** cycle_callback, **const std::string&** content = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response that obtains additional content from a callback executed in a deferred way. It leaves the client in pending state (returning a `100 CONTINUE` message) and suspends the connection. Besides the callback, optionally, you can provide a `content` parameter that sets the initial message sent immediately to the client. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. To use `deferred_response` you need to have the `deferred` option active on your webserver (enabled by default). * The `cycle_callback_ptr` has this shape: - _**ssize_t** cycle_callback(**char*** buf, **size_t** max_size)_. - You are supposed to implement a function in this shape and provide it to the `deferred_repsonse` method. The webserver will provide a `char*` to the function. It is responsibility of the function to allocate it and fill its content. The method is supposed to respect the `max_size` parameter passed in input. The function must return a `ssize_t` value representing the actual size you filled the `buf` with. Any value different from `-1` will keep the resume the connection, deliver the content and suspend it again (with a `100 CONTINUE`). If the method returns `-1`, the webserver will complete the communication with the client and close the connection. + _**ssize_t** cycle_callback(**shared_ptr<T> closure_data, char*** buf, **size_t** max_size)_. + You are supposed to implement a function in this shape and provide it to the `deferred_repsonse` method. The webserver will provide a `char*` to the function. It is responsibility of the function to allocate it and fill its content. The method is supposed to respect the `max_size` parameter passed in input. The function must return a `ssize_t` value representing the actual size you filled the `buf` with. Any value different from `-1` will keep the resume the connection, deliver the content and suspend it again (with a `100 CONTINUE`). If the method returns `-1`, the webserver will complete the communication with the client and close the connection. You can also pass a `shared_ptr` pointing to a data object of your choice (this will be templetized with a class of your choice). The server will guarantee that this object is passed at each invocation of the method allowing the client code to use it as a memory buffer during computation. ### Setting additional properties of the response The `http_response` class offers an additional set of methods to "decorate" your responses. This set of methods is: @@ -825,36 +825,37 @@ You can also check this example on [github](https://github.com/etr/libhttpserver #### Example of a deferred response through callback #include - + using namespace httpserver; - + static int counter = 0; - - ssize_t test_callback (char* buf, size_t max) { + + ssize_t test_callback (std::shared_ptr closure_data, char* buf, size_t max) { if (counter == 2) { return -1; - } else { + } + else { memset(buf, 0, max); strcat(buf, " test "); counter++; return std::string(buf).size(); } } - + class deferred_resource : public http_resource { - public: - const std::shared_ptr render_GET(const http_request& req) { - return std::shared_ptr(new deferred_response(test_callback, "cycle callback response")); - } + public: + const std::shared_ptr render_GET(const http_request& req) { + return std::shared_ptr >(new deferred_response(test_callback, nullptr, "cycle callback response")); + } }; - + int main(int argc, char** argv) { webserver ws = create_webserver(8080); - + deferred_resource hwr; ws.register_resource("/hello", &hwr); ws.start(true); - + return 0; } @@ -864,6 +865,63 @@ To test the above example, you can run the following command from a terminal: You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_deferred.cpp). +#### Example of a deferred response through callback (passing additional data along) + #include + #include + + using namespace httpserver; + + std::atomic counter; + + ssize_t test_callback (std::shared_ptr > closure_data, char* buf, size_t max) { + int reqid; + if (closure_data == nullptr) { + reqid = -1; + } else { + reqid = *closure_data; + } + + // only first 5 connections can be established + if (reqid >= 5) { + return -1; + } else { + // respond corresponding request IDs to the clients + std::string str = ""; + str += std::to_string(reqid) + " "; + memset(buf, 0, max); + std::copy(str.begin(), str.end(), buf); + + // keep sending reqid + sleep(1); + + return (ssize_t)max; + } + } + + class deferred_resource : public http_resource { + public: + const std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr > closure_data(new std::atomic(counter++)); + return std::shared_ptr > >(new deferred_response >(test_callback, closure_data, "cycle callback response")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + deferred_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } + +To test the above example, you can run the following command from a terminal: + + curl -XGET -v localhost:8080/hello + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/deferred_with_accumulator.cpp). + [Back to TOC](#table-of-contents) ## Copying diff --git a/examples/Makefile.am b/examples/Makefile.am index ee879c70..137c071c 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,7 +19,7 @@ LDADD = $(top_builddir)/src/libhttpserver.la AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads +noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads deferred_with_accumulator hello_world_SOURCES = hello_world.cpp service_SOURCES = service.cpp @@ -35,6 +35,7 @@ digest_authentication_SOURCES = digest_authentication.cpp minimal_https_SOURCES = minimal_https.cpp minimal_file_response_SOURCES = minimal_file_response.cpp minimal_deferred_SOURCES = minimal_deferred.cpp +deferred_with_accumulator_SOURCES = deferred_with_accumulator.cpp url_registration_SOURCES = url_registration.cpp minimal_ip_ban_SOURCES = minimal_ip_ban.cpp benchmark_select_SOURCES = benchmark_select.cpp diff --git a/examples/deferred_with_accumulator.cpp b/examples/deferred_with_accumulator.cpp new file mode 100644 index 00000000..80a9cae8 --- /dev/null +++ b/examples/deferred_with_accumulator.cpp @@ -0,0 +1,70 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +using namespace httpserver; + +std::atomic counter; + +ssize_t test_callback (std::shared_ptr > closure_data, char* buf, size_t max) { + int reqid; + if (closure_data == nullptr) { + reqid = -1; + } else { + reqid = *closure_data; + } + + // only first 5 connections can be established + if (reqid >= 5) { + return -1; + } else { + // respond corresponding request IDs to the clients + std::string str = ""; + str += std::to_string(reqid) + " "; + memset(buf, 0, max); + std::copy(str.begin(), str.end(), buf); + + // keep sending reqid + sleep(1); + + return (ssize_t)max; + } +} + +class deferred_resource : public http_resource { + public: + const std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr > closure_data(new std::atomic(counter++)); + return std::shared_ptr > >(new deferred_response >(test_callback, closure_data, "cycle callback response")); + } +}; + +int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + deferred_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} + diff --git a/examples/minimal_deferred.cpp b/examples/minimal_deferred.cpp index a08599c2..a7a3e51d 100644 --- a/examples/minimal_deferred.cpp +++ b/examples/minimal_deferred.cpp @@ -24,7 +24,7 @@ using namespace httpserver; static int counter = 0; -ssize_t test_callback (char* buf, size_t max) { +ssize_t test_callback (std::shared_ptr closure_data, char* buf, size_t max) { if (counter == 2) { return -1; } @@ -39,7 +39,7 @@ ssize_t test_callback (char* buf, size_t max) { class deferred_resource : public http_resource { public: const std::shared_ptr render_GET(const http_request& req) { - return std::shared_ptr(new deferred_response(test_callback, "cycle callback response")); + return std::shared_ptr >(new deferred_response(test_callback, nullptr, "cycle callback response")); } }; diff --git a/src/deferred_response.cpp b/src/deferred_response.cpp index fe17cb29..c1547962 100644 --- a/src/deferred_response.cpp +++ b/src/deferred_response.cpp @@ -28,44 +28,11 @@ namespace httpserver namespace details { -ssize_t cb(void* cls, uint64_t pos, char* buf, size_t max) +MHD_Response* get_raw_response_helper(void* cls, ssize_t (*cb)(void*, uint64_t, char*, size_t)) { - ssize_t val = static_cast(cls)->cycle_callback(buf, max); - if(val == -1) - { - static_cast(cls)->completed = true; - } - - return val; -} - -} - -MHD_Response* deferred_response::get_raw_response() -{ - if(!completed) - { - return MHD_create_response_from_callback( - MHD_SIZE_UNKNOWN, - 1024, - &details::cb, - this, - NULL - ); - } - else - { - return static_cast(this)->get_raw_response(); - } + return MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 1024, cb, cls, NULL); } -void deferred_response::decorate_response(MHD_Response* response) -{ - if(completed) - { - static_cast(this)->decorate_response(response); - } } } - diff --git a/src/httpserver/deferred_response.hpp b/src/httpserver/deferred_response.hpp index c5a9f1ff..02cc745d 100644 --- a/src/httpserver/deferred_response.hpp +++ b/src/httpserver/deferred_response.hpp @@ -25,6 +25,7 @@ #ifndef _DEFERRED_RESPONSE_HPP_ #define _DEFERRED_RESPONSE_HPP_ +#include #include "httpserver/string_response.hpp" namespace httpserver @@ -32,37 +33,37 @@ namespace httpserver namespace details { - ssize_t cb(void*, uint64_t, char*, size_t); -}; - -typedef ssize_t(*cycle_callback_ptr)(char*, size_t); + MHD_Response* get_raw_response_helper(void* cls, ssize_t (*cb)(void*, uint64_t, char*, size_t)); +} +template class deferred_response : public string_response { public: explicit deferred_response( - cycle_callback_ptr cycle_callback, + ssize_t(*cycle_callback)(std::shared_ptr, char*, size_t), + std::shared_ptr closure_data, const std::string& content = "", int response_code = http::http_utils::http_ok, const std::string& content_type = http::http_utils::text_plain ): string_response(content, response_code, content_type), cycle_callback(cycle_callback), - completed(false) + closure_data(closure_data) { } deferred_response(const deferred_response& other): string_response(other), cycle_callback(other.cycle_callback), - completed(other.completed) + closure_data(other.closure_data) { } deferred_response(deferred_response&& other) noexcept: string_response(std::move(other)), cycle_callback(std::move(other.cycle_callback)), - completed(other.completed) + closure_data(std::move(other.closure_data)) { } @@ -72,7 +73,7 @@ class deferred_response : public string_response (string_response&) (*this) = b; this->cycle_callback = b.cycle_callback; - this->completed = b.completed; + this->closure_data = b.closure_data; return *this; } @@ -83,7 +84,7 @@ class deferred_response : public string_response (string_response&) (*this) = std::move(b); this->cycle_callback = std::move(b.cycle_callback); - this->completed = b.completed; + this->closure_data = std::move(b.closure_data); return *this; } @@ -92,13 +93,20 @@ class deferred_response : public string_response { } - MHD_Response* get_raw_response(); - void decorate_response(MHD_Response* response); + MHD_Response* get_raw_response() + { + return details::get_raw_response_helper((void*) this, &(this->cb)); + } + private: - cycle_callback_ptr cycle_callback; - bool completed; + ssize_t (*cycle_callback)(std::shared_ptr, char*, size_t); + std::shared_ptr closure_data; - friend ssize_t details::cb(void* cls, uint64_t pos, char* buf, size_t max); + static ssize_t cb(void* cls, uint64_t pos, char* buf, size_t max) + { + deferred_response* dfr = static_cast*>(cls); + return dfr->cycle_callback(dfr->closure_data, buf, max); + } }; } diff --git a/test/integ/deferred.cpp b/test/integ/deferred.cpp index c853ab06..b846ee0c 100644 --- a/test/integ/deferred.cpp +++ b/test/integ/deferred.cpp @@ -47,7 +47,12 @@ size_t writefunc(void *ptr, size_t size, size_t nmemb, std::string *s) static int counter = 0; -ssize_t test_callback (char* buf, size_t max) +struct test_data +{ + int value; +}; + +ssize_t test_callback(std::shared_ptr closure_data, char* buf, size_t max) { if (counter == 2) { @@ -62,12 +67,41 @@ ssize_t test_callback (char* buf, size_t max) } } +ssize_t test_callback_with_data(std::shared_ptr closure_data, char* buf, size_t max) +{ + if (counter == 2) + { + return -1; + } + else + { + memset(buf, 0, max); + strcat(buf, ("test" + std::to_string(closure_data->value)).c_str()); + + closure_data->value = 84; + + counter++; + return std::string(buf).size(); + } +} + class deferred_resource : public http_resource { public: const shared_ptr render_GET(const http_request& req) { - return shared_ptr(new deferred_response(test_callback, "cycle callback response")); + return shared_ptr>(new deferred_response(test_callback, nullptr, "cycle callback response")); + } +}; + +class deferred_resource_with_data : public http_resource +{ + public: + const shared_ptr render_GET(const http_request& req) + { + std::shared_ptr internal_info(new test_data); + internal_info->value = 42; + return shared_ptr>(new deferred_response(test_callback_with_data, internal_info, "cycle callback response")); } }; @@ -82,6 +116,8 @@ LT_BEGIN_SUITE(deferred_suite) void tear_down() { + counter = 0; + ws->stop(); delete ws; } @@ -105,6 +141,24 @@ LT_BEGIN_AUTO_TEST(deferred_suite, deferred_response) curl_easy_cleanup(curl); LT_END_AUTO_TEST(deferred_response) +LT_BEGIN_AUTO_TEST(deferred_suite, deferred_response_with_data) + deferred_resource_with_data resource; + ws->register_resource("base", &resource); + curl_global_init(CURL_GLOBAL_ALL); + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "test42test84"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(deferred_response_with_data) + LT_BEGIN_AUTO_TEST_ENV() AUTORUN_TESTS() LT_END_AUTO_TEST_ENV()