3

I am trying to understand Middleware by writing a very simple session manager.

I need to add the SetCookie header in the response. I've looked at the wai-extra package, and found wai-session.

I'm using wai-3.0.2, which doesn't seem to give me direct access to the type constructors for Response, and all of the examples I've found pattern match on Response(..) to add headers.

Can you point me in the right direction?

ase
  • 13,231
  • 4
  • 34
  • 46
rob
  • 2,053
  • 15
  • 13
  • 1
    You could of course import `Network.Wai.Internal` to pattern match on `Response`'s constructors. – ibotty Nov 01 '14 at 21:06
  • @ibotty Okay, I feel silly. I'd tried that, but it failed. Turns out it failed because the name of one of the type constructors had changed and I didn't notice. – rob Nov 02 '14 at 01:29
  • next time try to explore it in ghci. use `:i` and you will get to know all the constructors and where they are defined. – ibotty Nov 03 '14 at 09:19
  • @ibotty: Thanks for the `:i` suggestion! I ended up learning a lot about Wai, so it wasn't a complete waste, but `:i` would have saved some time. Thanks! – rob Nov 04 '14 at 04:49

1 Answers1

6

Edit: Version 3.0.3.0 of Wai introduces a helper function mapResponseHeaders that is the same as mapHeader in the example below. This means the example no longer needs to pattern match on Response.

import Network.HTTP.Types (ResponseHeaders, Header)
import Network.Wai (Middleware, Response, mapResponseHeaders)

withHeader :: Header -> Middleware
withHeader h app req respond = app req $ respond . addHeader h

addHeader :: Header -> Response -> Response
addHeader h = mapResponseHeaders (\hs -> h:hs)

I have something working, and think I understand it, but would really like feedback and suggestions. I'm new to Haskell, and this is my first use of Wai. My biggest stumbling block was not realizing that the Application type changed in Wai 3.0.0 to a continuation passing style. (The documentation states this very clearly; I just missed it the first 15 times I read it.)

import Network.HTTP.Types (ResponseHeaders, Header)
import Network.Wai (Middleware)
import Network.Wai.Internal (Response(..))

withHeader :: Header -> Middleware
withHeader h app req respond = app req $ respond . addHeader h

mapHeader :: (ResponseHeaders -> ResponseHeaders) -> Response -> Response
mapHeader f (ResponseFile s h b1 b2) = ResponseFile s (f h) b1 b2
mapHeader f (ResponseBuilder s h b) = ResponseBuilder s (f h) b
mapHeader f (ResponseStream s h b) = ResponseStream s (f h) b
mapHeader _ r@(ResponseRaw _ _) = r

addHeader :: Header -> Response -> Response
addHeader h = mapHeader (\hs -> h:hs)

I made no attempt to modify headers for a ResponseRaw, because I couldn't figure out how.

I'm not sure it's clear enough that addHeader is partially applied and is the continuation function passed to the inner Application. This form might be clearer (or uglier):

withHeader h app req respond = app req $ \resp -> respond $ addHeader h resp

I copied mapHeader from wai-session, but added the case for ResponseRaw.

rob
  • 2,053
  • 15
  • 13
  • It's unfortunate to have to import the Response constructors from Network.Wai.Internal, but as far as I can see, it's the only way you can accomplish it. – hdgarrood Jul 03 '15 at 07:33
  • Also, `addHeader` being partially applied is clear to me; I'd rather that than the eta-expanded version. Finally in the last pattern of mapHeader I would prefer to use `_` to `f` since `f` is not being used. (I think this would generate a warning if you used -Wall?) – hdgarrood Jul 03 '15 at 07:35
  • @hdgarrood I've changed the unused `f` to `_`. That is better. As I get more accustomed to Haskell I also prefer the partially applied version. Thanks for the comments! – rob Jul 03 '15 at 19:46
  • I was looking for a way to *remove* a header (I'm using Spock and don't need sessions), and this answer was very helpful. I'm wondering if there is a way to avoid having to pattern match on the various types of Responses? – asjo May 13 '17 at 12:43
  • 1
    Hey @asjo. Yes, there is now. The function mapHeader is now available in Network.Wai as mapResponseHeaders. This frees you from having to pattern match the Response by moving that match into the library. I'll update the answer to reflect this easier option. – rob May 14 '17 at 16:55