Skip to content

Add data closure to the cycle_callback of deferred response #145

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 74 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 <httpserver.hpp>

using namespace httpserver;

static int counter = 0;

ssize_t test_callback (char* buf, size_t max) {
ssize_t test_callback (std::shared_ptr<void> 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<http_response> render_GET(const http_request& req) {
return std::shared_ptr<deferred_response>(new deferred_response(test_callback, "cycle callback response"));
}
public:
const std::shared_ptr<http_response> render_GET(const http_request& req) {
return std::shared_ptr<deferred_response<void> >(new deferred_response<void>(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;
}

Expand All @@ -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 <atomic>
#include <httpserver.hpp>

using namespace httpserver;

std::atomic<int> counter;

ssize_t test_callback (std::shared_ptr<std::atomic<int> > 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<http_response> render_GET(const http_request& req) {
std::shared_ptr<std::atomic<int> > closure_data(new std::atomic<int>(counter++));
return std::shared_ptr<deferred_response<std::atomic<int> > >(new deferred_response<std::atomic<int> >(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
Expand Down
3 changes: 2 additions & 1 deletion examples/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
70 changes: 70 additions & 0 deletions examples/deferred_with_accumulator.cpp
Original file line number Diff line number Diff line change
@@ -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 <atomic>
#include <httpserver.hpp>

using namespace httpserver;

std::atomic<int> counter;

ssize_t test_callback (std::shared_ptr<std::atomic<int> > 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<http_response> render_GET(const http_request& req) {
std::shared_ptr<std::atomic<int> > closure_data(new std::atomic<int>(counter++));
return std::shared_ptr<deferred_response<std::atomic<int> > >(new deferred_response<std::atomic<int> >(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;
}

4 changes: 2 additions & 2 deletions examples/minimal_deferred.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> closure_data, char* buf, size_t max) {
if (counter == 2) {
return -1;
}
Expand All @@ -39,7 +39,7 @@ ssize_t test_callback (char* buf, size_t max) {
class deferred_resource : public http_resource {
public:
const std::shared_ptr<http_response> render_GET(const http_request& req) {
return std::shared_ptr<deferred_response>(new deferred_response(test_callback, "cycle callback response"));
return std::shared_ptr<deferred_response<void> >(new deferred_response<void>(test_callback, nullptr, "cycle callback response"));
}
};

Expand Down
37 changes: 2 additions & 35 deletions src/deferred_response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<deferred_response*>(cls)->cycle_callback(buf, max);
if(val == -1)
{
static_cast<deferred_response*>(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<string_response*>(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<string_response*>(this)->decorate_response(response);
}
}

}

38 changes: 23 additions & 15 deletions src/httpserver/deferred_response.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,45 @@
#ifndef _DEFERRED_RESPONSE_HPP_
#define _DEFERRED_RESPONSE_HPP_

#include <memory>
#include "httpserver/string_response.hpp"

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 T>
class deferred_response : public string_response
{
public:
explicit deferred_response(
cycle_callback_ptr cycle_callback,
ssize_t(*cycle_callback)(std::shared_ptr<T>, char*, size_t),
std::shared_ptr<T> 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))
{
}

Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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<T>, char*, size_t);
std::shared_ptr<T> 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<T>* dfr = static_cast<deferred_response<T>*>(cls);
return dfr->cycle_callback(dfr->closure_data, buf, max);
}
};

}
Expand Down
Loading