-
-
Notifications
You must be signed in to change notification settings - Fork 412
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
Cannot consume body of Request
twice: not enough input
#1120
Comments
Consuming body twice is a bad idea. Every option I can think of are huge hacks
|
Indeed, we used to memoize the request body, but that causes all sorts of problems (you always retain it, you can't really do proper streaming request bodies). I second @phadej's suggestion. Perhaps a middleware could help? |
I understand that streaming is a useful feature. But a lot of services sign whole body of the request. This means that it's not possible to implement @alpmestan By mentioning middleware you mean this type of |
Yes. I'm not quite sure what would happen if you do something with the request body through the middleware and then try to use it in your servant app, like you normally would. But perhaps it's worth giving it a shot? |
I have been thinking about this a bit because lately I have been dealing with many different auth schemas. For instance, with Oauth1, the protocol requires (paraphrasing) taking the request, sorting the url-form-encoded parameters, assembling it into a string, adding the secret token, percent-encoding the string, and then HMAC-SHA1 signing the whole thing. In other words, in order to implement or verify Oauth1, one needs access to the entire request. The Oauth1 spec is pretty old, so doesn't mention JSON, but there are various API servers that offer a take on oauth1 that works along the lines of 1) taking the request body as a string, 2) appending a secret token to it (or prefixing it), and 3) signing the whole thing using SHA256 or something else. Similar to Oauth1, for a server that would like to first validate a request this variation requires consuming the request body entirely, and for a client, it would require adding a signature after the request has been otherwise assembled. Lastly, Oauth2 doesn't require signing requests in this way, because it uses the request-grant flow, where clients request access from servers for protected resources and they receive temporary tokens to access those resources. Specific to Haskell, for servers implementing either Oauth1 or the variation I mentioned, it seems like it should be possible to define a wai The documentation for wai, says that to retrieve the Anyway, it seems like it should work as wai Sidenote: for Servant clients, I don't really know how to sign requests before they're issued? |
I'd recommend using a
Client part can be done similarly, in reverse. An alternative is to write a variant of |
While working on @phadej @alpmestan I've tried to use debugMiddleware :: Middleware
debugMiddleware app req handle = do
body <- getWaiRequestBody req
BS8.putStrLn body
let newReq = req { requestBody = pure body }
app newReq handle
main :: IO ()
main = run 8080 $ debugMiddleware app42 |
You mean you want your debugMiddleware :: MVar -> Middleware
debugMiddleware = ...
main :: IO ()
main = newMVar foo >>= \mvar -> run 8080 $ debugMiddleware mvar app42 ? (sorry if I misunderstood, I skimmed through quickly) |
@alpmestan It's more about this commend by @phadej
|
Ah, not sure what @phadej meant here, I'll let him comment :-) |
I have a tangentially related question: I've been looking for a way to really make sure the request body size is not too large, not trusting the
|
Hi I'm dealing with similar things to @chshersh and I'm just wondering if I understand this correctly. Is the reason the requestBody is always empty in my logging Middleware because servant has already consumed it? is there any way I could log the requestBody inside the Middleware? |
Sorry for jumping in, but could this basically be related to how It didn't seem a very efficient strategy at that time. And it still doesn't. |
Well, I don't think there is a more efficient strategy: if you want your web server to handle an input stream twice sequentially, there isn't any way around keeping it for the second consumer. So yes, it's less efficient, but I would say it's about as efficient as the non-streaming approach, where the full request body is stored in memory before passed to application code and/or middleware. It may still be perfectly sufficient. |
I'm the epitome of a "no idea what I'm doing" Haskell author, but I ran into this recently (in writing Servant logic to handle Stripe webhook signatures) and came up with a working solution that implements the suggestions about In case it isn't obvious: this probably isn't a good idea to use everywhere. It makes assumptions that all clients reading the body are going to behave. Also, I sort of guessed at using a import qualified Data.Sequence as S
import Data.Sequence (Seq((:<|), Empty), (|>))
freezeReqBody :: Middleware
freezeReqBody app req handle' = do
body' <- newMVar =<< extractBody req
let req' = req { requestBody = requestBody' body' }
app req' handle'
where
requestBody' mvar = modifyMVar mvar \chunks -> do
case chunks of
(h :<| rest) -> return (rest |> h, h)
Empty -> return (Empty, mempty)
extractBody :: MonadIO m => Request -> m (Seq ByteString)
extractBody req = do
chunk <- S.singleton <$> liftIO (getRequestBodyChunk req)
(flip . iterateUntilM) (null . r) chunk \chunks' -> do
chunk' <- liftIO $ getRequestBodyChunk req
return $ chunks' |> chunk'
where r seq' = case S.viewr seq' of
S.EmptyR -> mempty
(_ S.:> e) -> e Again, strongly suggest that anyone who uses this only apply the middleware if it meets a predicate. From my own code: ifRequest fromStripe freezeReqBody $ ...
where fromStripe (lookup "Stripe-Signature" . requestHeaders -> Just _) = True
fromStripe _ = False I haven't shored up the code to be more cautious about reading in a limited number of bytes, which would probably be a good idea in production, otherwise this code will DoS itself with a fat body. This middleware is more of a convenience if you need to look at the body a few times and don't have control over whoever is reading the body and thus can't "reload" it when body reads occur. |
I'm running into this scenario as well. My use case is verifying Slack event requests. I understand that reading the whole body and retaining it has performance and DoS implications, but there really isn't any other way to integrate against an API like this. I would also be happy if someone could point me to a well-maintained middleware that does this sort of HMAC checking for me. |
@chshersh I stumbled into this while trying to do pretty much the same thing that you are (an HMAC signature of the payload) and I think I figured out why (a) your application hangs and (b) why you need an The application does not actually hang, but it gets stuck in reading and re-reading the You can observe the behavior by using this Because you must ensure that A solution to the problem might be to implement a 'chunked' reading of the body, while using the The alternative is what I suggested previously, which is reading everything into memory, setup an |
This fixes the following issue with `wai`: haskell-servant/servant#1120 (comment) Basically, if you consume the request body once (in the middleware) then it's no longer available for the actual handler. The solution is to "push back" the request body onto the `Request`, which is extremely hacky but works.
This fixes the following issue with `wai`: haskell-servant/servant#1120 (comment) Basically, if you consume the request body once (in the middleware) then it's no longer available for the actual handler. The solution is to "push back" the request body onto the `Request`, which is extremely hacky but works.
I'm trying to implement custom authorization schemes. Looks like
servant-auth
doesn't support this feature at the moment (though, this probably will be implemented during GSoC 2019):So I'm using
Servant.API.Experimental.Auth
. The problem with this approach is that it's not possible to consume body ofRequest
twice (for calculating its hash-sum or just printing for debugging purposes) since it's anIO
action which allows to consume the body only once. Consider the following minimal example:If I run this server and try to query it like this:
I see
However, if I comment the following line:
Everything works without problems.
I wonder, whether it's possible to implement some workaround to make body of the request consumable more than once?
The text was updated successfully, but these errors were encountered: