Serverless で MisocaAPI(OAuth2)を利用するサービスを作るまで on Cloud9

# はじめに
Serverless フレームワークを用いて Misoca API を使ってみたので手順をまとめます。
開発環境としてCloud9を利用します。Lambda 関数は Python3 で記述します。

Misocaについて実装していますがOAuth2を用いるサービスであれば基本的に同じ感じでできる、、はず？

# 1. Cloud9のセットアップ

- cloud9でワークスペースを作ります （テンプレートはBlankを選択しました）

![Cloud9でワークスペースを作成](https://qiita-image-store.s3.amazonaws.com/0/5557/aefc6e2f-8a44-7ec0-809e-57fde84e1d6f.png)

## 1.1. Python のバージョンを 3.6 にする
[参考] http://imabari.hateblo.jp/entry/2017/10/09/223748

```:現在のバージョンを確認
$ python -V
Python 2.7.6
```

```:aptのリポジトリを追加してupdateしてからpython3.6をインストール
$ sudo add-apt-repository ppa:jonathonf/python-3.6
$ sudo apt-get update
$ sudo apt-get install python3.6
```

```:pipを更新
wget https://bootstrap.pypa.io/ez_setup.py
sudo -H python3.6 ez_setup.py
wget https://bootstrap.pypa.io/get-pip.py
sudo -H python3.6 get-pip.py
```

```:シンボリックリンクを張り直す
$ sudo rm /usr/bin/python3
$ sudo ln -s /usr/bin/python3.6 /usr/bin/python3
$ sudo rm /usr/bin/python
$ sudo ln -s /usr/bin/python3 /usr/bin/python
```

```:pythonとpipのバージョンを確認
$ python -V
Python 3.6.3
$ pip -V
pip 9.0.1 from /usr/local/lib/python3.6/dist-packages (python 3.6)
```

## 1.2. AWS CLIのインストール

```:python3.6-devをインストール
$ sudo apt-get install python3-dev
$ sudo -H pip install awscli   
```
[未解決] awscliインストール時にPyYAMLのビルドに失敗のエラーメッセージが表示されるが何故かインストールは成功する、、、

## 1.3. AWS CLIの設定
AWSのIAMから KeyとSecretを取得しておきます
[参考] http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_access-keys.html

```:awscliの初期設定
$ aws configure
AWS Access Key ID [None]: [Your Key Here]
AWS Secret Access Key [None]: [Your Secret Here]
Default region name [None]: ap-northeast-1
Default output format [None]: json
```
ここでは東京リージョンを選択しています

```:接続確認
$ aws ec2 describe-instances
```
適当なコマンドを打ってエラーが出なければOKです

# 2. Serverlessをインストールしてプロジェクトを作成してデプロイ

```:serverlessのインストール
$ npm install serverless -g
$ sls -v
1.24.1
```
この時点でのバージョンは1.24.1

```:プロジェクトの作成
$ sls create -t aws-python3 -n my-misoca-serverless
```
my-misoca-serverlessという名前でプロジェクトを作成しました。
テンプレートとしてaws-python3を使っています。
コマンドを実行したディレクトリに serverless.yaml と handler.pyが作成されます。

serverless.yamlにデプロイ先のリージョンを指定します。
（以下のコードではテンプレートで作成されたものからコメントを削除しています）

```yaml:serverless.yaml
service: my-misoca-serverless

provider:
  name: aws
  runtime: python3.6

functions:
  hello:
    handler: handler.hello

```

デプロイする前にローカルで関数の実行を確認します

```:ローカルでhello関数を実行
$ sls invoke local -f hello
{
    "statusCode": 200,
    "body": "{\"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {}}"
}
```

デプロイします。

```:デプロイ
$ sls deploy -v
...
Service Information
service: my-misoca-serverless
stage: dev
region: ap-northeast-1
stack: my-misoca-serverless-dev
api keys:
  None
endpoints:
  None
functions:
  hello: my-misoca-serverless-dev-hello

Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:ap-northeast-1:535395497623:function:my-misoca-serverless-dev-hello:1
ServerlessDeploymentBucketName: my-misoca-serverless-dev-serverlessdeploymentbuck-uy0rkalebbwr
```
出力は省略していますが、Lambdaが作成されました。
![作成されたLambdaをコンソールで確認](https://qiita-image-store.s3.amazonaws.com/0/5557/753fe084-0283-8d3f-4c5a-39fa863a01e7.png)

Cloud Formationも確認。
![Cloud Formation](https://qiita-image-store.s3.amazonaws.com/0/5557/976b0e65-5c4a-5203-f4fd-e9d478066cd7.png)

リモートの関数を呼び出して確認します。

```:リモートでhello関数を実行
$ sls invoke -f hello -l                                                                                               
{
    "statusCode": 200,
    "body": "{\"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {}}"
}
--------------------------------------------------------------------
START RequestId: 6119c8f6-d1f2-11e7-b010-7b05d8617e25 Version: $LATEST
END RequestId: 6119c8f6-d1f2-11e7-b010-7b05d8617e25
REPORT RequestId: 6119c8f6-d1f2-11e7-b010-7b05d8617e25  Duration: 0.30 ms       Billed Duration: 100 ms         Memory Size: 1024 MB    Max Memory Used: 22 MB
```

handler.pyに定義されたhello関数が呼び出されています。
（以下のコードではテンプレートで作成されたものからコメントを削除しています）

```python:handler.py
import json


def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response
```

念のためCloud Watchでログを確認します。
![スクリーンショット 2017-11-26 0.10.58.png](https://qiita-image-store.s3.amazonaws.com/0/5557/fa960259-08ac-0399-6bb7-e10d8d6ed1e9.png)

デプロイするごとにログストリームが作成されます。
![スクリーンショット 2017-11-26 0.12.06.png](https://qiita-image-store.s3.amazonaws.com/0/5557/c45a833d-8d51-f617-b0fa-ade0392d7f50.png)

![スクリーンショット 2017-11-26 0.12.59.png](https://qiita-image-store.s3.amazonaws.com/0/5557/138a6d85-ec5e-3642-bc6e-34db96b6a106.png)

実行が確認できました。
ちなみに、pythonのprint関数などを使った出力はここに出力されますので、デバッグに重宝します。

# 3. Misoca APIを使う
Misoca : https://www.misoca.jp/

## 3.1. MisocaからAPIキーを取得
MisocaからOAuth2に使うAPIキーを取得するのですが、その前にOAuth2認証時に呼び出されるコールバック（内部は後で実装）をAPI GatewayとLambdaで作成します。

serverless.yaml内に記述を追加します。

```yaml:serverless.yamlの一部
functions:

  misoca_callback:
    handler: handler.misoca_callback
    events:
      - http:
          path: misoca_callback
          method: get
```

handler.pyに関数を作成します

```python:handler.pyの一部
def misoca_callback(event, context):
    print(event)
    
    response = {
        "statusCode": 200,
        "body": "misoca_callback"
    }

    return response
```

デプロイします。

```:デプロイ
$ sls deploy -v
...
Serverless: Stack update finished...
Service Information
service: my-misoca-serverless
stage: dev
region: ap-northeast-1
stack: my-misoca-serverless-dev
api keys:
  None
endpoints:
  GET - https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/misoca_callback
functions:
  hello: my-misoca-serverless-dev-hello
  misoca_callback: my-misoca-serverless-dev-misoca_callback

Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:ap-northeast-1:535395497623:function:my-misoca-serverless-dev-hello:2
MisocaUnderscorecallbackLambdaFunctionQualifiedArn: arn:aws:lambda:ap-northeast-1:535395497623:function:my-misoca-serverless-dev-misoca_callback:1
ServiceEndpoint: https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev
ServerlessDeploymentBucketName: my-misoca-serverless-dev-serverlessdeploymentbuck-uy0rkalebbwr
```

ここで、以下の部分のhttpsからがコールバック先のURLになるので記録しておきます。

```:エンドポイントのURL
endpoints:
  GET - https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/misoca_callback
```

Misocaにログインした状態で次のURL（新しいアプリケーションの登録）にアクセスします。
https://app.misoca.jp/oauth2/applications/new

![新しいアプリケーションの登録](https://qiita-image-store.s3.amazonaws.com/0/5557/a0c365fe-ef0c-fa72-d7ec-37937b13f016.png)
先程取得したURLとローカルテスト用のURIを Redirect URIに設定します。

![スクリーンショット 2017-11-26 0.33.35.png](https://qiita-image-store.s3.amazonaws.com/0/5557/2fcc8cb6-7f7b-126d-4f55-36ebff773dc7.png)

アプリケーションが作成されました。Application IDとSecretがAPIキーとして必要ですので記録しておきます。

## 3.2. serverless-python-requirements プラグインと必要な外部ライブラリのインストール

pythonでOAuth2クライアントを実装する（及びその他）ためにライブラリを使います。
Lambdaに用意されていないライブラリを使うためにserverless-python-requirementsというプラグインを利用します。

serverless-python-requirements:
https://github.com/UnitedIncome/serverless-python-requirements

次のコマンドでserverless-python-requirementsをインストールします。

```:serverless-python-requirementsのインストール
$ sls plugin install -n serverless-python-requirements
```

serverless.yamlに記述が追加され、package.jsonが作成されます。

```yaml:serverless.yamlの一部
plugins:
  - serverless-python-requirements
```

```json:package.json
{
  "name": "my-misoca-serverless",
  "description": "",
  "version": "0.1.0",
  "dependencies": {},
  "devDependencies": {
    "serverless-python-requirements": "^3.0.10"
  }
}
```

requirements.txtを作成してserverless-python-requirementsがインストールするライブラリを記述します。

```:requirements.txtの作成
$ touch requirements.txt
```

```:requirements.txt
requests
requests-oauthlib
boto3
```

同様のライブラリをCloud9上にもインストールしておきます。

```:pipでライブラリをインストール
$ sudo -H pip install requests requests-oauthlib boto3
```

## 3.3. OAuth2クライアントの実装（認証トークンの取得）

ライブラリ読み込みの宣言と定数の定義を先に記載しておきます。
後で説明しますが、OAuth2の認証情報はS3に保存しますのでバケットの名前を決めてます。

```python:handler.pyの一部
import json

from datetime import datetime
import tempfile

import urllib.parse
import boto3
import botocore

from requests_oauthlib import OAuth2Session


MISOCA_APPLICATION_ID='<Your Misoca Application ID>'
MISOCA_APP_SECRET_KEY='<Your Misoca Application Secrent>'
MISOCA_END_POINT='https://app.misoca.jp/api'
MISOCA_AUTHORIZE_URI='https://app.misoca.jp/oauth2/authorize'
MISOCA_TOKEN_URI='https://app.misoca.jp/oauth2/token'
MISOCA_REDIRECT_URI = 'https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/misoca_callback'

BUCKET_NAME = 'my-misoca-service-bucket'
FILE_NAME = 'credential.json'
```

handler.pyに認証を行う関数を作成します。
ローカル実行時はテスト用のリダイレクトURIを使用するようにしています。
ここでは認証用のURLを取得し、そのURLにリダイレクトさせています。
responseはAPI Gatewayが受け取るフォーマットに合わせています。
今回はscopeにreadを指定していますが書き込みが必要なAPIを使う場合はwriteを指定すればいいと思います（未確認）

```python:handler.pyの一部
def authorize(event, context):
    
    redirect_uri = MISOCA_REDIRECT_URI
    
    if type(context).__name__ == 'FakeLambdaContext':
        redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
    
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri=redirect_uri, scope='read')
    
    authorization_url, state = client.authorization_url(MISOCA_AUTHORIZE_URI)
    
    response = {
        'statusCode': 301,
        'headers': {
            "Location" : authorization_url
        },
        'body': None
    };
    
    return response
```
serverless.yamlにラムダ関数の記述を追加（後でAPI Gatewayのための記述を追加します）

```yaml:serverless.yamlの一部
functions:
  authorize:
    handler: handler.authorize
```

```:ローカル実行して確認
$ sls invoke local -f authorize -l
{
    "statusCode": 301,
    "headers": {
        "Location": "https://app.misoca.jp/oauth2/authorize?response_type=code&client_id=<your-client-id-here>&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=read&state=FtfNpTOGSlPGopnDSHnhQbp1WtUAyE"
    },
    "body": null
}
```

Location部分のURLをブラウザで開くとMisocaでの認証が表示されます。
![スクリーンショット 2017-11-26 1.31.07.png](https://qiita-image-store.s3.amazonaws.com/0/5557/faab1817-c8b2-caa1-cdce-6be2ca7f7bce.png)

認証すると認証コードが表示されます（このコードは一度しか使えません）
![スクリーンショット 2017-11-26 1.31.34.png](https://qiita-image-store.s3.amazonaws.com/0/5557/af6dd9e5-6515-b695-8a23-9c998f235601.png)


認証コードから認証トークンの取得を行います。
ここで作成する関数はローカルテストでのみ使用します。

```python:handler.pyの一部
def get_access_token(event, context):
    
    code = '28471cdfabce35d2cad93bdb8b26cdba40ac52b0803ddf35aed07984713ca591'
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri='urn:ietf:wg:oauth:2.0:oob', scope='read')
    dict = client.fetch_token(MISOCA_TOKEN_URI, code=code, client_secret=MISOCA_APP_SECRET_KEY)
    print(dict)
```

```yaml:serverless.yamlの一部
functions:
  get_access_token:
    handler: handler.get_access_token
```

```:ローカル実行して確認
$ sls invoke local -f get_access_token -l
{'access_token': '<access token>', 'token_type': 'bearer', 'expires_in': 7776000, 'refresh_token': '<refresh token>', 'scope': ['read'], 'created_at': 1511628336, 'expires_at': 1519404310.5428352}
```

アクセストークンとリフレッシュトークンが取得できれば成功です。

トークンをあとで利用するためにS3への保存を実装します。
ついでにS3からの読み出しも実装します。

```python:handler.pyの一部

def get_bucket(bucket):
    s3 = boto3.resource('s3') 
    b = s3.Bucket(bucket) 
    
    try:
        s3.meta.client.head_bucket(Bucket=bucket)
    except botocore.exceptions.ClientError as e:
        print ("The bucket doesn't exist. Creating...")
        b.create(CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-1' })
        
    return b
    
def save_token(dict):
    
    data = json.dumps(dict, sort_keys=True, indent=4)
    
    tmp = tempfile.NamedTemporaryFile()
    with open(tmp.name, 'w') as f:
        f.write(data)
        f.flush()
        bucket = get_bucket(BUCKET_NAME)
        res = bucket.upload_file(f.name, FILE_NAME)
        
def load_token():
    bucket = get_bucket(BUCKET_NAME)
    tmp_file = '/tmp/'+FILE_NAME
    try:
        bucket.download_file(FILE_NAME, tmp_file)
        data = json.load(open(tmp_file))
        return data
    except botocore.exceptions.ClientError as e:
        return None
```

LambdaからS3にバケットを作成したりファイルを読み書きするために IAM Role の許可を与えます。

```yaml:serverless.yamlの一部
provider:
  name: aws
  runtime: python3.6
  region: ap-northeast-1

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:CreateBucket"
        - "s3:ListBucket"
        - "s3:PutObject"
        - "s3:GetObject"
      Resource:
        - "arn:aws:s3:::my-misoca-serverless-bucket"
        - "arn:aws:s3:::my-misoca-serverless-bucket/*"
```

get_access_tokenを修正してバケットへのトークンの保存を確認します。
（コードは新しく取得する必要があります）

```python:handler.pyの一部
def get_access_token(event, context):
    
    code = '28471cdfabce35d2cad93bdb8b26cdba40ac52b0803ddf35aed07984713ca591'
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri='urn:ietf:wg:oauth:2.0:oob', scope='read')
    dict = client.fetch_token(MISOCA_TOKEN_URI, code=code, client_secret=MISOCA_APP_SECRET_KEY)
    save(dict)
```

AWSのコンソールでS3にトークンが保存されていることを確認します。
（ダウンロードして中身を確認）

![スクリーンショット 2017-11-26 2.14.12.png](https://qiita-image-store.s3.amazonaws.com/0/5557/3799e8cf-888b-e38e-bb5c-2e9c4250a0c6.png)

## 3.4. OAuth2クライアントの実装（コールバックの実装）

misoca_callback関数を実装します。

```python:handler.pyの一部
def misoca_callback(event, context):
    
    code = event['queryStringParameters']['code']
    
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri=MISOCA_REDIRECT_URI, scope='read')
    dict = client.fetch_token(MISOCA_TOKEN_URI, code=code, client_secret=MISOCA_APP_SECRET_KEY)
    
    save_token(dict)

    return {
      'statusCode': 200,
      'headers': {
      },
      'body': json.dumps(dict)
    };
```

authorizeのAPI Gateway記述を追加します。

```yaml:serverless.yamlの一部
functions:
  authorize:
    handler: handler.authorize
    events:
      - http:
          path: authorize
          method: get
    
```

デプロイします。
（\_\_pycache\_\_ が存在するとデプロイが失敗する場合は \_\_pycache\_\_ を削除する）

```:デプロイ
$ sls deploy -v
...
endpoints:
  GET - https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/misoca_callback
  GET - https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/authorize
...
```

authorizeのURLをブラウザで開いて認証する。
```
https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/authorize
```

リダイレクトされて認証トークンが表示されれば成功

## 3.5. Misoca APIを呼び出す

/v1/invoices を呼び出してみます。

```python:handler.pyの一部
def get_invoices(event, context):
    
    invoices = get('/v1/invoices')
    for invoice in invoices:
        print(invoice)
        
    return {
      'statusCode': 200,
      'headers': {
      },
      'body': json.dumps(invoices, sort_keys=True, indent=4)
    };
        
def get(function):
    
    client = get_client()
    if client != None:
        res = client.get(MISOCA_END_POINT+function)
        data = json.loads(res.content)
        return data
    else:
        return None

def get_client():
    extra = {
        'client_id': MISOCA_APPLICATION_ID,
        'client_secret': MISOCA_APP_SECRET_KEY,
    }

    def token_updater(token):
        save_token(token)

    token = load_token()
    if token != None:
        client = OAuth2Session(MISOCA_APPLICATION_ID,
                               token=token,
                               auto_refresh_kwargs=extra,
                               auto_refresh_url=MISOCA_TOKEN_URI,
                               token_updater=token_updater
                               )
        return client
    else:
        return None
```

```yaml:serverless.yamlの一部
functions:
  get_invoices:
    handler: handler.get_invoices
    events:
      - http:
          path: get_invoices
          method: get
```

デプロイしてURLを開くと請求書の一覧が取得できます。

```
https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/get_invoices
```

# まとめ
軽い気持ちで書き始めたらすっかり長くなってしまいましたが、せっかくなのでまとめてみました。

Serverlessは個人的に興味津々でどんどん使っていきたいのですが、Cloud Watchのログを見たり、ローカル実行したりを駆使することでデバッグしながら書いていくのですが、どこを見れば良いのかを知るまでが大変ですね。

一番ハマったのはLambdaでは成功するのにでAPI Gateway経由で実行すると"Internal server error"のみが表示されたときでした。これはreturnで返す連想配列がAPI Gatewayの形式に従ってなかったからなのですが、Cloud Watchのログではエラーメッセージが出ないため、見つけるのに苦労しました。最終的にはAPI Gatewayのテスト機能を使うことで解決しました。

最後に、serverless.yamlとhandler.pyの最終状態を記載しておきます。

```yaml:serverless.yaml
service: my-misoca-serverless

provider:
  name: aws
  runtime: python3.6
  region: ap-northeast-1
  
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:CreateBucket"
        - "s3:ListBucket"
        - "s3:PutObject"
        - "s3:GetObject"
      Resource:
        - "arn:aws:s3:::my-misoca-serverless-bucket"
        - "arn:aws:s3:::my-misoca-serverless-bucket/*"

functions:
  hello:
    handler: handler.hello
    
  misoca_callback:
    handler: handler.misoca_callback
    events:
      - http:
          path: misoca_callback
          method: get
          
  authorize:
    handler: handler.authorize
    events:
      - http:
          path: authorize
          method: get
          
  get_invoices:
    handler: handler.get_invoices
    events:
      - http:
          path: get_invoices
          method: get
    
  get_access_token:
    handler: handler.get_access_token

plugins:
  - serverless-python-requirements

```

```python:handler.py
import json

from datetime import datetime
import tempfile

import urllib.parse
import boto3
import botocore

from requests_oauthlib import OAuth2Session


MISOCA_APPLICATION_ID='<your misoca application id>'
MISOCA_APP_SECRET_KEY='<your misoca application secret>'
MISOCA_END_POINT='https://app.misoca.jp/api'
MISOCA_AUTHORIZE_URI='https://app.misoca.jp/oauth2/authorize'
MISOCA_TOKEN_URI='https://app.misoca.jp/oauth2/token'
MISOCA_REDIRECT_URI = 'https://bvu8tvkkva.execute-api.ap-northeast-1.amazonaws.com/dev/misoca_callback'

BUCKET_NAME = 'my-misoca-serverless-bucket'
FILE_NAME = 'credential.json'

def misoca_callback(event, context):
    
    code = event['queryStringParameters']['code']
    
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri=MISOCA_REDIRECT_URI, scope='read')
    dict = client.fetch_token(MISOCA_TOKEN_URI, code=code, client_secret=MISOCA_APP_SECRET_KEY)
    
    save_token(dict)

    return {
      'statusCode': 200,
      'headers': {
      },
      'body': json.dumps(dict)
    };


def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response
    
def get_access_token(event, context):
    
    code = '4eb83b39f75c9fbf5254e3a683e5472f6347a6c7e3dbbfa15b514a72376aee28'
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri='urn:ietf:wg:oauth:2.0:oob', scope='read')
    dict = client.fetch_token(MISOCA_TOKEN_URI, code=code, client_secret=MISOCA_APP_SECRET_KEY)
    save_token(dict)

def authorize(event, context):
    
    redirect_uri = MISOCA_REDIRECT_URI
    
    if type(context).__name__ == 'FakeLambdaContext':
        redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
        
    print(redirect_uri)
    
    client = OAuth2Session(MISOCA_APPLICATION_ID, redirect_uri=redirect_uri, scope='read')
    
    authorization_url, state = client.authorization_url(MISOCA_AUTHORIZE_URI)
    
    response = {
        'statusCode': 301,
        'headers': {
            "Location" : authorization_url
        },
        'body': None
    };
    
    return response

def get_invoices(event, context):
    
    invoices = get('/v1/invoices')
    for invoice in invoices:
        print(invoice)
        
    return {
      'statusCode': 200,
      'headers': {
      },
      'body': json.dumps(invoices, sort_keys=True, indent=4)
    };
        
def get(function):
    
    client = get_client()
    if client != None:
        res = client.get(MISOCA_END_POINT+function)
        data = json.loads(res.content)
        return data
    else:
        return None

def get_client():
    extra = {
        'client_id': MISOCA_APPLICATION_ID,
        'client_secret': MISOCA_APP_SECRET_KEY,
    }

    def token_updater(token):
        save_token(token)

    token = load_token()
    if token != None:
        client = OAuth2Session(MISOCA_APPLICATION_ID,
                               token=token,
                               auto_refresh_kwargs=extra,
                               auto_refresh_url=MISOCA_TOKEN_URI,
                               token_updater=token_updater
                               )
        return client
    else:
        return None

def get_bucket(bucket):
    s3 = boto3.resource('s3') 
    b = s3.Bucket(bucket) 
    
    try:
        s3.meta.client.head_bucket(Bucket=bucket)
    except botocore.exceptions.ClientError as e:
        print ("The bucket doesn't exist.")
        b.create(CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-1' })
        
    return b
    
def save_token(dict):
    
    data = json.dumps(dict, sort_keys=True, indent=4)
    
    tmp = tempfile.NamedTemporaryFile()
    with open(tmp.name, 'w') as f:
        f.write(data)
        f.flush()
        bucket = get_bucket(BUCKET_NAME)
        res = bucket.upload_file(f.name, FILE_NAME)
        
def load_token():
    bucket = get_bucket(BUCKET_NAME)
    tmp_file = '/tmp/'+FILE_NAME
    try:
        bucket.download_file(FILE_NAME, tmp_file)
        data = json.load(open(tmp_file))
        return data
    except botocore.exceptions.ClientError as e:
        return None
```