今時のPHP HTTPクライアントのGuzzleを使ってみた

# はじめに
LaravelでHTTPリクエストを投げたかったので[Guzzle](https://github.com/guzzle/guzzle)を使ってみました。
以前ですとPEARの[HTTP_Request2](http://pear.php.net/package/HTTP_Request2/)を使っていましたが、あまり最近使われていないようなので新しいパッケージを使ってみました。
HTTP_Request2は長年使ってまして、使いやすくて好きです。[cURL](http://php.net/manual/en/book.curl.php)も悪くはないのですが…。
Laravelで使いましたが、もちろん生のPHPでも使えます。

# Laravelのラッパー
Laravel 7系ではGuzzleラッパーが提供されているようです。

例

```php
$response = Http::post('http://test.com/users', [
    'name' => 'Steve',
    'role' => 'Network Administrator',
]);
```

@macky4 さんありがとうございます。

> Laravelだとラッパーのおかけでもっと楽に書けますよ！
> https://readouble.com/laravel/7.x/ja/http-client.html

# インストール
今回はLaravelで使ってみました。生のPHPでもOKです。

```console
$ laravel new hogeproject
$ cd hogeproject
$ composer require guzzlehttp/guzzle
Using version ^6.2 for guzzlehttp/guzzle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
```

コントローラの作成

```console
$ php artisan make:controller HogeController
```


# リクエストの作成

```console
$ php artisan make:controller HogeController
```

```php:routes/web.php
Route::get('/basic_request', 'HogeController@basic_request');
Route::get('/with_headers', 'HogeController@with_headers');
```

## GET
GETリクエスト

```php:app/Http/Controllers/HogeController.php
public function basic_request() {
    $base_url = 'http://example.com';
    $client = new \GuzzleHttp\Client( [
      'base_uri' => $base_url,
    ] );
    $path = '/index.html';
    $response = $client->request( 'GET', $path,
     [
       'allow_redirects' => true,
     ] );
    $response_body = (string) $response->getBody();
    echo $response_body;
}
```

## ステータスコード取得
```php

public function get_http_status_code() {
    $base_url = 'http://example.com';
    $client = new \GuzzleHttp\Client( [
	    'base_uri' => $base_url,
    ] );

    $path = '/index.html';
    $response = $client->request( 'GET', $path,
	    [
		    'allow_redirects' => true,
	    ] );
    $response_body = (string) $response->getStatusCode();
    echo $response_body;
}
```



## JSONで送る

```php
$response = $client->request('PUT', '/put', ['json' => ['foo' => 'bar']]);
```
`request`のオプション引数に`json`で配列を渡すと良い感じにしてくれます。
（おそらく）`json_encode()`, `Conent-Type: application/json`の指定を行ってくれます。

> http://docs.guzzlephp.org/en/latest/request-options.html#json

# リクエストヘッダー指定
リファラーなどのリクエストヘッダーを指定する場合には配列を渡します。

```php:app/Http/Controllers/HogeController.php
public function with_headers() {
	$base_url = 'http://example.com';
	$client = new \GuzzleHttp\Client( [
		'base_uri' => $base_url,
	] );

	$path = '/index.html';
	$headers = [
		'Origin'                    => 'http://www.example.com',
		'Accept-Encoding'           => 'gzip, deflate, br',
		'Accept-Language'           => 'ja,en-US;q=0.8,en;q=0.6',
		'Upgrade-Insecure-Requests' => '1',
		'User-Agent'                => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36',
		'Content-Type'              => 'application/x-www-form-urlencoded',
		'Accept'                    => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
		'Cache-Control'             => 'max-age=0',
		'Referer'                   => 'http://www.example.com',
		'Connection'                => 'keep-alive'
	];
	$response = $client->request( 'GET', $path,
		[
			'allow_redirects' => true,
			'headers'         => $headers,
			//'form_params'     => $form_params,
		] );
	$response_body = (string) $response->getBody();
	echo $response_body;
}
```

## GETでクエリストリングを指定

http://docs.guzzlephp.org/en/latest/quickstart.html#query-string-parameters

`query`に配列を指定するとクエリストリングを指定できるようです。

```php
public function get_with_params() {
    $base_url = 'http://example.com';
    $client = new \GuzzleHttp\Client( [
	    'base_uri' => $base_url,
    ] );

    $path = '/index.html';
    $dev_null = $client->get($path,
	    [
	        'query' => ['foo' => 'bar'],
		    'on_stats' => function (\GuzzleHttp\TransferStats $stats) use (&$url) {
			    $url = $stats->getEffectiveUri();
		    }
	    ] )->getBody()->getContents();
    echo $url;
}
```

# 404を許容する書き方
404の場合には例外を投げられてしまいます。
この例外を回避する2つの方法です。

まずはエラーメッセージについてです。

```html:404だったときのエラーメッセージ
 [GuzzleHttp\Exception\ClientException]
  Client error: `GET http://www.l2tp.org/index.html` resulted in a `404 Not Foun
  d` response:
  <!DOCTYPE html>
  <html lang="ja" class="no-js no-svg">
  <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width (truncated...)
```

## http_errorsをfalseにする
@bootjp さんに教えてもらいました。ありがとうございます！
リクエストを投げる際に`http_errors`というオプションを`false`にすると例外を投げられないようです。

```php
$client->request('GET', '/status/500');
// Throws a GuzzleHttp\Exception\ServerException

$res = $client->request('GET', '/status/500', ['http_errors' => false]);
echo $res->getStatusCode();
// 500
```

> `false`に設定すると400番台、500番台などのHTTPエラーを無効化します。デフォルトでは例外が飛ばされます。

<!-- 空行 -->

> Set to false to disable throwing exceptions on an HTTP protocol errors (i.e., 4xx and 5xx responses). Exceptions are thrown by default when HTTP protocol errors are encountered.

http://docs.guzzlephp.org/en/stable/request-options.html#http-errors

## 例外を拾う
もう一つの方法はClientExceptionで例外を拾います。
投げられる例外のClientExceptionはResponseと同じような使い方ができます。

```php
try {
    $client->request('GET', 'https://github.com/_abc_123_404');
} catch (ClientException $e) {
    echo Psr7\str($e->getRequest());
    echo Psr7\str($e->getResponse());
}
```

http://docs.guzzlephp.org/en/latest/quickstart.html

もしくはレスポンスを使う場合、getResponseを受けて渡すと良いようです。

```php
try {
    $client->request('GET', 'https://github.com/_abc_123_404');
} catch (ClientException $e) {
    $response = $e->getResponse();
}
  echo $response->getStatusCode();
```

# リクエストを確認する
デバッグを行うときにそもそも思った通りのリクエストが投げれているか、ということを確認したい場合があります。
そういう場合の確認方法です。

## リクエストしたbodyの確認
上記とは変わってbodyの確認を行います。
どういったリクエストが送られているか確認するときに使います。

```php
public function send_request_with_dump() {
    $base_url = 'http://example.com';
    $client = new \GuzzleHttp\Client( [
	    'base_uri' => $base_url,
    ] );

    $path = '/index.html';

    $stack = HandlerStack::create();
    // my middleware
    $stack->push(Middleware::mapRequest(function (RequestInterface $request) use (&$contentsRequest) {
    $contentsRequest = (string) $request->getBody();
      var_dump($contentsRequest);
      return $request;
    }));

    $dev_null = $client->request( 'GET', $path,
	    [
              'handler' => $stack,
	    ] )->getBody()->getContents();
    echo $url;
}
```

> How do I get the body of SENT data with Guzzle PHP?
> http://stackoverflow.com/a/34911017/2405335

ミドルウェアの機構を使ってリクエスト時に関数を実行します。戻り値でのとり方はわからなかったので参考先通りに`var_dump()`しています。

## リクエストヘッダの確認

リクエストしたヘッダの確認です。レスポンスヘッダじゃなくてリクエストしたヘッダです。

```php
$YourGuzzleclient=new Client();
$YourGuzzleclient->request('POST', '{Your url}',
  ['debug'=>true,'otheroptions'=>array()]
);
```

> http://stackoverflow.com/a/39239978/2405335

## リクエストしたURLの確認
ちょっと書き方が新しいですね。`on_stats`に`TransferStatus`を受けるクロージャを渡すと、その中でurlを取得できるようです。
`use`と参照渡しで外側でも参照できます。JavaScriptっぽいですね。
PHPの内部で変数などを実行した際にちゃんと送れているか、というデバッグに使いました。

```php
public function get_requested_url() {
    $base_url = 'http://example.com';
    $client = new \GuzzleHttp\Client( [
	    'base_uri' => $base_url,
    ] );

    $path = '/index.html';
    $dev_null = $client->request( 'GET', $path,
	    [
		    'on_stats' => function (\GuzzleHttp\TransferStats $stats) use (&$url) {
			    $url = $stats->getEffectiveUri();
		    }
	    ] )->getBody()->getContents();
    echo $url;  // http://example.com/index.html
}
```

参考: [How to read the response effective URL in Guzzle ~6.0](http://stackoverflow.com/questions/30682307/how-to-read-the-response-effective-url-in-guzzle-6-0)

# 並列アクセス
Promiseを使った並列アクセスが可能です。
クロージャを用意し、`new Pool()`に`$client`とRequestを行う関数を渡します。


```php
<?php
require_once 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

$client = new Client();

$requests = function ($urls) {
	for ($i = 0; $i < count($urls); $i++) {
		yield new Request('GET', $urls[$i]);
	}
};

$urls = [
	'http://example.com',
	'http://example.com',
	'http://example.com',
	'http://example.com',
];

$pool = new Pool($client, $requests($urls), [
	'concurrency' => 5,
	'fulfilled' => function (Response $response, $index) {
		// var_dump($response->getHeader('Server'));
		// this is delivered each successful response
	},
	'rejected' => function (RequestException $reason, $index) {
		// this is delivered each failed request
	},
]);

// Initiate the transfers and create a promise
$promise = $pool->promise();

// Force the pool of requests to complete.
$promise->wait();
```



# 参考
[Using GuzzleHttp with Laravel]
(https://medium.com/laravel-5-the-right-way/using-guzzlehttp-with-laravel-1dbea1f633da)
[Guzzle で Twitter REST API を叩く](http://qiita.com/kawanamiyuu/items/2cab57e3d3932a2c5e4e)