Repository navigation
Request body not available when using Server #340
Description
Note: I'm completely new to React and async programming. I'm experimenting with both reactphp and amphp to get the gist of what this is all about. So this is my feedback as a complete noob about one of the total WTFs I had with React.
I was trying to setup a very basic HTTP server using an event loop and then integrate https://github.com/webonyx/graphql-php to make a GraphQL server example.
GraphQL takes the query as a JSON in the request body. So I was trying this:
$server = new Server(function (ServerRequestInterface $request) {
$rawInput = $request->getBody()->getContents();
$input = json_decode($rawInput, true);
$query = $input['query'];
$promise = GraphQL::promiseToExecute(
new ReactPromiseAdapter(),
$this->createSchema(),
$query
);
// ...
});The problem I ran into was that $request->getBody()->getContents() returned an empty string instead of the body.
After some debugging I found the reason:
- Server uses
RequestBodyBufferMiddlewareto buffer the request body so it should be available. - However Server also uses
RequestBodyParserMiddlewarewhich parses the body (not needed in my case, I need the whole body). - Unfortunately parsing the body has the side-effect of removing it from the Request object (see https://github.com/ringcentral/psr7/blob/360faaec4b563958b673fb52bbe94e37f14bc686/src/BufferStream.php#L39) which is why
getContents()returned an empty string in my handler. - To make things even worse this does not happen always because the
RequestBodyParserMiddlewareis only used under certain conditions. This makes the Server quite unreliable.
Once I understood the issue it was of course easy to fix using StreamingServer without the RequestBodyParserMiddleware.
That's all. I'd not call this a bug but it certainly is a complete WTF for a React noob like me. It's up to you if you want to do something about it or not. Doing the same thing in Amphp was very easy in comparison because there was no WTF moment.
On a side note React is a complete nightmare to debug for me at the moment since it seems to hide errors by default. I'd like to ask someone more experienced about it but I'm unsure where to ask.
I'm also not quite satisfied with my final code, especially compared to the same thing written with Amphp which most likely means I missed something so I'd like to ask some react expert how to make it cleaner. Not posting it here because it's off-topic.
Where can I reach the React PHP community to ask such questions?
(I was unable to connect to the IRC channel linked on the website. Not to mention that IRC is not exacly a modern platform - it would be much better if there was a gitter, slack, discord or something like that.)
Activity
clue commentedon Apr 13, 2019
@enumag Thank you for filing this issue and the excellent questions!
That's interesting! I agree that this is a very common use case, so I've just filed #341 to add some documentation and an example for a JSON-based API. I can not reproduce the problem you're seeing in this ticket, so perhaps you can check it out and see if this works for you?
Thanks for diving into this and reporting, it sounds like this may in fact be a bug. I haven't tried to reproduce this yet, but perhaps anybody cares to create a PR with a test case for this and with your suggested changes applied?
Ouch, but I appreciate the feedback. I'm happy to hear you've got this sorted out besides these initial difficulties nonetheless. I understand that it takes some time to get used to ReactPHP and its execution model. We're very open for input and continuously aiming to improve on this front, so if you happen to find something that you think can be improved, please make sure to let us know! 👍
Filing this an issue is what we usually prefer 👍 See also https://reactphp.org/#support for ways to reach out to us or file a meta issue in https://github.com/reactphp/react if you feel there's a way we can do better.
enumag commentedon Apr 13, 2019
@clue Since you seem to be quite interested in my feedback I went back to find out more.
The problem was that I was testing the server using this shell command:
As you can see I did not specify the
Content-Typeheader. It seems that something in React addedContent-Type: application/x-www-form-urlencodedto my request as a default which is why theRequestBodyParserMiddlewaretried to parse it and removed the actual content fromBufferStreamas a result.I believe that if I added proper
Content-Type: application/jsonheader, it would have worked just fine. It just didn't occur to me to add it while I was just testing React.In case you want to try it out yourself I pushed my experiments here (checkout the
reactbranch). I reverted it to the errorneous state where the content get's cleared by that middlewere so$rawInputhere is an empty string.I'm not really "suggesting" anything at this point. While I do have 10 years of experience with PHP and am an expert in Symfony, Doctrine etc. I'm complete noob here with async programming and React PHP. I just wanted to let you know about the WTF moment I ran into, not really sure what is the proper way of fixing it - leave it as is can also be an option of course.
You can actually see my issue with debugging in my experiments repo. In the state the react branch is now (commit https://github.com/enumag/async-experiments/commit/fa99008442ee9c9ff4d93c22672398031a2af40d) the server simply responds with this:
As you can imagine this is not very helpful to find out the problem.
The running
server.phpscript prints a messagewhich is only visible because of this. At first I didn't even have that in so that "Error 500" was literally all I had about the error. No message, no stacktrace, no anything - this is what I mean by react "hiding errors by default". Of course you do have a code like this in the docs but let's be realistic - the readme is quite long and it's not like a noob will thoroughly read it before trying it out - I certainly did not. So if error reporting is not properly setup within the very first example code in the readme, with a note that it's important to have it there, then there is a good chance a noob will accidentally skip it like I did.
I didn't want to fill your issue tracker with questions, but if that's what you prefer, I'm completely fine with it.
Thank you very much for your help!
EDIT: On a side note, code based on react seems to be full of callbacks and
thenchains so I went to try to add Recoil on top of React right after fixing my React issue. Coroutines are much easier to read in my opinion. Sadly I could not get it to work. Would you have any ideas about that? Of course if you're unfamiliar with Recoil then ignore this.recoilphp/recoil#164
clue commentedon Apr 17, 2019
@enumag Thank you for reporting back, this is indeed important feedback for us! As a consequence, I've filed #341 to improve documentation for common JSON/XML requests and I hope the next person encountering this problem will be able to find this out right in the updated documentation 👍
It looks like
curlautomatically adds aContent-Type: application/x-www-form-urlencodedrequest header by default. You can simply pass the-vflag to see its outgoing request headers and incoming response headers.Again, thanks for this feedback! As a consequence, I've filed #342 to improve documentation for error reporting as a first step and I hope the next person encountering this will be able to find this out right in the updated documentation 👍 Once this is in, we will keep working on improving default error reporting.
This may or may not be optimal depending on your particular question, but it's something that has worked out quite well for us most of the time. Getting slightly off-topic here, but issues have far better discoverability than more chat-like interactions and as such this helps avoiding having to answer the same question multiple times. As a rule of thumb: If you have a question, somebody else might too.
I understand where you're coming from and agree that coroutines appear to solve some of the architectural problems in an async application. It's definitely an interesting concept to discuss, so make sure to also file a separate ticket in this repo if you feel like discussing this further 👍
I'd like to make this ticket actionable and would like to get the next release out. I believe this has been answered, so I'm closing this for now. Please come back with more details if this problem persists and we can reopen this 👍
clue commentedon Apr 17, 2019
I'm not sure I follow what you're suggesting here :) If you feel this is still an issue, please report a dedicated ticket and I'm happy to look into this. 👍
acasademont commentedon Sep 19, 2019
Hi @clue I just stumbled upon this!
We are in fact using the
RequestBodyParserMiddlewareand it works great, we're sending an intentional curl request for a form. The problem is that we would also like to keep the whole body but right now is being erased due to theBufferStreamimplementation. It should be quite easy to fix, you just need to reinject the body contents on the newly created request. I could work on a PR if needed.The
RequestBodyParserMiddlewareTestworks because it doesn't use aBufferStreambody but a normal string. If that test is changed to use theBufferStreamit will fail.bramus commentedon Mar 26, 2020
Running into the same issue here and was (still am, actually) really scratching my head.
If I summarise this discussion then problem is that the middleware mentioned can't properly handle scenarios where a
Content-Typeheader is missing.I've tried changing my implementation to using
React\Http\StreamingServeras suggested but to no avail — it even got worse then: the$requestnow reported the proper size (Woohoow! 🎉) but the body itself remained empty (Huh? 🤔). See examples below for details.Not too sure how I can solve this I must say …
To be complete, here are the versions I'm running:
Example using React\Http\Server
Code:
Request with header
Note: From its name I expected
getParsedBodyto have actually parsed the JSON (because of the header being present), yet now it returnsNULLRequest without header
Note: That output for
getParsedBodysure looks weird …Example using React\Http\StreamingServer
Code:
Request with header
Request without header
Suggestions
As a user, if I may suggest:
$request->getBody()should always return the “raw” body (now it appears to get eaten by the middleware even though its reported size is bigger than 0)If I understand things correctly I think suggestion 2 will be an automatic result of fixing 1.
bramus commentedon Mar 26, 2020
Damn I feel stupid right now. Was already digging into the
RequestBodyParserand indeed saw the conditionals there to only parse things in casex-www-form-urlencodedwas set, yet still couldn't pinpoint why things went wrong.Thanks for pointing out that curl auto-injected the header, @CharlotteDunois! All is clear now :)