Middleware.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. namespace GuzzleHttp;
  3. use GuzzleHttp\Cookie\CookieJarInterface;
  4. use GuzzleHttp\Exception\RequestException;
  5. use GuzzleHttp\Promise as P;
  6. use GuzzleHttp\Promise\PromiseInterface;
  7. use Psr\Http\Message\RequestInterface;
  8. use Psr\Http\Message\ResponseInterface;
  9. use Psr\Log\LoggerInterface;
  10. /**
  11. * Functions used to create and wrap handlers with handler middleware.
  12. */
  13. final class Middleware
  14. {
  15. /**
  16. * Middleware that adds cookies to requests.
  17. *
  18. * The options array must be set to a CookieJarInterface in order to use
  19. * cookies. This is typically handled for you by a client.
  20. *
  21. * @return callable Returns a function that accepts the next handler.
  22. */
  23. public static function cookies(): callable
  24. {
  25. return static function (callable $handler): callable {
  26. return static function ($request, array $options) use ($handler) {
  27. if (empty($options['cookies'])) {
  28. return $handler($request, $options);
  29. } elseif (!($options['cookies'] instanceof CookieJarInterface)) {
  30. throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
  31. }
  32. $cookieJar = $options['cookies'];
  33. $request = $cookieJar->withCookieHeader($request);
  34. return $handler($request, $options)
  35. ->then(
  36. static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface {
  37. $cookieJar->extractCookies($request, $response);
  38. return $response;
  39. }
  40. );
  41. };
  42. };
  43. }
  44. /**
  45. * Middleware that throws exceptions for 4xx or 5xx responses when the
  46. * "http_errors" request option is set to true.
  47. *
  48. * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages.
  49. *
  50. * @return callable(callable): callable Returns a function that accepts the next handler.
  51. */
  52. public static function httpErrors(?BodySummarizerInterface $bodySummarizer = null): callable
  53. {
  54. return static function (callable $handler) use ($bodySummarizer): callable {
  55. return static function ($request, array $options) use ($handler, $bodySummarizer) {
  56. if (empty($options['http_errors'])) {
  57. return $handler($request, $options);
  58. }
  59. return $handler($request, $options)->then(
  60. static function (ResponseInterface $response) use ($request, $bodySummarizer) {
  61. $code = $response->getStatusCode();
  62. if ($code < 400) {
  63. return $response;
  64. }
  65. throw RequestException::create($request, $response, null, [], $bodySummarizer);
  66. }
  67. );
  68. };
  69. };
  70. }
  71. /**
  72. * Middleware that pushes history data to an ArrayAccess container.
  73. *
  74. * @param array|\ArrayAccess<int, array> $container Container to hold the history (by reference).
  75. *
  76. * @return callable(callable): callable Returns a function that accepts the next handler.
  77. *
  78. * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
  79. */
  80. public static function history(&$container): callable
  81. {
  82. if (!\is_array($container) && !$container instanceof \ArrayAccess) {
  83. throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
  84. }
  85. return static function (callable $handler) use (&$container): callable {
  86. return static function (RequestInterface $request, array $options) use ($handler, &$container) {
  87. return $handler($request, $options)->then(
  88. static function ($value) use ($request, &$container, $options) {
  89. $container[] = [
  90. 'request' => $request,
  91. 'response' => $value,
  92. 'error' => null,
  93. 'options' => $options,
  94. ];
  95. return $value;
  96. },
  97. static function ($reason) use ($request, &$container, $options) {
  98. $container[] = [
  99. 'request' => $request,
  100. 'response' => null,
  101. 'error' => $reason,
  102. 'options' => $options,
  103. ];
  104. return P\Create::rejectionFor($reason);
  105. }
  106. );
  107. };
  108. };
  109. }
  110. /**
  111. * Middleware that invokes a callback before and after sending a request.
  112. *
  113. * The provided listener cannot modify or alter the response. It simply
  114. * "taps" into the chain to be notified before returning the promise. The
  115. * before listener accepts a request and options array, and the after
  116. * listener accepts a request, options array, and response promise.
  117. *
  118. * @param callable $before Function to invoke before forwarding the request.
  119. * @param callable $after Function invoked after forwarding.
  120. *
  121. * @return callable Returns a function that accepts the next handler.
  122. */
  123. public static function tap(?callable $before = null, ?callable $after = null): callable
  124. {
  125. return static function (callable $handler) use ($before, $after): callable {
  126. return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
  127. if ($before) {
  128. $before($request, $options);
  129. }
  130. $response = $handler($request, $options);
  131. if ($after) {
  132. $after($request, $options, $response);
  133. }
  134. return $response;
  135. };
  136. };
  137. }
  138. /**
  139. * Middleware that handles request redirects.
  140. *
  141. * @return callable Returns a function that accepts the next handler.
  142. */
  143. public static function redirect(): callable
  144. {
  145. return static function (callable $handler): RedirectMiddleware {
  146. return new RedirectMiddleware($handler);
  147. };
  148. }
  149. /**
  150. * Middleware that retries requests based on the boolean result of
  151. * invoking the provided "decider" function.
  152. *
  153. * If no delay function is provided, a simple implementation of exponential
  154. * backoff will be utilized.
  155. *
  156. * @param callable $decider Function that accepts the number of retries,
  157. * a request, [response], and [exception] and
  158. * returns true if the request is to be retried.
  159. * @param callable $delay Function that accepts the number of retries and
  160. * returns the number of milliseconds to delay.
  161. *
  162. * @return callable Returns a function that accepts the next handler.
  163. */
  164. public static function retry(callable $decider, ?callable $delay = null): callable
  165. {
  166. return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
  167. return new RetryMiddleware($decider, $handler, $delay);
  168. };
  169. }
  170. /**
  171. * Middleware that logs requests, responses, and errors using a message
  172. * formatter.
  173. *
  174. * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
  175. *
  176. * @param LoggerInterface $logger Logs messages.
  177. * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
  178. * @param string $logLevel Level at which to log requests.
  179. *
  180. * @return callable Returns a function that accepts the next handler.
  181. */
  182. public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable
  183. {
  184. // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter
  185. if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) {
  186. throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class));
  187. }
  188. return static function (callable $handler) use ($logger, $formatter, $logLevel): callable {
  189. return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) {
  190. return $handler($request, $options)->then(
  191. static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface {
  192. $message = $formatter->format($request, $response);
  193. $logger->log($logLevel, $message);
  194. return $response;
  195. },
  196. static function ($reason) use ($logger, $request, $formatter): PromiseInterface {
  197. $response = $reason instanceof RequestException ? $reason->getResponse() : null;
  198. $message = $formatter->format($request, $response, P\Create::exceptionFor($reason));
  199. $logger->error($message);
  200. return P\Create::rejectionFor($reason);
  201. }
  202. );
  203. };
  204. };
  205. }
  206. /**
  207. * This middleware adds a default content-type if possible, a default
  208. * content-length or transfer-encoding header, and the expect header.
  209. */
  210. public static function prepareBody(): callable
  211. {
  212. return static function (callable $handler): PrepareBodyMiddleware {
  213. return new PrepareBodyMiddleware($handler);
  214. };
  215. }
  216. /**
  217. * Middleware that applies a map function to the request before passing to
  218. * the next handler.
  219. *
  220. * @param callable $fn Function that accepts a RequestInterface and returns
  221. * a RequestInterface.
  222. */
  223. public static function mapRequest(callable $fn): callable
  224. {
  225. return static function (callable $handler) use ($fn): callable {
  226. return static function (RequestInterface $request, array $options) use ($handler, $fn) {
  227. return $handler($fn($request), $options);
  228. };
  229. };
  230. }
  231. /**
  232. * Middleware that applies a map function to the resolved promise's
  233. * response.
  234. *
  235. * @param callable $fn Function that accepts a ResponseInterface and
  236. * returns a ResponseInterface.
  237. */
  238. public static function mapResponse(callable $fn): callable
  239. {
  240. return static function (callable $handler) use ($fn): callable {
  241. return static function (RequestInterface $request, array $options) use ($handler, $fn) {
  242. return $handler($request, $options)->then($fn);
  243. };
  244. };
  245. }
  246. }