Boost logo

Boost :

Subject: Re: [boost] [beast] Request for Discussion
From: Christof Donat (cd_at_[hidden])
Date: 2016-09-29 05:23:11


Hi,

Am 29.09.2016 03:06, schrieb Vinnie Falco:
>> I don't think a synchronous interface is necessary.
>
> Probably true. A synchronous interface would require a redundant code
> path.

I second that.

>> I'd be happy to work with you on creating a proposed higher level
>> interface
>> that I think would stand a chance at WG21. It may improve consensus
>> for the
>> Boost review, but if it has the opposite effect we could just rip it
>> out.
>> What do you think?
>
> There's a lot of merit to a team effort for building a good design.
> However, I'm not sure that I want to broaden the scope of Beast's
> current interfaces.

I agree, that there is much value in having one library for the low
level stuff, which beast is very well suited. I also agree, that higher
level APIs should be built on top of that instead of putting everything
in the same project.

> With respect to a high level client interface, [...]
> The cool thing about this, is that someone has already
> created such an interface. It is Huu Nguyen, the author of "C++
> Requests: Curl for People". And the repository is here:
> https://github.com/whoshuu/cpr
> [...]
> I reached out to the author and he related to me that his ambition is
> to iterate on this library to produce a version that does not have
> curl as a dependency. I think the most productive use of development
> energies with respect to building a high level HTTP client would go
> towards supporting the refinement of this library, or if the author
> does not have the resources then to examine his interface and use it
> as a model for creating such a library (using Beast for the protocol
> level support).

Really great.

> On the subject of a turn-key generic HTTP server I think there are
> more possibilities that I could put something together. [...]

I think frameworks for HTTP Servers should be separated in various
parts:

1. Low Level HTTP stuff - that is what beast is perfect for
2. A Request Router
3. A Database Access Layer - most Web applications are good off to use a
database
4. A way to create the HTML output without mixing it with the code

For 3 and 4 I'd propose Roland Bocks sqlpp11
(https://github.com/rbock/sqlpp11) and kiss-templates
(https://github.com/rbock/kiss-templates)

So some weeks ago I started a request router as hobby project based on
beast v1. It does not even remotely work yet and of course the interface
is not at all fixed now. As usuall hobby projects might get a lot of
love, but never enough time, so the advance is very slow at the moment.

The routing looks like this:

auto appRoute = route_url(route("^/foo/([0-9]+)"s,
app::foo{url_match_cast<int>{"$1"s}}),
                           route("^/bar/([a-fA-F0-9]*)"s,
app::bar{url_match_cast<std::string>{"$1"s}}),
                           route("^/baz/([0-9]*([.][0-9]+)?)",
app::baz{url_match_cast<double>{"$1"}}));

auto r = route_url(route("^/oldAppBase(.*)$"s,
redirect("/newAppBase$1"s)),
                    route("^/newAppBase(.*)$"s, appRoute("$1"s)),
                    route("^/webSockets(.*)$"s, sockHandler{"$1"s}),
                    route("^/otherWebSockets(.*)$"s,
otherSockHandler{"$1"s}),
                    route("^/static(.*)$"s,
static_file_handler{boost::filesystem::current_path(), "$1"s}));

The idea here is, that you can create independent components, like
"appRoute", and mount them together to build a complete application. The
Components themself can be build with multiple handlers, that can be
components themselves as well. This is inspired by Django.

The handlers might look like this:

namespace app {
     class foo: public base_http_handler<foo> {
         using base_http_handler<foo>::operator();
         url_match_cast<int> get_foo_;
         public:
             foo(const url_match_cast<int>& get_foo):
                 get_foo_{get_foo} {};

             template<typename Request>
             beast::http::response_v1<beast::http::string_body> operator
() (const std::smatch& match, const Request& req) {
                 auto resp =
beast::http::response_v1<beast::http::string_body>{};
                 resp.status = 200;
                 resp.reason = beast::http::reason_string(200);

                 resp.headers.replace("Content-Type", "text/html");
                 auto foo_text =
boost::lexical_cast<std::string>(get_foo_(match));
                 resp.body = "<html><head><title>foo: "s + foo_text +
"</title></head><body>foo: "s + foo_text + "</body></html>"s;
                 resp.headers.replace("Content-Length",
resp.body.size());
            return resp;
             };
     };
}

But they can also just be lambdas with the signature like this call
operator. With generalized captures, this could also be writen as:

route("^/foo/([0-9]+)"s,
       [get_foo_=url_match_cast<int>{"$1"s}] (const std::smatch& match,
                                              const Request& req)
         -> beast::http::response_v1<beast::http::string_body> operator
() {
           // ...
           auto foo = get_foo_(match);
           // ...
       })

The parameter match is the match of the regular expression on the path.
With get_foo_(match) we get the first parameter ("$1") from that match
as an integer (url_match_cast<int>).

I think, the main loop should not be part of the framework. My current
idea would look like to use std::future<boost::system::error_code> as
return type for the call operator of a rout_url:

std::future<boost::system::error_code> success_async = r(url, req,
my_socket);

What do you think about such an interface?

Christof


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk