Open alpmestan opened 9 years ago
I like this a lot. Here are some thoughts:
Redirect303
or SeeOther
, or something along those lines? We can add other redirects later.lnk
is a GET endpoint (cf. RFC 7231)Redirect
combinator that would take a status code parameter, and have some KnownStatusCode
class do the discrimination on the status code to allow the HasServer
instance to be picked.IsGetLink :: * -> Bool
and have a IsGetLink lnk ~ 'True
constraint on the instance?Had a little idea about this. If we don't want to be too opinionated about redirects, we can do:
data Redirect (status :: Natural) (c :: [*] -> * -> *) lnk api
deriving (Typeable)
class KnownMethod (c :: [*] -> * -> *) where
methodOf :: Proxy c -> Method
instance KnownMethod Delete where
methodOf _ = methodDelete
instance KnownMethod Get where
methodOf _ = methodGet
instance KnownMethod Patch where
methodOf _ = methodPatch
instance KnownMethod Post where
methodOf _ = methodPost
instance KnownMethod Put where
methodOf _ = methodPut
instance (KnownMethod c, 300 <= st, st <= 307, IsElem lnk api, HasLink lnk)
=> HasServer (Redirect st c lnk api) where
Nice. For type-safe conformance to the relevant RFCs, I think we'd really want something like
(..., (st == 303 `And` IsGet lnk) `Or` (st == 307 `And` c ~ MethodOf lnk), ...)
&c. That way, we have a single instance, but all the nuances of the differences between code checked and built-in.
Of course, we would probably factor it out as a IsGoodRedirect
type family so it doesn't look as ugly. @alpmestan what do you think?
Do you really want to enforce that the method of the endpoint we redirect from should be the same as the link's? That defeats the typical POST new entity -> redirect to its uri. But I'm totally onboard with your suggestion of having types enforce the RFC.
307 does not allowing changing of method. 303 requires changing to GET. See https://tools.ietf.org/html/rfc7538
Any updates on this? I have a little time and could do it.
Oh, if you want to, please be my guest! I'd definitely be glad to review and then actually use the final Redirect
combinator as described in the last few comments here :)
Ok, @alpmestan and I discussed this a little more. The current proposal doesn't tie the link you actually use with the link in the type at all, which is bad. One option is to get the type for safeLink
from the combinator, which would stay much as it is. The consequences are that
The alternative is to change the Link
type so that we can tag it with the API type it was constructed for, and then just require one type param for the Redirect
combinator (the API type), and check that that and the Link
type match. The second involves more changes, but seems more general. Any thoughts?
Actually, we should be tagging links anyhow.
I'd be keen to see a PR with the first approach first, and then see if we really need the tagging. I'm not sure of what would be tagged anyway since links just end up being URI
s. However, if you really want to go with the alternative approach, I won't argue against it.
How do I redirect to a static file? I'm trying to redirect to a HTML file after successfully doing an insert (CRUD). Tried responseFile but doesn't typecheck because it returns Response. Does Servant got something like Scotty's redirect?
You can do it "the good old way" I guess:
left $ errXXX { errHeaders = [("Location", "/foo.html")] }
where XXX
is one of the redirect status codes (301, ...). This should work in any handler (and is a bit of a hack/escape hatch, yes).
@alpmestan thanks for the pointer. CMIIW, servant's using Except instead of Either, right?
Yeah, in the master
branch of this repo (which is the upcoming 0.5 release's branch, basically). But it's not on hackage yet.
Has there been any more movement on this? I'm surprised that it seems to have died out seeing as (a) redirects are pretty common in http programming and (b) it looks like code for this (or at least a prototype) was already done when this post was made.
I don't understand the internals of Servant well enough to implement it myself, but if there's something usable out there (even if not bullet-proof) then I'd be interested in trying it out. Otherwise I guess using ServantErr is the only way to do it?
ServantErr is the "escape hatch" way to do redirects. The goal of this issue was to discuss some smarter redirection mechanism, one which would be able to check for example that the uri it redirects to belongs to some API type. I wrote the code above for a consulting gig back then and it was working fine. It is however out of data.
I agree however that this would be a very useful addition. It would even be about time that we have a proper solution for redirects. @dmjio very recently expressed interest in that too. If we get people to agree on what this redirect combinator should support, what it should look like, I can definitely provide some guidance for anyone interested in implementing it.
Any news on the progress of this?
I just ran into a situation with a small elm app I built where I want to use a redirect to keep my root route /
instead of index.html
,
which yes I could do in any number of ways but I landed here and this struck me as interesting.
Nothing in particular, although there's been a bit more discussion in https://github.com/haskell-servant/servant/issues/135. Relatedly, @phadej and I have been discussing an overhaul of some existing combinators that would among many things give a principled story about redirects, you can read more about this at https://github.com/haskell-servant/servant/issues/841
Maybe we could define Get302/Post302
Verb that would require Headers '[Header "Location" String]
in response type?
We definitely can define a "shortcut": type Get302 (cts :: [*]) a = Verb 'GET 302 cts (Headers '[Header "Location" String] a)
. And maybe we could define an alias for 'addHeader' specialized to producing a suitable Headers ... a
value. Not sure what people would like?
@alpmestan I can't figure out how would nesting Headers
work then:
Get302 '[PlainText] (Headers '[ Header "Set-Cookie" SetCookie
, Header "Set-Cookie" SetCookie
] NoContent)
Oh, if you have other headers you'd probably instead do:
type Get302 (cts :: [*]) (hs :: [*]) a = Verb 'GET 302 cts (Headers (Header "Location" String ': hs) a)
where hs
would be the list of extra headers, in addition to "Location"
. Of course, hs
would be '[]
if no other response header needs to be set.
That works, thanks!
EDIT: server part doesn't compile, let me check.
There's nothing magic going on above. You just set in stone the things you want (HTTP method, status code, "Location"
header) and leave everything else as parameters to your custom Get
. I'm suspecting the type errors you'll see have to do with using addHeader
enough times, and in the right order maybe?
Yes - I didn't know GHC doesn't fully resolve type synonyms in error messages, so I was misguided.
I'd say having Get/Post 302/301 would be a good step forward towards having redirection and more type safety can be added later on. Would you accept such PR if I give it a try?
Absolutely (speaking in my name only of course) ! It would be perfect if you could even perhaps mention them in the server part of the tutorial or a small dedicated cookbook recipe? So that people can more easily discover them and how they should be used.
I'd love to be able to do the following in a type-safe manner using Servant:
Consider a simplified "blog" API with endpoints:
/post/:post_id/comments/newest
/post/:post_id/comments/:comment_id
The first endpoint asks the API server "what is the comment_id
of the newest comment for the post with the given post_id
?". The server responds with a HTTP 302 response indicating the URL of this comment in the Location
header -- essentially "filling out" the :comment_id
parameter in the second endpoint URL.
This is useful in scenarios where the following conditions apply:
/post/:id/comments/newest
is relatively cheap/post/:post_id/comments/:comment_id
is relatively expensiveIf these requirements are fulfilled, using this redirect schema means a caching middleware can cache responses to /post/:post_id/comments/:comment_id
with an infinite "expiration", and cheaply respond to /post/:id/comments/newest
with an URL of a (possibly already-cached) comment
Is that what this proposal is about? Specifically, the above requires a combinator which inherits the return type (e.g. Comment
) of another API endpoint, so that the API endpoint which redirects uses the return type of the endpoint to which it redirects.
Hi everyone,
This has been discussed a bit offline but I thought I should open an issue and throw some code in here for discussion and potential inclusion in servant. The deal here is to offer a proper generic combinator for redirection.
One thing however is that we have some decent machinery for generating type safe links, which of course looks like a good basis to build the redirect combinator on.
For a servant-0.4 compatible solution, one could consider such an implementation:
(complete usable code here)
The only thing I'm not happy about is that I hardcode the
303
status code. We could probably take an additional parameter of kindNatural
(type-level natural number) and only have theHasServer
instance when that number is a valid status code used for redirects.Thoughts?
PS: I use the above combinator in a work project already and hence would be keen to eventually include such a combinator in servant itself since I would otherwise have to maintain that combinator independently and adapt it for any change to servant/servant-server.