Skip to content
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

Support parsing urlencoded request body (RequestBodyParserMiddleware) #220

Merged
merged 4 commits into from
Sep 25, 2017

Conversation

WyriHaximus
Copy link
Member

@WyriHaximus WyriHaximus commented Sep 12, 2017

Body parser for #215
To be used with #216

Todo:

  • Middleware
  • Basic Tests
  • Readme

/**
* @param bool $keepOriginalBody Keep the original body after parsing or not
*/
public function __construct($keepOriginalBody = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be true by default to replicate PHP's default behaviour (and that of most PSR-7 implementations)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree 👍 I'd also vote to completely remove this parameter for now as the motivation is a bit unclear, plus we can still add this in a follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

@jsor
Copy link
Member

jsor commented Sep 14, 2017

I'd prefer a dedicated UrlEncodedRequestBodyParserMiddleware without the addType() extension point. Smells like some kind of middleware-in-middleware system ;-) WDYT?

@christoph-kluge
Copy link
Contributor

christoph-kluge commented Sep 14, 2017

I'd prefer a dedicated UrlEncodedRequestBodyParserMiddleware

I'd also prefer a dedicated middleware per type but on the other
hand I also like $parser->addType('application/json', new JsonRequestBodyParser()).

}

try {
$value = $this->types[$type];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$value might be a bit unclear here. Maybe something more readable like $request = $parser($request) ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

/** @var ServerRequestInterface $request */
$request = $value($request);
} catch (\Exception $e) {
return $next($request);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log somehow the parsing failure?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could allow a PSR-3 logger as constructor argument and log errors to it, or emit errors as Server does

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be interesting what PHP's behaviour is in case of invalid request bodies. I think we should probably follow that behaviour.

@WyriHaximus
Copy link
Member Author

@jsor @christoph-kluge The thing is this set up allows us to add more types in the future, infect I already have a multipart PR ready for when this PR is merged:

final class RequestBodyParserMiddleware
{
    private $types = array();

    public function __construct()
    {
        $this->addType('application/x-www-form-urlencoded', function (ServerRequestInterface $request) {
            $ret = array();
            parse_str((string)$request->getBody(), $ret);

            return $request->withParsedBody($ret);
        });
        $this->addType('multipart/form-data', function (ServerRequestInterface $request) {
            return MultipartParser::parseRequest($request);
        });
        $this->addType('multipart/mixed', function (ServerRequestInterface $request) {
            return MultipartParser::parseRequest($request);
        });
    }
}

@jsor
Copy link
Member

jsor commented Sep 14, 2017

@WyriHaximus That would be the MultipartRequestBodyParserMiddleware. Your solution introduces yet another configuration layer.

@WyriHaximus
Copy link
Member Author

@jsor With solid defaults, which can be extended. But I get your point will file a PRs for both MultipartRequestBodyParserMiddleware and UrlEncodedRequestBodyParserMiddleware middleware so we can compare 👍

@WyriHaximus
Copy link
Member Author

@jsor Filed them as #222 and #223

@WyriHaximus
Copy link
Member Author

Also closed #200 and #202 since #222 and #223 are superseding those as middlewares (thanks for the nudge @andig).

@WyriHaximus
Copy link
Member Author

After discussing this PR with @clue and @jsor we decided to go with a slight alteration in this middleware. or view command line instructions: removing the ability to add additional parsers for custom types and only to include parsers for body types from the HTTP 1.0/1.1/2.0 specs.

I'll add documentation later today

@WyriHaximus
Copy link
Member Author

Documentation added, @jsor @clue could you review?

README.md Outdated

```php
$middlewares = new MiddlewareRunner([
new RequestBodyParserMiddleware(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably have added the RequestBodyBufferMiddleware before to document the requirement.

{
$type = $request->getHeaderLine('Content-Type');

if ($type === 'application/x-www-form-urlencoded') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to find out since ever if header field values are case-sensitive. I'm talking only about the values, as header names are clearly defined case-insensitive. I've only found some definitions for specific header values, eg. for Connect or Transfer Coding names.

Not sure, but it probably won't hurt to lowercase $type here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure either, but I've updated the middleware forcing it to lowercase 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reference: This change is correct 👍 Header field values are not case-insensitive per se, but only for certain header field names, including this one:

The type, subtype, and parameter name tokens are case-insensitive.

https://tools.ietf.org/html/rfc7231#section-3.1.1.1 and https://tools.ietf.org/html/rfc1866#section-8.2.1

README.md Outdated
@@ -725,6 +726,38 @@ $middlewares = new MiddlewareRunner([
]);
```

#### RequestBodyParserMiddleware

Another built-in middleware is `RequestBodyParserMiddleware` which takes a fully buffered request body
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should read just

The RequestBodyParserMiddleware takes a fully buffered request body...

Referring to previous paragraphs and repeating "built-in middleware..." might be become tedious once we add more and more middleware.

WyriHaximus added a commit to WyriHaximus/http that referenced this pull request Sep 20, 2017
@andig
Copy link
Contributor

andig commented Sep 20, 2017

@WyriHaximus as successor of #223 this PR is now missing the multipart parts? Will multipart be another middleware or integrated here?

@WyriHaximus
Copy link
Member Author

@andig multipart will be filed in a follow up PR once this one is in.

@clue
Copy link
Member

clue commented Sep 25, 2017

I like the direction this PR is heading and agree that it makes sense to keep "default body parsers" ("what PHP does") in a single middleware because it's unlikely a consumer would want to access individual parsers anyway. As described by @WyriHaximus, multipart handling will be added to the same code base in a separate PR.

I'm currently in the process of rebasing and squashing some of these changes and will update this PR with some added tests. Other than that, this LGTM :shipit:

{
$type = strtolower($request->getHeaderLine('Content-Type'));

if ($type === 'application/x-www-form-urlencoded') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to copy from here :) https://github.com/slimphp/Slim/blob/3.x/Slim/Http/Request.php#L204-L241

Great work btw :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #225 :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WyriHaximus
Copy link
Member Author

@clue awesome 🎉

Copy link
Member

@clue clue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 🎉 I've rebased your changes and squashed a number of related commits after adding some tests to ensure this has 100% coverage, now let's get this in! :shipit:


use Psr\Http\Message\ServerRequestInterface;

final class RequestBodyParserMiddleware

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to not see final in this case. There are literally a metric ton of different request body formats and this is rather limiting (from the perspective of a http framework). XML, Json, form-urlencoded are only the most popular however there are many custom encodings as well.
For framework usage it would be nice to be able extend.

Also it's possible to have a mixture of encoding types inside an application (I know it sounds weird, but it happens... a lot).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geggleto going to provide a third party middleware that allows this as I also have a use for it 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #225 :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geggleto We've decided to provide only some core middleware which are required to build a standard PHP/PSR-7 request. Everything else can be easily provided third-party middleware packages. See #221 for reference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WyriHaximus WyriHaximus merged commit cb4cf5e into reactphp:master Sep 25, 2017
@clue clue changed the title Middleware request body parser Support parsing request body (RequestBodyParserMiddleware) Sep 25, 2017
@geggleto geggleto mentioned this pull request Sep 25, 2017
@clue clue changed the title Support parsing request body (RequestBodyParserMiddleware) Support parsing urlencoded request body (RequestBodyParserMiddleware) Sep 25, 2017
WyriHaximus added a commit to WyriHaximus/reactphp-http-middleware-custom-request-body-parsers that referenced this pull request Sep 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants