Middleware.php 9.7 KB

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