Guide Home
POD Home

Request Handling

When a request is received, Sloop cascades through the handlers hash looking for a match on the path; '/' represents the base of each level in the tree. This does not need to exist, but if it does for the root, then every request will find a handler. Otherwise, requests that do match anything receive a 404 response (via Sloop::Client::generic()). For example:

'/' => sub {
    # Handler A
},
foo => {
    '/' => sub {
        # Handler B
    },
    bar => sub {
        # Handler C
    }
},
banana => sub {
    #Handler D
}

Notice this means any request beginning with /foo/bar (of which there is obviously an infinite number) will go to Handler C. This is probably not desirable behaviour, if Handler C always returns the same content, and represents a singular URL. However, Sloop::Client::Request::pathFromLevel() provides a means of resolving this.

sub {
    my $client = shift;
    if ($client->{request}->pathFromLevel) {
        $client->generic(404);
        return;
    }
    # Requests with a trailing path have been weeded out.
}

Details about pathFromLevel are in the POD for Sloop::Client::Request. This method will return an empty array if the complete path has been absorbed by the cascade. Otherwise, it returns the remainder elements; going back to the example, if the complete request path was /foo/bar/alpha/foxtrot calling $client->{request}->pathFromLevel in Handler B would return ('alpha', 'foxtrot'). A dynamic handler can use this remainder as a parameter, and return an error page (such as the generic 404) when it is invalid.

Including this check in the handler code for each and every one is a bit tedious, particularly if all they want to do is reject requests with a trailing path. Here Sloop::Lib::checkURL() comes in handy. It takes three arguments, a Sloop::Client object, a minimum number of permitted extra path elements (which may be zero, or undefined), and a maximum (which may also be zero or undefined).

use Sloop::Lib qw(checkURL);

sub {
    my $client = checkURL(shift);
    return if !$client;
    # Requests with a trailing path have been weeded out.
}

Details about this function are documented in the POD for Sloop::Lib. Leaving out the last two parameters means no trailing path will be permitted; requests which do not meet the criteria will receive a 404 reply and checkURL() will return undefined, otherwise it returns the client object passed in.

For further brevity, Sloop has the following special form for declaring handlers using an array reference of (up to) three elements:

'/' => [ sub {
    # Requests without at least two trailing elements,
    # or with more than four, have been weeded out.
    my $client = shift;
}, 2, 4 ]

The first element must be a subroutine reference to a normal handler. The last two are optional and represent the min and max arguments to checkURL(). In the example they are specified, which is why this handler will reject requests without 2-4 additional path elements. Beware that if those are an exact match for a parallel handler's path spec, the cascade will lead there and not here (as with /foo/bar vs. /foo in the first example).

Matching in the cascade is exact and case sensitive (as per usual with hash key look ups). If you want to work around this, you can define a custom client constructor which includes a postRequestHandler; this callback will fire after the request has been parsed and a Sloop::Client::Request object attached, but before the handler cascade occurs, giving you the opportunity to examine and modify $client->{request}->{path}. The handler is set globally via the server constructor call:

my $Sloop = Sloop::Server->new (
    client_constructor => sub {
        Sloop::Client::Regular::new-> (
            @_,
            postRequestHandler => sub {
                my $client = shift;
                # Do whatever with $client->{request}, etc.
            }
        )
    }
);

Make sure you follow this pattern passing @_ through. You can redefine $Sloop->{client_constructor} at any time, but again, since it fires before the request path is resolved, it applies to all incoming requests.

Anatomy of a Handler

The handler is simple: it receives one argument, a Sloop::Client derived object (::Regular, or for HTTPS connections, ::Secure), and requires nothing be returned. The client object will have an attached Sloop::Client::Request object with the details of the request.

Inside the handler, if you don't want to leak the client object, you must either send a response (e.g., via $client->reply()) or else call $client->cancel. Otherwise, the client connection will never time out (because it is still waiting on a reply), and will never be returned to you again via a callback. In all likelihood the actual remote client will disconnect at some point and this will free the corresponding object, but if you do not want to reply, nothing productive will happen with the connection anyway — so just use $client->cancel. This immediately closes the socket properly and removes internal references to the object.

Static Content

Directories (including complete trees) of static pages can be handled with Sloop::Static::directory(), which will return a 404 if the remaining path is not valid (i.e., checkURL() or the array shorthand should not be used).