cro-http-server
Cro::HTTP::Server
The Cro::HTTP::Server class is the most convenient way to host a HTTP or
HTTPS service in Cro. Instances of Cro::HTTP::Server do the Cro::Service
role, and as such have the start and stop methods. The only required
configuration for a Cro::HTTP::Server is an application, which is any
Cro::Transform that consumes a Cro::HTTP::Request and produces a
Cro::HTTP::Response. This will often be produced using Cro::HTTP::Router.
A minimal setup
The following example demonstrates how Cro::HTTP::Server can be used to host
a simple "Hello, world" HTTP application, configuring it to listen on port 8888
of localhost. A signal handler is added to allow for clean shutdown of the
server upon Ctrl + C.
use Cro::HTTP::Router;
use Cro::HTTP::Server;
my $application = route {
get -> {
content 'text/html', '<strong>Hello, world!</strong>';
}
}
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application
);
$hello-service.start;
react whenever signal(SIGINT) {
$hello-service.stop;
exit;
}Configuring HTTPS
By default, Cro::HTTP::Server will operates as a HTTP server. To set it up
as a HTTPS server instead, use the tls named parameter. It expects to receive
a hash of arguments providing the TLS configuration, which it will in turn pass
to the constructor of Cro::TLS::Listener. Typically, certificate-file and
private-key-file, specifying the locations of files containing a certificate
and private key respectively, should be passed.
my %tls = private-key-file => 'server.pem',
certificate-file => 'cert.pem';
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :%tls, :$application
);Request body parsers and response body serializers
Additional request body parsers (implementations of Cro::BodyParser)
and response body serializers (implementations of Cro::BodySerializer)
can be added at the server level. Alternatively, the set of default set of
body parsers and serializers can be replaced entirely.
To add extra body parsers to the set of defaults, pass a list of them to
add-body-parsers.
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application,
add-body-parsers => [
YAMLBodyParser,
XMLBodyParser
]
);These will take precedence over (that is, tested for applicability ahead of)
the default set of body parsers. To replace those entirely, pass a list of the
body parsers to use as the body-parsers named parameter:
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application,
body-parsers => [
# Don't parse any kind of body except a JSON one; anything else
# will throw an exception when `.body` is called.
Cro::HTTP::BodyParser::JSON
]
);If both body-parsers and add-body-parsers is used, then both will be used,
with those in add-body-parsers again having higher precedence.
If the class that represents a body parser or body serializer does not have attributes, passing type object is equivalent to passing an instance.
add-body-parsers => [
YAMLBodyParser.new
]
# as well as
add-body-parsers => [
YAMLBodyParser
]A similar scheme applies for body serializers. Use add-body-serializers to
add extra ones to the defaults:
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application,
add-body-serializers => [
YAMLBodySerializer,
XMLBodySerializer
]
);Or replace the set of body serializers entirely by passing body-serializers:
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application,
body-serializers => [
# The body can only ever be something that can be JSON serialized.
Cro::HTTP::BodySerializer::JSON
]
);If both add-body-serializers and body-serializers are passed, they both
will be used, with those in add-body-serializers taking precedence.
Middleware
HTTP middleware is implemented as a Cro::Transform. There are four places
that HTTP middleware can be inserted. There are, in order of processing:
before-parse - operates on the raw bytes coming over the network prior to the
Cro::HTTP::RequestParserseeing them. The transform should consume aCro::TCP::Messageand produce aCro::TCP::Message. It is relatively unusual to need to insert middleware at this stage, though it could be useful for getting rate limiting in early before the effort to even parse a request has been expended, for example.before - operates on requests after they have been parsed, but before they reach the application. Consumes a
Cro::HTTP::Requestand produces aCro::HTTP::Request. This is a common place to put middleware that does authentication, authorization, session handling, CSRF protection, and so forth.after - operates on responses produced by the application. Consumes a
Cro::HTTP::Responseand produces aCro::HTTP::Response. This is a common place to put middleware that does things like logging and inserting headers to increase security (likeX-Frame-Options,Strict-Transport-Security,Content-Security-Policy, and so forth).after-serialize - operates on the bytes sent back over the network in response to a request. Consumes a
Cro::TCP::Messageand produces aCro::TCP::Message. It is unusual to need to insert middleware at this stage.
The names of these places are named parameters that can be passed to the
Cro::HTTP::Server constructor. Either a single Cro::Transform or an
Iterable (for example, List) of Cro::Transforms may be passed.
For example, the following piece of middleware:
class StrictTransportSecurity does Cro::Transform {
has Duration:D $.max-age is required;
method consumes() { Cro::HTTP::Response }
method produces() { Cro::HTTP::Response }
method transformer(Supply $pipeline --> Supply) {
supply {
whenever $pipeline -> $response {
$response.append-header:
'Strict-Transport-Security',
"max-age=$!max-age";
emit $response;
}
}
}
}Could be applied as follows:
my Cro::Service $hello-service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application,
after => StrictTransportSecurity.new(Duration.new(30 * 24 * 60 * 60))
);HTTP versions
The :http option can be passed to control which versions of HTTP should be
supported. It can be passed a single item or list. Valid options are 1.1
(which will implicitly handle HTTP/1.0 too) and 2.
:http<1.1> # HTTP/1.1 only
:http<2> # HTTP/2 only
:http<1.1 2> # HTTP/1.1 and HTTP/2 (TLS only; selected by ALPN)The default is :http<1.1> for a HTTP server. For a HTTPS server, if the TLS
library (IO::Socket::Async::SSL) detects that ALPN support is available then
it will default to :http<1.1 2>; otherwise it will default to :http<1.1>.
If :http<1.1 2> is specified and ALPN support is not available, then an
exception will be thrown. ALPN is the only supported mechanism for selecting
between HTTP/1.1 and HTTP/2, thus the requirement on it for this configuration
(and why there is no option for both versions with HTTP).
Allowed request methods
By default, the following HTTP request methods are allowed:
GET
HEAD
POST
PUT
DELETE
PATCH
CONNECT
OPTIONS
A request using a request method other than these shall be refused. To change
set of allowed request methods, pass a list using the allowed-methods named
constructor parameter:
my $service = Cro::HTTP::Server.new(
:host('localhost'), :port(8888), :$application,
:allowed-methods<GET HEAD PUT POST DELETE LINK UNLINK>
);