

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 第 3 適用於 PHP 的 AWS SDK 版中的處理常式和中介軟體
<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);
```

**注意**  
若要在建構多區域用戶端之後變更其處理常式，請使用 的 `useCustomHandler`方法`Aws\MultiRegionClient`。  

```
$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**  
此生命週期步驟旨在驗證命令的輸入參數。  
您可以透過 `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');
```

**sign**  
在透過線路傳送 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);
```

當您執行通過 history 中介軟體的請求後，即可檢查歷史記錄容器。

```
// 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\_timeout](guide_configuration.md#http-connect-timeout) 
+  [debug](guide_configuration.md#http-debug) 
+  [decode\_content](guide_configuration.md#http-decode-content) (選用)
+  [延遲](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\_stats\_receiver (選用) - 如果使用 [stats](guide_configuration.md#config-stats) 組態參數進行請求，即透過 HTTP 傳輸統計資料的關聯陣列來呼叫此函數。

除非將選項指定為選用，否則處理常式必須能夠處理選項，或必須傳回遭拒絕的 promise。

除了處理特定的 `@http` 選項之外，處理器還必須新增採用下列格式的 `User-Agent` 標頭；您可以使用 `Aws\Sdk::VERSION` 來取代以下格式中的 “3.X”，並用您的處理器特定的 User-Agent 字串來取代 “HandlerSpecificData/version ...”。

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