Did you know ... Search Documentation:
Packs (add-ons) for SWI-Prolog

Package "arouter"

Title:Alternative HTTP path router
Rating:Not rated. Create the first rating!
Latest version:1.1.1
SHA1 sum:b9639e19ebc606060a98ffc8ea12da9061f807fd
Author:Raivo Laanemets http://rlaanemets.com/
Home page:https://github.com/rla/alternative-router

Reviews

No reviews. Create the first review!.

Details by download location

VersionSHA1#DownloadsURL
0.0.12cca79ac4c26b542853e0720a6347379dba07d8b2http://packs.rlaanemets.com/alternative-router/arouter-0.0.1.tgz
1.0.02a8f103bd01002a14dda67e92c61255699db9a296https://github.com/honnix/alternative_router.git
830a2eb869d9e48a8e230de18137d17016086e7732http://packs.rlaanemets.com/alternative-router/arouter-1.0.0.tgz
1.1.1b1d2f6ca0076c1ecce33b511a816fc48ad3172bf1https://github.com/honnix/alternative_router.git
b9639e19ebc606060a98ffc8ea12da9061f807fd58http://packs.rlaanemets.com/alternative-router/arouter-1.1.1.tgz

alternative-router

This is an alternative router/dispatcher to Swi-Prolog's http_dispatch module. The main motivation for creating this module was more convenient (less verbose) implementation of RESTful web services.

![Build Status](https://travis-ci.org/rla/alternative-router)

Example

:- use_module(library(http/thread_httpd)).
:- use_module(library(arouter)).

:- route_get(hello/Name, handle_hello(Name)).

handle_hello(Name):-
    format('Content-Type: text/plain; charset=UTF-8~n~n'),
    format('Hello ~w', [Name]).

:- http_server(route, [port(8008)]).

Save it to a file, run it, and then visit http://localhost:8008/hello/world.

Path terms

Normal path terms correspond directly to URL paths with the implicit root (/) symbol. For example, path/to/something corresponds to URL `http://example.com/path/to/something`. To match the root itself, / alone must be used. To match an URL path with a slash in the end, an empty atom has to be used at the end of the path term. For example, to match URL `http://example.com/path/to/something/`, a path term path/to/something/'' must be used.

Using with http_dispatch

Make fallback to http_dispatch/1 like this:

handle_request(Request):-
(   route(Request)
->  true
;   http_dispatch(Request)).

and use handle_request/1 as the handler in http_server.

Routing with fallbacks

To route with fallbacks:

:- http_server(route_with_fallbacks([module:fallback1, module:fallback2]), [port(8008)]).

If no matched route could be found, fallbacks will be tried according to the order until one succeeds or routing fails completely. This is particularly useful when combining routing table, static content handler and 404 handler together.

:- http_server(route_with_fallbacks([module:handle_static, module:handle_404]), [port(8008)]).

handle_static :-
    http_current_request(Request),
    http_reply_from_files('/path/to/static/content', [], Request).

handle_404 :-
    http_current_request(Request),
    http_404([], Request).

Before-handler

Routes can have intermediate goals. The following example is cheking auth information before executing the handler:

:- route_get(api/resource, auth, handle_resource).

auth(Next):-
(   http_session_data(user(User)),
    memberchk(role(admin), User)
->  call(Next)
;   current_request(Request),
    memberchk(path(Path), Request),
    current_output(Out),
    http_reply(forbidden(Path), Out, [])).

handle_resource:-
    ...

The before-handler predicate calls its first argument when the request should pass it. Otherwise it should produce the response itself.

Route-path matching

A path will match the route when:

  • Path and rule are /.
  • Path is an atomic value and the route is same atomic value or a variable.
  • Path is /(P1, P2), route is /(R1, R2) and R1 matches P1 and R2 matches P2.

Route priority

Routes are tested in reverse to the adding order. Overlapping routes can be added. Structurally equivalent routes cannot be added. Routes are overlapping when one route has atom in position of route variable in the other. Routes are structurally equivalent when:

  • Both routes are /.
  • Both routes are variables.
  • Both routes are same atomic values.
  • One route is /(A1, B1), the other is /(A2, B2) and A1, A2 are structurally equivalent and B1, B2 are structurally equivalent.

Structural equivalence is used for detecting duplicate rules. This plays nice with the make/0 goal.

Trailing /

To define a route with trailing /:

:- route_get(hello/Name/'', handle_hello(Name)).

Blueprint

Got the idea from Flask.

To register a blueprint (with or without a trailing / are both fine because it is removed anyway when registering):

:- blueprint(b, '/a/long/prefix').

Or:

:- blueprint(b, '/a/long/prefix/').

To register a route under the blueprint:

:- route_get_b(b, hello/Name, handle_hello(Name)).

Or:

:- b.route_get(hello/Name, handle_hello(Name)).

To register a route directly under the blueprint:

:- route_get_b('', handle_hello(Name)).

To register a route directly under the blueprint with a trailing /:

:- route_get_b(/, handle_hello(Name)).

Non-deterministic routing

In some cases another matching (overlapping) route might have to be tried. This can be done by throwing arouter_next from the current route handler. Example:

:- route_get(something/specific, handle_specific).

handle_specific:-
    ...

:- route_get(something/Generic, handle_generic(Generic)).

handle_generic(Generic):-
    (   Generic = specific
    ->  throw(arouter_next)
    ;   ...).

The handler handle_specific will handle the request in this case after throwing arouter_next from the handle_generic handler (handlers are tried in reverse order of adding them).

List of predicates

Adding new routes

`route_get(+Route, :Goal)` registers a new GET handler.

`route_put(+Route, :Goal)` registers a new PUT handler.

`route_del(+Route, :Goal)` registers a new DELETE handler.

`route_post(+Route, :Goal)` registers a new POST handler.

`routes(+Route, Methods, :Goal)` registers routes according to Methods.

`route_get_b(+Blueprint, +Route, :Goal)` registers a new GET handler.

`route_put_b(+Blueprint,+Route, :Goal)` registers a new PUT handler.

`route_del_b(+Blueprint,+Route, :Goal)` registers a new DELETE handler.

`route_post_b(+Blueprint,+Route, :Goal)` registers a new POST handler.

`routes_b(+Blueprint,+Route, Methods, :Goal)` registers routes according to Methods.

`route_get(+Route, :Before, :Goal)` registers a new GET handler with a before action.

`route_put(+Route, :Before, :Goal)` registers a new PUT handler with a before action.

`route_del(+Route, :Before, :Goal)` registers a new DELETE handler with a before action.

`route_post(+Route, :Before, :Goal)` registers a new POST handler with a before action.

`routes(+Route, +Methods, :Bedore, :Goal)` register routes according to Methods with a before action.

`route_get_b(+Blueprint,+Route, :Before, :Goal)` registers a new GET handler with a before action.

`route_put_b(+Blueprint,+Route, :Before, :Goal)` registers a new PUT handler with a before action.

`route_del_b(+Blueprint,+Route, :Before, :Goal)` registers a new DELETE handler with a before action.

`route_post_b(+Blueprint,+Route, :Before, :Goal)` registers a new POST handler with a before action.

`routes_b(+Blueprint,+Route, +Methods, :Bedore, :Goal)` register routes according to Methods with a before action.

`new_route(+Method, +Route, :Goal)` registers a new custom method handler.

`new_route_b(+Blueprint, +Method, +Route, :Goal)` registers a new custom method handler.

`new_route(+Method, +Route, :Before, :Goal)` registers a new custom method handler with a before action.

`new_route_b(+Blueprint, +Method, +Route, :Before, :Goal)` registers a new custom method handler with a before action.

All predicates above will throw an error when the Route does not contain the suitable term.

Route handler predicates can take variables from the route. Example:

:- route_get(post/show/Slug, post_show(Slug)).

post_show(Slug):-
    ...

However, they do not take the Request argument, unlike the http_dispatch handlers. To obtain the current request, use the http_current_request/1 predicate.

Dispatching

route(+Request) - takes given request and attempts to find suitable handler.

Request must contain terms method(Method) and path(Path). Throws handler_failed(Method, Path) when handler was found but it failed during execution.

Inspecting routes

Use the `route(?Method, ?Route, ?Before, ?Goal)` predicate.

Removing routes

Use the route_remove(Method, Route) predicate. Both arguments can be unbound or partially instantiated. Or route_remove_b(Blueprint, Method, Route).

Installation

To install as a package:

pack_install(arouter).

Tested with Swi-Prolog 7.x but should work with earlier versions too.

Full API documentation

See http://packs.rlaanemets.com/alternative-router/doc/.

Running tests

In the package root, insert into swipl:

[tests/tests].
run_tests.

Or if you cloned the repo:

make test

Debugging

Enable debugging with:

?- use_module(library(debug)).
?- debug(arouter).

Changelog

  • 2015-11-01 version 1.1.1. Attempt to preserve route order on make.
  • 2015-11-01 version 1.1.0. Non-deterministic routing.
  • 2014-05-08 version 1.0.0. Precise route matching semantics.
  • 2014-02-01 version 0.0.1

Bug reports/feature requests

Please send bug reports/feature request through the GitHub project page.

License

The MIT license. See the LICENSE file.

Contents of pack "arouter"

Pack contains 11 files holding a total of 37.1K bytes.