APIGatewayでxmlを返したい

#はじめに
AWSのAPIGatewayで`json`を返している記事は数多くありますが`xml`を返している記事があまり見つからなかったので調査しました。(ググり力不足の可能性あり)
今回はServerlessFrameworkを使用して作成しました。

# 環境
* OS: macOS High Sierra(10.13.6)
* Python: 3.6.5
* pipenv: 2018.7.1
* ServerlessFramework: 1.29.2

# プロジェクト作成

プロジェクトを作成

```
sls create -t aws-python3 -n api-gateway-xml
```
serverless-python-requirements(モジュールを一緒にデプロイするために必要)をインストール

```
sls plugin install -n serverless-python-requirements
```

pipenvで仮想環境を作成

```
pipenv install --python 3.6.5   
```

#serverless.ymlとhandler.pyの編集
とりあえず最初はシンプルにjson返してみましょう

```yml:serverless.yml
service: api-gateway-xml
provider:
  name: aws
  runtime: python3.6
  stage: dev # とりあえずdevで
  region: ap-northeast-1 # リージョン指定
  profile: xxxxxxxxxx # credentialsが複数あるのであれば指定できる

functions:
  main: # 生成されたhelloはなんかダサいので関数名変えました
    handler: handler.get_sin
    events:
      - http: GET /sin/{theta} # APIGatewayのルーティング、{}はパスのパラメータ

plugins:
  - serverless-python-requirements

custom: # pipenvで入れたライブラリを使用する
  pythonRequirements:
    usePipenv: true 
```

```python:handler.py
import json
import math


def get_sin(event, context):
    theta = event['pathParameters']['theta']
    try:
        theta = int(theta)
        sin = math.sin(math.radians(theta))
        body = {
            "sin": sin
            }
    except:
        body = {
            "error": 'not number'
            }
    
    response = {
        'statusCode': 200,
        'isBase64Encoded': False,
        'headers': {'Content-Type': 'application/json'},
        'body': json.dumps(body)
    }

    return response

```
あんまりいい例が思いつかなかったので`sinθ`の値を返すAPIを作成しました。`{theta}`には角度を入力します。
※ちなみに安直にnumpyをpipenvで入れたらLambda実行時にエラーになりました。どうやらCで書かれたライブラリはMac用にビルドされているので使えないようです。なので`math`ライブラリで書いています。

# デプロイとテスト
デプロイ

```
sls deploy -v
```

```
Service Information
service: api-gateway-xml
stage: dev
region: ap-northeast-1
stack: api-gateway-xml-dev
api keys:
  None
endpoints:
  GET - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/sin/{theta}
functions:
  main: api-gateway-xml-dev-main
```
デプロイが終了するとエンドポイントが表示されるので`curl`を投げてみましょう。

```
curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/sin/30 | jq 
```

```json
{
  "sin": 0.49999999999999994
}
```
返ってきましたね。

#本題：xmlにして返す
今回使用するのは[xmltodict](https://github.com/martinblech/xmltodict)というライブラリです。
xmlをdict(辞書)に変換してくれるライブラリですがその逆のdict(辞書)からxmlに変換もしてくれます。

```
pipenv install xmltodict
```

handler.pyを編集

```python:handler.py
import math
import xmltodict


def get_sin(event, context):
    theta = event['pathParameters']['theta']
    try:
        theta = int(theta)
        sin = math.sin(math.radians(theta))
        body = {
            "sin": sin
            }
    except:
        body = {
            "error": 'not number'
            }
    
    response = {
        'statusCode': 200,
        'isBase64Encoded': False,
        'headers': {'Content-Type': 'text/xml'},
        'body': xmltodict.unparse(
            body, pretty=True, encoding='utf-8')
    }

    return response
```
デプロイしたら`curl`を投げてテスト

```
curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/sin/30
```

```xml
<?xml version="1.0" encoding="utf-8"?>
<sin>0.49999999999999994</sin>
```
xmlになって返ってきました。

```python
body = {
        'result':{'sin': sin}
        }
```
bodyの部分をこのような構成にするとxmlの子要素も作成してくれます

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
	<sin>0.49999999999999994</sin>
</result>
```

# 日本語を扱う場合の注意点
上記した`handler.py`の`xmltodict.unparse`で`encoding='utf-8'`としていますが、文字化けします。
headersを以下のように編集すると文字化けしなくなります

```python
response = {
        'statusCode': 200,
        'isBase64Encoded': False,
        'headers': {'Content-Type': 'text/xml;charset=UTF-8'}, # ;charset=UTF-8を追記
        'body': xmltodict.unparse(
            body, pretty=True, encoding='utf-8')
    }
```