guzzle / guzzle

Guzzle, an extensible PHP HTTP client

Home Page:https://docs.guzzlephp.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Guzzle exception behavior does not comply to PSR-18 spec

futape opened this issue · comments

Guzzle version(s) affected: 7.5.0 (probably all)

Description

PSR-18 states that

A Client MUST NOT treat a well-formed HTTP request or HTTP response as an error condition. For example, response status codes
in the 400 and 500 range MUST NOT cause an exception and MUST be returned to the Calling Library as normal.

A Client MUST throw an instance of Psr\Http\Client\ClientExceptionInterface if and only if it is unable to send the HTTP request at all
or if the HTTP response could not be parsed into a PSR-7 response object.

However, Guzzle's GuzzleException interface, extended by all Guzzle exception classes, implements the PSR-18 ClientExceptionInterface.
That means, that it issues a ClientExceptionInterface on any error.
That is fine for a ConnectException, implemnting PSR-18 NetworkExceptionInterface which in turn extends ClientExceptionInterface, for example, but does not comply to the spec for most other exceptions.
For example Guzzle's ClientException, thrown on 4xx errors, and ServerException, thrown on 5xx errors, both implement ClientExceptionInterface by implementing GuzzleException somewhere in their stacks, which is clearly against the spec saying "For example, response status codes in the 400 and 500 range MUST NOT cause an exception and MUST be returned to the Calling Library as normal.", which sounds to as if the client may not only isn't allowed to throw a ClientExceptionInterface in that case, but is not allowed to throw an exception at all.

Also Guzzle's ClientException, ServerException and TooManyRedirectsException all extend RequestException, either directly or by extending BadResponseException, which in turn implements PSR-18 RequestExceptionInterface.
PSR-18 states that

If a request cannot be sent because the request message is not a well-formed HTTP request or is missing some critical piece of
information (such as a Host or Method), the Client MUST throw an instance of Psr\Http\Client\RequestExceptionInterface.

That is clearly not the case for ClientException and ServerException. I would also say, that TooManyRedirectsException is not considered a RequestExceptionInterface.


Note on redirects: PSR-18 intends that clients return the response as received. Features that change that behavior, like following redirects, break the spec and it should be up to application developers to opt-in for that kind of features.

It is temping to allow configuration or add middleware to an HTTP client so it could i.e. follow redirects or throw exceptions.
If that is a decision from an application developer, they have specifically said they want to break the specification.
That is an issue (or feature) the application developer should handle.
Third party libraries MUST NOT assume that a HTTP client breaks the specification.
(https://www.php-fig.org/psr/psr-18/meta/#middleware-and-wrapping-a-client)

Therefore the follow_redirects option should be false by default and users should have to enable it explicitly, knowing that the client no longer complies to PSR-18 if they do so.

Suggested solution

Similar to redirects, a solution could be to disable the http_errors option by default and to require the user to enable them explicitly, knowing that turns off PSR-18-compliancy. The result may be near to PSR-18. However, the exception class hierarchy would still not be correct.
A problem with the solution "make if false by default" is that third-party developers don't know that the user opt-in for non-PSR-18-compliancy and expect a compliant HTTP client instance. The PSR-18 meta document states that

Third party libraries MUST NOT assume that a HTTP client breaks the specification.

Therefore, by spec it should be fine for library developers to don't care about that. However, the application developer should know, that he will likely break things if he decides to enable non-PSR-18-compliant features.

I just noticed, that sendRequest() disables non-PSR-18 features like error exceptions and redirecting, so that the compliance is preserved. I'm going to close this issue.

Anyway, I'm not sure whether Guzzle's InvalidArgumentException really should implement PSR-18 ClientExceptionInterface.