haskell-servant / servant-snap

Snap port of servant-server (WIP)
Other
28 stars 18 forks source link

415 Unsupported Media Type #27

Closed vitalibarozzi closed 4 years ago

vitalibarozzi commented 5 years ago

Given:

`type Api = ReqBody '[JSON] Int :> Get '[JSON] Int

api = Proxy :: Proxy Api

main = quickHttpServe (serveSnap api return)`

with:

curl -i -X GET -H "Content-Type: application/json" -d '666' http://localhost:8000/

i get back a "415 Unsupported Media Type", witch is weird to me. But if i try:

curl -i -X GET -H "Content-Type: application/json;foo=bar" -d '666' http://localhost:8000/

or even:

curl -i -X GET -H "Content-Type: application/json;=" -d '666' http://localhost:8000/

ir returns the expected 666. I don't understand why. Any clues?

sigrlami commented 5 years ago

@vitalibarozzi your handler expects json payload and you send plain value 666 try valid json {..}. Most probably servant fallback to plain text on wrong content type or conversion failure.

You can use Capture and handle incoming Int as REST-like path parameter

vitalibarozzi commented 5 years ago

I used a number here to make it shorter, but it has the same behaviour with a proper json object.

E.g.: `{-# LANGUAGE TypeOperators #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DeriveGeneric #-}

module Main where

import Data.Proxy (Proxy(..)) import Data.Text (Text, append) import Servant import Servant.Server (serveSnap) import Snap.Http.Server (quickHttpServe) import Data.Aeson import GHC.Generics

data MyData = MyData { user :: String , pass :: String } deriving stock Generic deriving anyclass (FromJSON, ToJSON)

type Api = ReqBody '[JSON] MyData :> Post '[JSON] MyData

api = Proxy :: Proxy Api

main = quickHttpServe $ serveSnap api return`

and with curl:

curl -i -X POST -H "Content-Type: application/json" -d '{"user":"bar","pass":"foo"}' http://localhost:8000

ir returns 415

but with:

curl -i -X POST -H "Content-Type: application/json;=" -d '{"user":"bar","pass":"foooo"}' http://localhost:8000

it works as expected.

I'm going to avoid using the body for now and comeback to this eventually. Bu what puzzles me is that it works if i add anything with = or even several =. I've come to this result by trying to add charset=utf-8 to the end of the content-type, and shrink from there to the = sign only.

aveltras commented 4 years ago

I can confirm the problem. Having "application/json; charset=utf-8" in a Content-Type header works but having "application/json" doesn't and results in a 415 error code when sending a json payload.

santiweight commented 4 years ago

Hey there. I ran into this bug recently and it was quite surprising - this seems like quite a core bug. I can take a look at trying to fix if you point in the right direction.

As an aside, should I be using servant-snap? I saw some mention of attempting to have servant-snap be the intended usage of servant?

imalsogreg commented 4 years ago

@santiweight Thank you for bumping this issue - it's definitely surprising! I see that 4 years ago I updated the test suite to set charset=utf-8 for every Content-Type header in the test suite, and I didn't document why.

I'll first try to reproduce the issue locally, then I'll try to reproduce it for POST endpoints (a GET should not have a request body, so I wonder if servant's aberrant support of GET/ReqBody is involved).

santiweight commented 4 years ago

Thank you for the quick help :) This is a great fix!

imalsogreg commented 4 years ago

I think this will be fixed by https://github.com/haskell-servant/servant-snap/pull/31

Looking at the code before this change, I really don't see how it ever could have worked with Content-Type: application/json. Changing the test-suites to accommodate the unintended requirement on charset=utf-8 masked this problem for years. It's one of those cases where you look at your old habits and can only shake your head. Hopefully I've learned something for next time :)