Skip to content

Parsing request body mulitple times behaves oddly. #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mf59816 opened this issue Jan 2, 2015 · 3 comments
Closed

Parsing request body mulitple times behaves oddly. #3

mf59816 opened this issue Jan 2, 2015 · 3 comments

Comments

@mf59816
Copy link

mf59816 commented Jan 2, 2015

Consider the routing table:

type Routes =
  ...
  :<|> ReqBody User :> Post UserID
  :<|> Capture "userid" UserID :> ReqBody User :> Put ()
  ...

The first route fails for Put requests, but the second will find that
the request body has already been parsed, and will complain about ""
not being a parseable json representation of User.

The work-around is simple:

type Routes =
  ...
  :<|> Capture "userid" UserID :> ReqBody User :> Put ()
  :<|> ReqBody User :> Post UserID
  ...

But I'm not sure it is that simple in all cases, or that obvious to
understand the situation once you've stumbled into this trap.

We have some (limited) resources to work on a solution, but we would
like to hear what you think first.

Servant is AWwesome, btw. Thanks for writing it! (:

@alpmestan
Copy link
Contributor

Hey,

First and foremost, thanks for reporting this issue, nice catch!

Looking at the code in servant-server:

instance (FromJSON a, HasServer sublayout)
      => HasServer (ReqBody a :> sublayout) where

  type Server (ReqBody a :> sublayout) =
    a -> Server sublayout

  route Proxy subserver request respond = do
    mrqbody <- decode' <$> lazyRequestBody request
    case mrqbody of
      Nothing -> respond $ failWith InvalidBody
      Just v  -> route (Proxy :: Proxy sublayout) (subserver v) request respond

We're not trying to be too smart here, so it seems that running lazyRequestBody request (it's an IO action) once empties it. That's the only explanation I can see, and I certainly wasn't expecting this.

lazyRequestBody basically just runs an IO action that gets the next chunk (strict bytestring) repeatedly until it has all been consumed. It uses requestBody whose docs say:

Get the next chunk of the body. Returns empty when the body is fully consumed.

@jkarni @soenkehahn @mf59816 One solution would be to rely on something Julian is working on: canonicalize the API type to 1/ make the routing O(log n) 2/ put the request bodies at the very end of the endpoints they appear in, so that the request body is the last thing we (try to) extract. Any other ideas?

@jkarni
Copy link
Member

jkarni commented Jan 2, 2015

Pretty serious issue - thanks @mf59816 for reporting! https://github.com/haskell-servant/servant-server/pull/3 has what I think is a fix. I haven't tested it yet though - mf, if you want to add the example you gave here as a test case in the servant-server that'd be very welcome. If not, I'll do it myself soon.

@jkarni
Copy link
Member

jkarni commented Jan 4, 2015

@jkarni jkarni closed this as completed Jan 4, 2015
alpmestan added a commit that referenced this issue Apr 20, 2015
alpmestan added a commit that referenced this issue Apr 20, 2015
Add .gitignore file which ignore sandbox files
jkarni added a commit that referenced this issue Apr 20, 2015
This test case reproduces servant issue #3.
jkarni added a commit that referenced this issue Apr 20, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants