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