Open chreke opened 9 years ago
There is nothing for that at this point but I suppose it could be added. Sounds interesting.
A situation where this would be useful is when you're creating a REST API with HATEOAS, eg resources contain links to themselves and related resources.
Thanks for the swift reply! :) On May 4, 2015 18:27, "Loïc Hoguin" notifications@github.com wrote:
There is nothing for that at this point but I suppose it could be added. Sounds interesting.
— Reply to this email directly or view it on GitHub https://github.com/ninenines/cowboy/issues/816#issuecomment-98772043.
This is interesting, I'll see if I can help.
How should it be done?
cowboy_router:construct_uri("/users/:id", #{id => 30})
is nice but routes have to be reparsed.cowboy_router:construct_uri(Req, user_route, #{id => 30})
might be faster, but some compiled routes passed to cowboy_router
must have id attached to them. The "routing table" must be attached to the Req as a meta. This one is also better since a resource can be identified in multiple URIs but there is only one "canonical". Moreover, by changing a route in the routing list, you will change the returned URIs too, so there's no need to duplicate the change e.g: change how to route request to user_handler
in cowboy's option and change the code that construct a route to user_handler
.Another problem is constraint. User-defined constraints can convert bindings from binary to some other type (even pids if you are doing websocket). It would be nice to let binding values be of the converted type too e.g: cowboy_router:construct_uri("/user/:id/:feed_ws", #{id => 30, feed_ws => Pid})
. This would mean user-defined constraints have to be a pair of functions, one for binary->type and another for type->binary. Currently, cowboy allows one to specify only one constraint. What should be done when user wants to construct a constraint? throw(badarg)
? throw(badconstraint)
?
Here are some suggestions:
url_for
(from the Rails world) or reverse
(in Django).cowboy_router:reverse(user_handler, #{id => 1234})
and not cowboy_router:reverse("/users/:id", #{id => 1234})
. This way, if we change the URL for user_handler
we don't need to update the calls to reverse
.reverse
function is passed a request, maybe we could return a fully qualified URL, e.g. http://www.myapi.com/users/1234
instead of just /users/1234
?reverse
function accept binaries, at least initially?Doing it is one thing, doing it efficiently is another.
Perhaps it would make sense to make the router compile to an ets table. But this means we wouldn't be able to update the dispatch dynamically in middlewares (so edge case I doubt anyone does it anyway).
With an ets table, we can use match or select functions to get only the routes that might fit, and avoid copying the whole dispatch onto all connection processes.
If we have this ets table then we can also use it when we want to do "reverse routing" without having to keep track of the dispatch in Req, saving memory again. The match function would be a little different but the idea is the same: we want all routes that give a handler and then we find the right one from there.
Nothing prevents us from making constraint functions accept different types. For example the constraint 'int' would say true for integers and for binaries containing integer values, and false otherwise. Though it's probably not that easy, especially since we want to use constraints in other places too like the new cowboy_req:match functions.
Food for thoughts.
I didn't know you can change route dynamically. I guess it's possible since it's just an environment. But IIRC the cowboy doc already said that one should use ranch:set_protocol_option/2
to update route (globally) anyway so I guess that should be the only way.
I still don't think having one function for both direction of conversion is a good idea. Say if I have a function that convert base64 to raw binary (twitter uuid for example), there wouldn't be an easy way to differentiate. Maybe we can use a pair of function like {Forward, Reverse}
while still accepting a single function but throws on reverse
. Built-in constraints like int
should of course just work for both directions. Once could tag the parameter on reversal like: {reverse, 3}
and have the function deals with it but that's effectively two functions and old constraints would mysteriously crash.
On fully qualified URL: I did have to return a host name different from the one in the request once. Nothing in HTTP or REST say you can't create a resource somewhere else anyway so I guess it should be optional.
ets sounds nice but I'm not sure how one would match a concrete piece of data against a list of patterns. Usually it's the other way around with ets, unless you are talking about just matching against the handler atom.
You can change the route but that's a hack. The proper way is as you say, so it should be fine.
Yes my idea is that we simply match against the handler atom and then have some logic to do the rest. What that logic is remains to be determined, of course.
As for constraints, you're right. Perhaps we need a function that receives 2 arguments instead of 1, and we can use the second argument to give context ie say that we are in 'normal' or 'reverse' mode or something. Not sure, this is a tough one.
A related thing of interest for HATEOAS is parsing URLs using the router rules without doing actual routing. Exporting cowboy_router:match/3
would do. Any plans for this?
What's the use case for that? What do you do with the info?
The user POSTs to http://myapi.example.org/blogposts/123/comments
content such as {"user": "http://myapi.example.org/users/42", "comment": "Bla bla bla."}
. I want to parse the "user" URL to get the ID number 42 to look it up in a database. The idea of HATEOAS is to URLs like this in the API instead of the internal IDs.
Yep, sounds good. Will be considered.
I think the functionality of reverse routing would be too limited, if implemented. The problem is that we can't go from "handler module + bindings" to getting the host (unless it's used exactly once), that wildcards would cause problems, that reusing handlers for more than one path would not be resolvable (for example if you have generic handlers and reuse them for many different types of resources). I do not think it wise to implement something that leaves so much uncertainty in its behavior, and only works well if routes are defined a certain way.
On the other hand exposing cowboy_router:match is still a consideration and will happen, perhaps not in 2.0 but soon after.
Ok, thanks for the update! Looking forward to 2.0 ! 🙂 On Sat, 10 Jun 2017 at 13:05, Loïc Hoguin notifications@github.com wrote:
I think the functionality of reverse routing would be too limited, if implemented. The problem is that we can't go from "handler module + bindings" to getting the host (unless it's used exactly once), that wildcards would cause problems, that reusing handlers for more than one path would not be resolvable (for example if you have generic handlers and reuse them for many different types of resources). I do not think it wise to implement something that leaves so much uncertainty in its behavior, and only works well if routes are defined a certain way.
On the other hand exposing cowboy_router:match is still a consideration and will happen, perhaps not in 2.0 but soon after.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/ninenines/cowboy/issues/816#issuecomment-307558443, or mute the thread https://github.com/notifications/unsubscribe-auth/ABMY9vdBny5D5XAIkFOKWGuSMvvMypv1ks5sCniEgaJpZM4EPcxH .
I've done both reverse constraints in c22173037134becbac882533f80339f4127a8ad2. Feedback welcome!
I've implemented URI templates in Cowlib https://github.com/ninenines/cowlib/blob/master/src/cow_uri_template.erl
It is one step in the reverse routing direction. I am considering moving the router to use the URI template syntax + constraints, either in 3.0 or optionally for now. Then the template can always be expanded, perhaps via a cowboy_req
function so that the host/scheme/.. can be added if it's not present in the template (similar to what cowboy_req:uri
is already doing).
Anyway it's still a long way off.
Is it possible to do "reverse routing" in Cowboy, i.e. to generate a URL given a handler + bindings?
For example, given a handler (e.g.
'user_handler'
) and a set of URL bindings (e.g.[{user_id, 1234}]
) is it possible to generate a URL that will resolve to the given handler + bindings? (e.g."/users/1234“
)