HTTP requests and responses are the two fundamental objects in web programming. All clients communicating to an external API use some form of HTTP client. Many libraries are coupled to one specific client or implement a client and/or adapter layer themselves. This leads to bad library design, version conflicts, or code unrelated to the library domain.
Thanks to PSR-7 we know how HTTP requests and responses ideally look, but nothing defines how a request should be sent and a response received. A common interface for HTTP clients will allow libraries to be decoupled from specific implementations.
The reason asynchronous requests are not covered by this PSR is the lack of a common standard for Promises. Promises are sufficiently complex enough that they deserve their own specification, and should not be wrapped into this one.
A separate interface for asynchronous requests can be defined in a separate PSR once a Promise PSR is accepted. The method signature for asynchronous requests MUST be different from the method signature for synchronous requests because the return type of asynchronous calls will be a Promise. Thus this PSR is forwards compatible, and clients will be able to implement one or both interfaces based on the features they wish to expose.
The intention of this PSR is to provide library developers with HTTP clients that have a well defined behaviour. A library should be able to use any compliant client without special code to handle client implementation details (Liskov substitution principle). The PSR does not try to restrict nor define how to configure HTTP clients.
An alternative approach would be to pass configuration to the client. That approach would have a few drawbacks:
The main interface behaviour is defined by the method sendRequest(RequestInterface $request): ResponseInterface
.
While the shorter method name send()
has been proposed, this was already used by existing and very common HTTP clients like Guzzle. As such, if they are to adopt this standard, they may need to break backwards compatibility in order to implement the specification. By defining sendRequest()
instead, we ensure they can adopt without any immediate BC breaks.
The domain exceptions NetworkExceptionInterface
and RequestExceptionInterface
define
a contract very similar to each other. The chosen approach is to not let them extend each other
because inheritance does not make sense in the domain model. A RequestExceptionInterface
is simply not a
NetworkExceptionInterface
.
Allowing exceptions to extend a RequestAwareException
and/or ResponseAwareException
interface
has been discussed but that is a convenience shortcut that one should not take. One should rather
catch the specific exceptions and handle them accordingly.
One could be more granular when defining exceptions. For example, TimeOutException
and HostNotFoundException
could be subtypes of NetworkExceptionInterface
. The chosen approach is not to define such subtypes because
the exception handling in a consuming library would in most cases not be different between those exceptions.
The initial idea was to allow the client to be configured to throw exceptions for responses with HTTP status 4xx and 5xx. That approach is not desired because consuming libraries would have to check for 4xx and 5xx responses twice: first, by verifying the status code on the response, and second by catching potential exceptions.
To make the specification more predictable, it was decided that HTTP clients never will throw exceptions for 4xx and 5xx responses.
The specification does not put any limitations on middleware or classes that want
to wrap/decorate an HTTP client. If the decorating class also implements ClientInterface
then it must also follow the specification.
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.
The HTTP client PSR has been inspired and created by the php-http team. Back in 2015, they created HTTPlug as a common interface for HTTP clients. They wanted an abstraction that third party libraries could use so as not to rely on a specific HTTP client implementation. A stable version was tagged in January 2016, and the project has been widely adopted since then. With over 3 million downloads in the two years following the initial stable version, it was time to convert this "de-facto" standard into a formal specification.
Below are the two implementations provided by the working group to pass the review period: