Proxy環境化で graphql-request を使う

この記事は、proxy 環境下で **graphql-request** を使うためのメモです。

# はじめに

- **graphql-request** は、非常にシンプルで軽量な GraphQL のクライアント実装です。
  - npm: [graphql-request](https://www.npmjs.com/package/graphql-request)
  - GitHub: [graphql-request](https://github.com/prisma/graphql-request)



# 環境

下記の環境で動作確認しました。

- Windows 10 Pro (64bit)
- [Git for Windows 2.20.1](https://gitforwindows.org/)
  - Git Bash : GNU bash, version 4.4.23(1)-release (x86_64-pc-msys)
- [Node.js 8.10.0](https://nodejs.org/download/release/v8.10.0/)
  - ここでは AWS Lambda でサポートされている最新の Node.js のバージョンに合わせています
- npm 5.6.0
- [TypeScript 3.3](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-3.html)


# graphql-request の動作確認

## サンプルプロジェクトの作成

Git Bash を起動して、適当な名前で node module を作成する。

```
$ mkdir sample-graphql
$ cd sample-graphql
$ npm init -y
Wrote to C:\work\sample-graphql\package.json:

{
  "name": "sample-graphql",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

```

## graphql-request のインストール

npm install で graphql-request をインストールする。

```
$ npm install --save graphql-request
npm WARN registry Using stale data from https://registry.npmjs.org/ because the host is inaccessible -- are you offline?
npm WARN registry Using stale package data from https://registry.npmjs.org/ due to a request error during revalidation.
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN sample-graphql@1.0.0 No description
npm WARN sample-graphql@1.0.0 No repository field.

+ graphql-request@1.8.2
added 4 packages in 0.853s

```


## サンプルコードの作成

[graphql-request](https://www.npmjs.com/package/graphql-request) の Quickstart を参考にサンプルコードを作成する。

```js:index.js
const GraphQLClient = require('graphql-request').GraphQLClient;

const endpoint = 'https://api.graph.cool/simple/v1/movies';
const options = {};
const client = new GraphQLClient(endpoint, options);

const query = `{
  Movie(title: "Inception") {
    releaseDate
    actors {
      name
    }
  }
}`

const variables = {};

client.request(query, variables)
  .then(data => console.log(JSON.stringify(data, null, 2)))
  .catch(error => console.error(error));

```


## サンプルコードの実行

* 非 Proxy 環境下での実行結果

```
$ node index.js
{
  "Movie": {
    "releaseDate": "2010-08-28T20:00:00.000Z",
    "actors": [
      {
        "name": "Leonardo DiCaprio"
      },
      {
        "name": "Ellen Page"
      },
      {
        "name": "Tom Hardy"
      },
      {
        "name": "Joseph Gordon-Levitt"
      },
      {
        "name": "Marion Cotillard"
      }
    ]
  }
}
```

* Proxy 環境下での実行結果
  * 接続できずにタイムアウトで失敗

```
$ node index.js
{ FetchError: request to https://api.graph.cool/simple/v1/movies failed, reason: connect ETIMEDOUT 13.33.0.193:443
    at ClientRequest.<anonymous> (C:\work\sample-graphql\node_modules\node-fetch\lib\index.js:1393:11)
    at emitOne (events.js:116:13)
    at ClientRequest.emit (events.js:211:7)
    at TLSSocket.socketErrorListener (_http_client.js:387:9)
    at emitOne (events.js:116:13)
    at TLSSocket.emit (events.js:211:7)
    at emitErrorNT (internal/streams/destroy.js:64:8)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)
  message: 'request to https://api.graph.cool/simple/v1/movies failed, reason: connect ETIMEDOUT 13.33.0.193:443',
  type: 'system',
  errno: 'ETIMEDOUT',
  code: 'ETIMEDOUT' }
```

# Proxy 対応

## https-proxy-agent のインストール

[https-proxy-agent](https://www.npmjs.com/package/https-proxy-agent) は Proxy 経由で HTTPS 接続する node module です。

```
$ npm install --save https-proxy-agent
npm WARN sample-graphql@1.0.0 No description
npm WARN sample-graphql@1.0.0 No repository field.

+ https-proxy-agent@2.2.1
added 6 packages in 1.766s

```

## サンプルコードの修正と実行

**graphql-request** は [node-fetch](https://www.npmjs.com/package/node-fetch) を利用しており、 constructor の options に渡した値が [Options](https://www.npmjs.com/package/node-fetch#options) として渡されます。

```js:index.js
const GraphQLClient = require('graphql-request').GraphQLClient;
const HttpsProxyAgent = require('https-proxy-agent');

const endpoint = 'https://api.graph.cool/simple/v1/movies';
const options = {
  agent: new HttpsProxyAgent(process.env.HTTP_PROXY),
};
const client = new GraphQLClient(endpoint, options);

const query = `{
  Movie(title: "Inception") {
    releaseDate
    actors {
      name
    }
  }
}`

const variables = {};

client.request(query, variables)
  .then(data => console.log(JSON.stringify(data, null, 2)))
  .catch(error => console.error(error));

```

実行結果

```
$ node index.js
{
  "Movie": {
    "releaseDate": "2010-08-28T20:00:00.000Z",
    "actors": [
      {
        "name": "Leonardo DiCaprio"
      },
      {
        "name": "Ellen Page"
      },
      {
        "name": "Tom Hardy"
      },
      {
        "name": "Joseph Gordon-Levitt"
      },
      {
        "name": "Marion Cotillard"
      }
    ]
  }
}
```

# おまけ：TypeScript の場合


## https-proxy-agent.d.ts の作成

```ts:https-proxy-agent.d.ts
import { Agent } from 'http';

declare class HttpsProxyAgent extends Agent {
  constructor(uri: string | { protocol?: string; host?: string; hostname?: string; port?: string });
}

export = HttpsProxyAgent;
```

## サンプルコードの作成

```ts:index.ts
import { GraphQLClient } from 'graphql-request';
import { Variables } from 'graphql-request/dist/src/types';
import HttpsProxyAgent from 'https-proxy-agent';

const endpoint = 'https://api.graph.cool/simple/v1/movies';
const options = {
  headers: {},
  agent: process.env.HTTP_PROXY ? new HttpsProxyAgent(process.env.HTTP_PROXY) : null,
};
const client = new GraphQLClient(endpoint, options);

const query = `{
  Movie(title: "Inception") {
    releaseDate
    actors {
      name
    }
  }
}`

const variables = {};

client.request(query, variables)
  .then(data => console.log(JSON.stringify(data, null, 2)))
  .catch(error => console.error(error));

```