Skip to content

Commit

Permalink
[WIP] Provide a routing context instead of a stack
Browse files Browse the repository at this point in the history
Each context consist of a mapping of 'string' to 'Value?' with a
possible parent context.

It is the best compromise between a mapping for the parameters and a
routing stack.

Keys are resolved recursively in the context tree.

Remove 'Request.params', it's not needed anymore.

Update tests, documentation and examples.
  • Loading branch information
arteymix committed Jan 16, 2016
1 parent 3ab48b9 commit 9b244a3
Show file tree
Hide file tree
Showing 20 changed files with 270 additions and 266 deletions.
4 changes: 2 additions & 2 deletions docs/application.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ a :doc:`route` instance.

.. code:: vala
app.get ("", (req, res, next, stack) => {
app.get ("", (req, res, next, context) => {
res.body.write_all ("Hello world!".data, null);
});
Expand All @@ -48,7 +48,7 @@ processing. The callback, named handler, receives four arguments:
- a :doc:`vsgi/request` that describes a resource being requested
- a :doc:`vsgi/response` that correspond to that resource
- a ``next`` continuation to `keep routing`
- a routing ``stack`` to retrieve and store states from previous and for
- a routing ``context`` to retrieve and store states from previous and for
following handlers

Serving the application
Expand Down
8 changes: 4 additions & 4 deletions docs/redirection-and-error.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ the :doc:`router` can handle them properly.
throw new ClientError.NOT_FOUND ("");
});
During status handling, the error message will be pushed on the routing stack
as a ``string``.
During status handling, the error message will be pushed on the routing context
as a ``string`` to the key ``message``.

.. code:: vala
app.status (404, (req, res, next, stack) => {
var message = stack.pop_tail ().get_string ();
app.status (404, (req, res, next, context) => {
var message = context["message"].get_string ();
});
59 changes: 16 additions & 43 deletions docs/router.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,21 @@ It can be performed automatically with ``Router.use``:
next (req, res);
});
Routing stack
-------------
Routing context
---------------

During the routing, states can obtained from a previous handler or passed to
the next one using the routing stack. The stack is a simple `GLib.Queue`_ that
can be accessed from its head or tail.

.. warning::

The queue tail is used to perform stack operations with ``push_tail`` and
``pop_tail``.

.. _GLib.Queue: http://valadoc.org/#!api=glib-2.0/GLib.Queue
the next one using the routing context.

.. code:: vala
app.get ("", (req, res, next, stack) => {
stack.push_tail ("some value");
app.get ("", (req, res, next, context) => {
context["some key"] = "some value";
next (req, res);
});
app.get ("", (req, res, next, stack) => {
var some_value = stack.pop_tail ().get_string ();
app.get ("", (req, res, next, context) => {
var some_value = context["some key"]; // or context.parent["some key"]
});
HTTP methods
Expand Down Expand Up @@ -151,14 +143,14 @@ Thrown status code can be handled by a ``HandlerCallback`` pretty much like how
typically matched requests are being handled.

The received :doc:`vsgi/request` and :doc:`vsgi/response` object are in the
same state they were when the status was thrown. The error message is stacked
and available in the ``HandlerCallback`` last parameter.
same state they were when the status was thrown. The error message is bound to
the key ``message`` in the routing context.

.. code:: vala
app.status (Soup.Status.NOT_FOUND, (req, res, next, stack) => {
app.status (Soup.Status.NOT_FOUND, (req, res, next, context) => {
// produce a 404 page...
var message = stack.pop_tail ().get_string ();
var message = context["message"].get_string ();
});
Similarly to conventional request handling, the ``next`` continuation can be
Expand Down Expand Up @@ -257,7 +249,7 @@ application.
a maximum inter-operability with other frameworks based on VSGI.

The following example delegates all ``GET`` requests to another router which
will process in isolation with its own routing stack.
will process in isolation with its own routing context.

.. code:: vala
Expand Down Expand Up @@ -301,25 +293,6 @@ Filters
// res is transparently gzipped
})
Stacked states
~~~~~~~~~~~~~~

Additionally, states can be passed to the next handler in the queue by pushing
them in a stack.

.. code:: vala
app.get ("", (req, res, next, stack) => {
message ("pre");
stack.push_tail (new Object ()); // propagate the state
next (req, res);
});
app.get ("", (req, res, next, stack) => {
// perform an operation with the provided state
var obj = stack.pop_tail ();
});
Sequence
--------

Expand Down Expand Up @@ -386,8 +359,8 @@ responses designed for non-human client.
});
});
app.status (Status.NOT_ACCEPTABLE, (req, res, next, stack) => {
res.body.write_all ("<p>%s</p>".printf (stack.pop_tail ().get_string ()).data, null);
app.status (Status.NOT_ACCEPTABLE, (req, res, next, context) => {
res.body.write_all ("<p>%s</p>".printf (context["message"].get_string ()).data, null);
});
Middleware
Expand Down Expand Up @@ -482,8 +455,8 @@ attached to a :doc:`route`, the processing will happen in a ``NextCallback``.

.. code:: vala
app.get ("home", (req, res, next, stack) => {
app.get ("home", (req, res, next, context) => {
compress (req, res, (req, res) => {
res.body.write_all ("Hello world!".data, null);
}, stack);
}, new Context.with_parent (context));
});
50 changes: 25 additions & 25 deletions examples/app/app.vala
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ app.get ("cookies", (req, res) => {
});

app.scope ("cookie", (inner) => {
inner.get ("<name>", (req, res) => {
inner.get ("<name>", (req, res, next, context) => {
foreach (var cookie in VSGI.Cookies.from_request (req))
if (cookie.name == req.params["name"])
if (cookie.name == context["name"].get_string ())
res.body.write_all ("%s\n".printf (cookie.value).data, null);
});

inner.post ("<name>", (req, res) => {
var cookie = new Soup.Cookie (req.params["name"], req.flatten_utf8 (), "0.0.0.0", "/", 60);
inner.post ("<name>", (req, res, next, context) => {
var cookie = new Soup.Cookie (context["name"].get_string (), req.flatten_utf8 (), "0.0.0.0", "/", 60);
res.headers.append ("Set-Cookie", cookie.to_set_cookie_header ());
});
});
Expand Down Expand Up @@ -140,13 +140,13 @@ app.method (VSGI.Request.GET, "custom-method", (req, res) => {
res.body.write_all (req.method.data, null);
});

app.get ("hello/<id>", (req, res) => {
res.body.write_all ("hello %s!".printf (req.params["id"]).data, null);
app.get ("hello/<id>", (req, res, next, context) => {
res.body.write_all ("hello %s!".printf (context["id"].get_string ()).data, null);
});

app.get ("users/<int:id>/<action>", (req, res) => {
var id = req.params["id"];
var test = req.params["action"];
app.get ("users/<int:id>/<action>", (req, res, next, context) => {
var id = context["id"].get_string ();
var test = context["action"].get_string ();
var writer = new DataOutputStream (res.body);

writer.put_string (@"id\t=> $id\n");
Expand All @@ -155,8 +155,8 @@ app.get ("users/<int:id>/<action>", (req, res) => {

app.types["permutations"] = /abc|acb|bac|bca|cab|cba/;

app.get ("custom-route-type/<permutations:p>", (req, res) => {
res.body.write_all (req.params["p"].data, null);
app.get ("custom-route-type/<permutations:p>", (req, res, next, context) => {
res.body.write_all (context["p"].get_string ().data, null);
});

app.regex (VSGI.Request.GET, /custom-regular-expression/, (req, res) => {
Expand Down Expand Up @@ -193,8 +193,8 @@ app.get ("not-found", (req, res) => {

var api = new Router ();

api.get ("repository/<name>", (req, res) => {
var name = req.params["name"];
api.get ("repository/<name>", (req, res, next, context) => {
var name = context["name"].get_string ();
res.body.write_all (name.data, null);
});

Expand All @@ -209,17 +209,17 @@ app.get ("next", (req, res) => {
res.body.write_all ("Matched by the next route in the queue.".data, null);
});

app.get ("state", (req, res, next, stack) => {
stack.push_tail ("I have been passed!");
app.get ("state", (req, res, next, context) => {
context["state"] = "I have been passed!";
next (req, res);
});

app.get ("state", (req, res, next, stack) => {
res.body.write_all (stack.pop_tail ().get_string ().data, null);
app.get ("state", (req, res, next, context) => {
res.body.write_all (context["state"].get_string ().data, null);
});

// Ctpl template rendering
app.get ("ctpl/<foo>/<bar>", (req, res) => {
app.get ("ctpl/<foo>/<bar>", (req, res, next, context) => {
var tpl = new View.from_string ("""
<p>hello {foo}</p>
<p>hello {bar}</p>
Expand All @@ -230,8 +230,8 @@ app.get ("ctpl/<foo>/<bar>", (req, res) => {
</ul>
""");

tpl.push_string ("foo", req.params["foo"]);
tpl.push_string ("bar", req.params["bar"]);
tpl.push_string ("foo", context["foo"].get_string ());
tpl.push_string ("bar", context["bar"].get_string ());
tpl.push_strings ("strings", {"a", "b", "c"});
tpl.push_int ("int", 1);

Expand All @@ -240,9 +240,9 @@ app.get ("ctpl/<foo>/<bar>", (req, res) => {
});

// serve static resource using a path route parameter
app.get ("static/<path:resource>.<any:type>", (req, res) => {
var resource = req.params["resource"];
var type = req.params["type"];
app.get ("static/<path:resource>.<any:type>", (req, res, next, context) => {
var resource = context["resource"].get_string ();
var type = context["type"].get_string ();
var path = "/static/%s.%s".printf (resource, type);
bool uncertain;

Expand Down Expand Up @@ -279,9 +279,9 @@ app.get ("server-sent-events", stream_events ((req, send) => {
});
}));

app.status (Soup.Status.NOT_FOUND, (req, res, next, stack) => {
app.status (Soup.Status.NOT_FOUND, (req, res, next, context) => {
var template = new View.from_stream (resources_open_stream ("/templates/404.html", ResourceLookupFlags.NONE));
template.environment.push_string ("message", stack.pop_tail ().get_string ());
template.environment.push_string ("message", context["message"].get_string ());
res.status = Soup.Status.NOT_FOUND;
HashTable<string, string> @params;
res.headers.get_content_type (out @params);
Expand Down
4 changes: 2 additions & 2 deletions examples/fastcgi/app.vala
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public static int main (string[] args) {
res.body.write_all ("Hello world!".data, null);
});

app.get ("random/<int:size>", (req, res) => {
var size = int.parse (req.params["size"]);
app.get ("random/<int:size>", (req, res, next, context) => {
var size = int.parse (context["size"].get_string ());
var writer = new DataOutputStream (res.body);

for (; size > 0; size--) {
Expand Down
12 changes: 6 additions & 6 deletions examples/memcached/app.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ using VSGI.Soup;
var app = new Router ();
var memcached = new Memcached.Context.from_configuration ("--SERVER=localhost".data);

app.get ("<key>", (req, res, next, stack) => {
var key = stack.pop_tail ().get_string ();
app.get ("<key>", (req, res, next, context) => {
var key = context["key"].get_string ();

uint32 flags;
Memcached.ReturnCode error;
Expand All @@ -39,8 +39,8 @@ app.get ("<key>", (req, res, next, stack) => {
}
});

app.put ("<key>", (req, res, next, stack) => {
var key = stack.pop_tail ().get_string ();
app.put ("<key>", (req, res, next, context) => {
var key = context["key"].get_string ();

var buffer = new MemoryOutputStream (null, realloc, free);

Expand All @@ -59,8 +59,8 @@ app.put ("<key>", (req, res, next, stack) => {
}
});

app.delete ("<key>", (req, res, next, stack) => {
var key = stack.pop_tail ().get_string ();
app.delete ("<key>", (req, res, next, context) => {
var key = context["key"].get_string ();

var error = memcached.delete (key.data, 0);

Expand Down
2 changes: 1 addition & 1 deletion examples/scgi/app.vala
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ app.get ("async", (req, res) => {
res.body.write_all_async.begin ("Hello world!".data, Priority.DEFAULT, null);
});

new Server ("org.valum.example.SCGI", app.handle).run ();
new Server ("org.valum.example.SCGI", app.handle).run ({"app", "--port", "3003"});
83 changes: 83 additions & 0 deletions src/valum-context.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* This file is part of Valum.
*
* Valum 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 3 of the License, or (at your option) any
* later version.
*
* Valum 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 Valum. If not, see <http://www.gnu.org/licenses/>.
*/

using GLib;

/**
* Routing context that stores various states for middleware interaction.
*
* @since 0.3
*/
public class Valum.Context : Object {

/**
* Internal mapping of states.
*/
private HashTable<string, Value?> states = new HashTable<string, Value?> (str_hash, str_equal);

/**
* Parent's context from which missing keys are resolved.
*
* @since 0.3
*/
public Context? parent { construct; get; default = null; }

/**
* Create a new root context.
*
* @since 0.3
*/
public Context () {

}

/**
* Create a new child context.
*
* @since 0.3
*/
public Context.with_parent (Context parent) {
Object (parent: parent);
}

/**
* Obtain a key from this context or its parent if it's not found.
*
* @since 0.3
*/
public new Value? @get (string key) {
return states[key] ?? (parent == null ? null : parent[key]);
}

/**
* Set a key in this context.
*
* @since 0.3
*/
public new void @set (string key, Value? @value) {
states[key] = @value;
}

/**
* Lookup if this context or its parent has a key.
*
* @since 0.3
*/
public bool contains (string key) {
return states.contains (key) || (parent != null && parent.contains (key));
}
}
Loading

0 comments on commit 9b244a3

Please sign in to comment.