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