

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 适用于 PHP 的 AWS SDK 版本 3 中的处理程序和中间件
<a name="guide_handlers-and-middleware"></a>

适用于 PHP 的 AWS SDK的主要扩展机制是，通过**处理程序**和**中间件**进行扩展。每个开发工具包客户端类都拥有一个 `Aws\HandlerList` 实例，可通过客户端的 `getHandlerList()` 方法访问。您可以检索客户端的 `HandlerList`，并对其进行修改来添加或删除客户端行为。

## 处理程序
<a name="handlers"></a>

处理程序是一个将命令和请求实际转换为结果的函数。处理程序通常发送 HTTP 请求。处理程序可由中间件组成，以增强行为。处理程序是一个函数，它接受 `Aws\CommandInterface` 和 `Psr\Http\Message\RequestInterface`，并返回用 `Aws\ResultInterface` 执行或因 `Aws\Exception\AwsException` 原因而被拒绝的 Promise。

以下处理程序的每次调用均返回相同的模拟结果。

```
use Aws\CommandInterface;
use Aws\Result;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Promise;

$myHandler = function (CommandInterface $cmd, RequestInterface $request) {
    $result = new Result(['foo' => 'bar']);
    return Promise\promise_for($result);
};
```

您可以在客户端的构造函数中提供 `handler` 选项，将此处理程序与开发工具包客户端结合使用。

```
// Set the handler of the client in the constructor
$s3 = new Aws\S3\S3Client([
    'region'  => 'us-east-1',
    'version' => '2006-03-01',
    'handler' => $myHandler
]);
```

您还可以在客户端构造完成后使用 `setHandler` 的`Aws\ClientInterface` 方法更改其处理程序。

```
// Set the handler of the client after it is constructed
$s3->getHandlerList()->setHandler($myHandler);
```

**注意**  
要在多区域客户端构造完成后更改其处理程序，请使用 `Aws\MultiRegionClient` 的 `useCustomHandler` 方法。  

```
$multiRegionClient->useCustomHandler($myHandler);
```

### 模拟处理程序
<a name="mock-handler"></a>

我们建议在使用开发工具包编写测试时使用 `MockHandler`。您可以使用 `Aws\MockHandler` 返回模拟结果或引发模拟异常。加入队列的结果或异常，MockHandler 会按 FIFO 顺序将它们从队列中移除。

```
use Aws\Result;
use Aws\MockHandler;
use Aws\DynamoDb\DynamoDbClient;
use Aws\CommandInterface;
use Psr\Http\Message\RequestInterface;
use Aws\Exception\AwsException;

$mock = new MockHandler();

// Return a mocked result
$mock->append(new Result(['foo' => 'bar']));

// You can provide a function to invoke; here we throw a mock exception
$mock->append(function (CommandInterface $cmd, RequestInterface $req) {
    return new AwsException('Mock exception', $cmd);
});

// Create a client with the mock handler
$client = new DynamoDbClient([
    'region'  => 'us-west-2',
    'version' => 'latest',
    'handler' => $mock
]);

// Result object response will contain ['foo' => 'bar']
$result = $client->listTables();

// This will throw the exception that was enqueued
$client->listTables();
```

## 中间件
<a name="middleware"></a>

中间件是一类特殊的高级函数，可对传输命令的行为进行增强，并委托给“下一个”处理程序。中间件函数接受 `Aws\CommandInterface` 和 `Psr\Http\Message\RequestInterface`，并返回用 `Aws\ResultInterface` 执行或因 `Aws\Exception\AwsException` 原因而被拒绝的 Promise。

中间件是更高阶的函数，可修改经过中间件传递的命令、请求或结果。中间件具有以下形式。

```
use Aws\CommandInterface;
use Psr\Http\Message\RequestInterface;

$middleware = function () {
    return function (callable $handler) use ($fn) {
        return function (
            CommandInterface $command,
            RequestInterface $request = null
        ) use ($handler, $fn) {
            // Do something before calling the next handler
            // ...
            $promise = $fn($command, $request);
            // Do something in the promise after calling the next handler
            // ...
            return $promise;
        };
    };
};
```

中间件接收要执行的命令和可选的请求对象。中间件可增强请求和命令，或将它们保留原样。然后中间件会调用链条中的下一个处理程序，或选择将下一个处理程序短路并返回 Promise。调用下一个处理程序创建的 Promise，可使用 Promise 的 `then` 方法进行增强，在将 Promise 返回中间件组之前，修改最终结果或错误。

### HandlerList
<a name="handlerlist"></a>

开发工具包使用 `Aws\HandlerList` 管理执行命令所用的中间件和处理程序。每个开发工具包客户端都拥有一个 `HandlerList`，这个 `HandlerList` 会克隆并添加到客户端创建的每条命令。您可以将中间件添加到客户端的 `HandlerList`，为客户端创建的每条命令附加要使用的中间件和默认处理程序。您可以修改特定命令的 `HandlerList`，添加或删除特定命令的中间件。

`HandlerList` 表示用于包装**处理程序**的一组中间件。`HandlerList` 可将中间件组拆分为一些具名步骤，表示传输命令的生命周期中的各个部分。这样有助于管理中间件的列表，以及包装处理程序的顺序。

1.  `init` - 添加默认参数

1.  `validate` - 验证必要参数

1.  `build` - 序列化 HTTP 请求，准备发送

1.  `sign` - 对序列化 HTTP 请求进行签名

1. <handler>（不是一个步骤，但执行实际传输过程）

**init**  
生命周期中的这个步骤表示命令的初始化，以及尚未序列化的请求。此步骤通常用于在命令中添加默认参数。  
您可以使用 `init` 和 `appendInit` 方法在 `prependInit` 步骤中添加中间件，其中 `appendInit` 将中间件添加到 `prepend` 列表的最后，`prependInit` 将中间件添加到 `prepend` 列表的开头。  

```
use Aws\Middleware;

$middleware = Middleware::tap(function ($cmd, $req) {
    // Observe the step
});

// Append to the end of the step with a custom name
$client->getHandlerList()->appendInit($middleware, 'custom-name');
// Prepend to the beginning of the step
$client->getHandlerList()->prependInit($middleware, 'custom-name');
```

**验证**  
生命周期中的这个步骤用于验证命令的输入参数。  
您可以使用 `validate` 和 `appendValidate` 方法在 `prependValidate` 步骤中添加中间件，其中 `appendValidate` 将中间件添加到 `validate` 列表的最后，`prependValidate` 将中间件添加到 `validate` 列表的开头。  

```
use Aws\Middleware;

$middleware = Middleware::tap(function ($cmd, $req) {
    // Observe the step
});

// Append to the end of the step with a custom name
$client->getHandlerList()->appendValidate($middleware, 'custom-name');
// Prepend to the beginning of the step
$client->getHandlerList()->prependValidate($middleware, 'custom-name');
```

**build**  
生命周期中的这个步骤用于针对所执行的命令序列化 HTTP 请求。下游生命周期事件会收到一条命令和 PSR-7 HTTP 请求。  
您可以使用 `build` 和 `appendBuild` 方法在 `prependBuild` 步骤中添加中间件，其中 `appendBuild` 将中间件添加到 `build` 列表的最后，`prependBuild` 将中间件添加到 `build` 列表的开头。  

```
use Aws\Middleware;

$middleware = Middleware::tap(function ($cmd, $req) {
    // Observe the step
});

// Append to the end of the step with a custom name
$client->getHandlerList()->appendBuild($middleware, 'custom-name');
// Prepend to the beginning of the step
$client->getHandlerList()->prependBuild($middleware, 'custom-name');
```

**签名**  
生命周期中的这个步骤用于在通过线路发送 HTTP 请求之间进行签名。通常，在 HTTP 请求经过签名后，您即不应该进行更改，以避免签名错误。  
这是处理程序传输 HTTP 请求之前 `HandlerList` 中的最后一步。  
您可以使用 `sign` 和 `appendSign` 方法在 `prependSign` 步骤中添加中间件，其中 `appendSign` 将中间件添加到 `sign` 列表的最后，`prependSign` 将中间件添加到 `sign` 列表的开头。  

```
use Aws\Middleware;

$middleware = Middleware::tap(function ($cmd, $req) {
    // Observe the step
});

// Append to the end of the step with a custom name
$client->getHandlerList()->appendSign($middleware, 'custom-name');
// Prepend to the beginning of the step
$client->getHandlerList()->prependSign($middleware, 'custom-name');
```

### 可用中间件
<a name="available-middleware"></a>

您可使用开发工具包提供的若干中间件增强客户端的行为，或观察命令的执行。

#### mapCommand
<a name="map-command"></a>

如果要在将命令序列化为 HTTP 请求之前修改命令，`Aws\Middleware::mapCommand` 中间件很有用。例如，可使用 `mapCommand` 执行验证或添加默认参数。`mapCommand` 函数接受的可调用函数可接受 `Aws\CommandInterface` 对象并返回 `Aws\CommandInterface` 对象。

```
use Aws\Middleware;
use Aws\CommandInterface;

// Here we've omitted the require Bucket parameter. We'll add it in the
// custom middleware.
$command = $s3Client->getCommand('HeadObject', ['Key' => 'test']);

// Apply a custom middleware named "add-param" to the "init" lifecycle step
$command->getHandlerList()->appendInit(
    Middleware::mapCommand(function (CommandInterface $command) {
        $command['Bucket'] = 'amzn-s3-demo-bucket';
        // Be sure to return the command!
        return $command;
    }),
    'add-param'
);
```

#### mapRequest
<a name="map-request"></a>

如果您需要修改已序列化但尚未发送的请求，`Aws\Middleware::mapRequest` 中间件很有用。例如，可使用它为请求添加自定义 HTTP 标题。`mapRequest` 函数接受的可调用函数可接受 `Psr\Http\Message\RequestInterface` 参数并返回 `Psr\Http\Message\RequestInterface` 对象。

```
use Aws\Middleware;
use Psr\Http\Message\RequestInterface;

// Create a command so that we can access the handler list
$command = $s3Client->getCommand('HeadObject', [
    'Key'    => 'test',
    'Bucket' => 'amzn-s3-demo-bucket'
]);

// Apply a custom middleware named "add-header" to the "build" lifecycle step
$command->getHandlerList()->appendBuild(
    Middleware::mapRequest(function (RequestInterface $request) {
        // Return a new request with the added header
        return $request->withHeader('X-Foo-Baz', 'Bar');
    }),
    'add-header'
);
```

当执行命令时，会随自定义标题发送。

**重要**  
请注意，中间件是在 `build` 步骤的最后追加到处理程序列表中的。这是为了确保在调用中间件之前生成请求。

#### mapResult
<a name="mapresult"></a>

如果您需要修改命令执行的结果，`Aws\Middleware::mapResult` 中间件很有用。`mapResult` 函数接受的可调用函数可接受 `Aws\ResultInterface` 参数并返回 `Aws\ResultInterface` 对象。

```
use Aws\Middleware;
use Aws\ResultInterface;

$command = $s3Client->getCommand('HeadObject', [
    'Key'    => 'test',
    'Bucket' => 'amzn-s3-demo-bucket'
]);

$command->getHandlerList()->appendSign(
    Middleware::mapResult(function (ResultInterface $result) {
        // Add a custom value to the result
        $result['foo'] = 'bar';
        return $result;
    })
);
```

命令执行后，返回的结果中将包含 `foo` 属性。

#### 历史记录
<a name="history"></a>

`history` 中间件可用于测试开发工具包是否执行了您期望的命令、发送了您期望的 HTTP 请求，并收到了您期望的结果。这个中间件与 Web 浏览器的历史记录基本类似。

```
use Aws\History;
use Aws\Middleware;

$ddb = new Aws\DynamoDb\DynamoDbClient([
    'version' => 'latest',
    'region'  => 'us-west-2'
]);

// Create a history container to store the history data
$history = new History();

// Add the history middleware that uses the history container
$ddb->getHandlerList()->appendSign(Middleware::history($history));
```

在清除条目之前，`Aws\History` 历史记录容器中默认可存储 10 条记录，您可以将要保留的条目数量传递到构造函数中，从而自定义条目数量。

```
// Create a history container that stores 20 entries
$history = new History(20);
```

您可以在执行传递历史记录中间件的请求之后，检查历史记录容器。

```
// The object is countable, returning the number of entries in the container
count($history);

// The object is iterable, yielding each entry in the container
foreach ($history as $entry) {
    // You can access the command that was executed
    var_dump($entry['command']);
    // The request that was serialized and sent
    var_dump($entry['request']);
    // The result that was received (if successful)
    var_dump($entry['result']);
    // The exception that was received (if a failure occurred)
    var_dump($entry['exception']);
}

// You can get the last Aws\CommandInterface that was executed. This method
// will throw an exception if no commands have been executed.
$command = $history->getLastCommand();

// You can get the last request that was serialized. This method will throw an exception
// if no requests have been serialized.
$request = $history->getLastRequest();

// You can get the last return value (an Aws\ResultInterface or Exception).
// The method will throw an exception if no value has been returned for the last
// executed operation (e.g., an async request has not completed).
$result = $history->getLastReturn();

// You can clear out the entries using clear
$history->clear();
```

#### tap
<a name="tap"></a>

`tap` 中间件可用作观察工具。您可以使用此中间件，在通过中间件链条发送命令时调用函数。`tap` 函数接受的可调用函数可接受 `Aws\CommandInterface`，以及被执行的可选 `Psr\Http\Message\RequestInterface`。

```
use Aws\Middleware;

$s3 = new Aws\S3\S3Client([
    'region'  => 'us-east-1',
    'version' => '2006-03-01'
]);

$handlerList = $s3->getHandlerList();

// Create a tap middleware that observes the command at a specific step
$handlerList->appendInit(
    Middleware::tap(function (CommandInterface $cmd, RequestInterface $req = null) {
        echo 'About to send: ' . $cmd->getName() . "\n";
        if ($req) {
            echo 'HTTP method: ' . $request->getMethod() . "\n";
        }
    }
);
```

## 创建自定义处理程序
<a name="creating-custom-handlers"></a>

处理程序只是一个接受 `Aws\CommandInterface` 对象和 `Psr\Http\Message\RequestInterface` 对象的函数，它可返回由 `GuzzleHttp\Promise\PromiseInterface` 满足、或由 `Aws\ResultInterface` 拒绝的 `Aws\Exception\AwsException`。

虽然开发工具包有多个 `@http` 选项，处理程序只需要知道如何使用以下选项：
+  [connect\$1timeout](guide_configuration.md#http-connect-timeout) 
+  [debug](guide_configuration.md#http-debug) 
+  [decode\$1content](guide_configuration.md#http-decode-content)（可选）
+  [delay](guide_configuration.md#http-delay) 
+  [progress](guide_configuration.md#http-progress)（可选）
+  [proxy](guide_configuration.md#http-proxy) 
+  [sink](guide_configuration.md#http-sink) 
+  [synchronous](guide_configuration.md#http-sync)（可选）
+  [stream](guide_configuration.md#http-stream)（可选）
+  [-timeout](guide_configuration.md#http-timeout) 
+  [确认](guide_configuration.md#http-verify) 
+ http\$1stats\$1receiver（可选）- 如果使用 [stats](guide_configuration.md#config-stats) 配置参数进行请求，通过 HTTP 传输统计数据的关联数组进行调用的函数。

除非此选项指定为可选，否则处理程序必须处理该选项，或必须返回拒绝的 Promise。

除了处理特定的 `@http` 选项，处理程序还必须添加具有以下形式的 `User-Agent` 标题，其中“3.X”可替换为 `Aws\Sdk::VERSION`，“HandlerSpecificData/version ...”应替换为您的处理程序特定的 User-Agent 字符串。

 `User-Agent: aws-sdk-php/3.X HandlerSpecificData/version ...` 