Edited at

AWS Lambda + API Gateway + PythonでEvernoteのRestful APIを実装する


はじめに

AWS LambdaとPythonを利用してノートテイキングアプリEvernoteのRestful APIを実装し、ほかのLambdaや外部システムから呼び出せるようにした「restfulapiforevernote」を作った際のメモ。

Evernoteにはもともと各種言語のサードパーティアプリに組み込んでEvernote APIにアクセス可能とするパッケージが公式で提供されているが、より簡単かつ汎用的に外部システムと連携ができる仕組みであるRestful APIは提供されていなかったため、Evernote APIを利用するシステムごとにパッケージを組み込む必要があった。

そこでEvernoteパッケージとノート検索、作成、更新などの主要な処理をAWS Lambda上でマイクロサービス化し、API GatewayでRestful APIとして開放して、ほかのシステムからRESTfulな接続だけでEvernote上のデータにアクセスできるようにした。

飽くまで内部向けで野良APIとして公開はしていないので、利用したい場合は構築手順に従って各自構築すること。コードがスパゲッティ、一部機能が未実装、エラー処理が甘い、検索のレスポンスがJSON形式ではない、など改善点は多々あるが、APIリファレンスも記載するのでそれに従えば動きはする。改善でき次第随時反映していく。


前提環境


構築手順


  • 変数初期化

$ app_name=restfulapiforevernote

$ workdir=${PWD}/$app_name
$ backet_name=${app_name}-$(echo -n $(aws sts get-caller-identity | jq -r .Account) | md5sum | cut -c 1-10)


  • 構築用プログラム一式を作成する。(githubにも上げてあるので以下のコマンドでクローンしてもOK)

$ git clone https://github.com/r-wakatsuki/${app_name}.git $workdir


  • プログラム一式のファイルパスは以下の通り。

restfulapiforevernote/

 ├ restfulapiforevernote-aws-function-00/
 │ └ lambda_function.py
 ├ restfulapiforevernote-aws-layer-00/
 │ └ Dockerfile
 └ restfulapiforevernote-aws-stack-00.yml


  • Dockerfile

evernote3のパッケージが含まれたLambda Layer用のzipを作成するためのDockerfile。

FROM python:3.7

WORKDIR /work

CMD apt update && \
apt install -y zip && \
mkdir python && \
pip install -t ./python evernote3 oauth2 && \
zip -r ./zipdir/layer.zip python


  • lambda_function.py

Lambdaに配置するコード。restfulapiforevernoteのバックエンドとして実際にEvernote APIへのアクセスを行う。


lambda_function.py

from base64 import b64decode

import json,re,os,boto3
from evernote.api.client import EvernoteClient
import evernote.edam.type.ttypes as Types
from evernote.edam.notestore.ttypes import NoteFilter, NotesMetadataResultSpec

def lambda_handler(event, context):

#共通処理 事前定義
def return200(res_body):
return{
'statusCode': 200,
'body': json.dumps(res_body)
}

def return400(message_str):
body = {
'errorMessage': message_str
}
return{
'statusCode': 400,
'body': json.dumps(body)
}

global noteobject

#共通処理 Evernote認証
access_token = boto3.client('kms').decrypt(CiphertextBlob=b64decode(os.environ['en_access_token']))['Plaintext'].decode()
client = EvernoteClient(token=access_token, sandbox=False)
try:
note_store = client.get_note_store()
except Exception as e:
return(return400(str(e)))

#<ノート検索・複数取得><ノート個別取得>向け処理
nhead_e1 = r'^<\?xml version="1.0" encoding="UTF-8"\?>(\n)?'
nhead_e2 = r'^<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">(\n)?'
nhead_e3 = r'^<en-note.*?>(\n)?'

#API:<ノート検索・複数取得>
if event['httpMethod'] == 'GET' and event['resource'] == '/notes':

#<ノート検索・複数取得>向け処理 リクエストからパラメータ値を取得してセット
req_query = event['queryStringParameters']
if req_query is None:
return(return400("パラメータが指定されていません"))
req_searchword = req_query.get('q','')
req_notebookguid = req_query.get('notebookguid','')
try:
req_searchtype = int(req_query.get('searchtype',0))
req_includecontent = int(req_query.get('includecontent',0))
req_maxnotescount = int(req_query.get('maxnotescount',1))
except:
return(return400('不正な型のパラメータ'))

filter = NoteFilter(notebookGuid = req_notebookguid)
resultSpec = NotesMetadataResultSpec(includeTitle=True,includeNotebookGuid=True)
metalist = note_store.findNotesMetadata(filter, 0, 250, resultSpec)
notelist = []

for meta in metalist.notes:
noteobject = ''
if req_searchtype == 0: #タイトル部分一致(既定)
if str(req_searchword) in meta.title:
if req_includecontent == 0: #コンテンツを含まない
noteobject = {
'noteguid' : meta.guid,
'title': meta.title,
'notebookguid' : meta.notebookGuid
}
if req_includecontent == 1: #コンテンツを含む
content = note_store.getNoteContent(access_token,meta.guid)
content = re.sub(nhead_e1, '', content)
content = re.sub(nhead_e2, '', content)
content = re.sub(nhead_e3, '', content)
content = re.sub(r'(\n)?</en-note>$', '', content)
noteobject = {
'noteguid' : meta.guid,
'title': meta.title,
'notebookguid' : meta.notebookGuid,
'content' : content,
}
if req_searchtype == 1: #タイトル完全一致
if req_searchword == meta.title:
if req_includecontent == 0: #コンテンツを含まない
noteobject = {
'noteguid' : meta.guid,
'title': meta.title,
'notebookguid' : meta.notebookGuid
}
if req_includecontent == 1: #コンテンツを含む
content = note_store.getNoteContent(access_token,meta.guid)
content = re.sub(nhead_e1, '', content)
content = re.sub(nhead_e2, '', content)
content = re.sub(nhead_e3, '', content)
content = re.sub(r'(\n)?</en-note>$', '', content)
noteobject = {
'noteguid' : meta.guid,
'title': meta.title,
'notebookguid' : meta.notebookGuid,
'content' : content
}
if req_searchtype == 2: #タイトル・本文部分一致
pass #作成中
if noteobject != '':
notelist.append(noteobject)
if len(notelist) == req_maxnotescount:
break

return(return200(notelist))

#API:<ノート個別取得>
if event['httpMethod'] == 'GET' and event['resource'] == '/note/{noteguid}':

#リクエストからパラメータ値を取得してセット
try:
req_noteguid = json.loads(event['pathParameters']).get('noteguid')
except:
req_noteguid = event['pathParameters'].get('noteguid')
if req_noteguid is None:
return(return400("{noteguid}パス未指定"))

n = note_store.getNote(access_token,req_noteguid,False,False,False,False)
content = note_store.getNoteContent(access_token,req_noteguid)
content = re.sub(nhead_e1, '', content)
content = re.sub(nhead_e2, '', content)
content = re.sub(nhead_e3, '', content)
content = re.sub(r'(\n)?</en-note>$', '', content)
noteobject = {
'noteguid' : n.guid,
'title': n.title,
'content' : content,
'notebookguid' : n.notebookGuid
}

return(return200(noteobject))

#<ノート作成><ノート更新><ノート削除>向け処理
n = Types.Note()
nhead_e = '<?xml version="1.0" encoding="UTF-8"?>'
nhead_e += '<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">'

#<ノート作成><ノート更新><ノート削除>向け処理 リクエストボディから値を取得してセット
try:
req_body_dict = json.loads(event['body'])
except:
return(return400('不正な形式のデータ'))
req_notecontent = req_body_dict.get('notecontent','')
req_resource_ary = req_body_dict.get('resource_ary',[])
req_notetitle = req_body_dict.get('notetitle')
req_notebookguid = req_body_dict.get('notebookguid','')
if req_notetitle == None:
return(return400('notetitleが未指定'))
else:
n.title = req_notetitle
if req_notebookguid != '':
n.notebookGuid = req_notebookguid
else:
pass

#API:<ノート作成>
if event['httpMethod'] == 'POST' and event['resource'] == '/note':
n.content = nhead_e + '<en-note>' + req_notecontent + '</en-note>'
if len(req_resource_ary) != 0:
resources = []
for index,item in enumerate(req_resource_ary):
data = Types.Data()
data.body = b64decode(item['databody'])
data.size = len(data.body)
data.bodyHash = item['bodyhash']
resource = Types.Resource()
resource.mime = item['mimetype']
resource.data = data
attr = Types.ResourceAttributes()
attr.fileName = 'img' + str([index-1]) + '.jpg'
resource.attributes = attr
resources.append(resource)
if len(resources) != 0:
n.resources = resources
n = note_store.createNote(n)
noteobject = {
'noteguid' : n.guid,
'title': n.title,
'notebookguid' : n.notebookGuid
}
return(return200(noteobject))

#<ノート更新><ノート削除>向け処理 リクエストからパラメータ値を取得してセット
try:
req_noteguid = json.loads(event['pathParameters']).get('noteguid')
except:
req_noteguid = event['pathParameters'].get('noteguid')
if req_noteguid is None:
return(return400("{noteguid}パスが未指定"))

#API:<ノート更新>
if event['httpMethod'] == 'PATCH' and event['resource'] == '/note/{noteguid}':
n.guid = req_noteguid
if req_notecontent != '':
n.content = nhead_e + '<en-note>' + req_notecontent + '</en-note>'
if len(req_resource_ary) != 0:
pass #作成中
n = note_store.updateNote(access_token,n)
noteobject = {
'noteguid' : n.guid,
'title': n.title,
'notebookguid' : n.notebookGuid
}
return(return200(noteobject))

#API:<ノート削除>
if event['httpMethod'] == 'DELETE':
pass #作成中

return(return400('不正なメソッドおよびリソースの指定 %s, %s' % (event['resource'],event['httpMethod'])))



  • restfulapiforevernote-aws-stack-00.yml

restfulapiforevernoteの構成要素となるLambda、Layer、API GatewayなどのAWSリソースをCloud Formationで作成するためのyamlテンプレート。


restfulapiforevernote-aws-stack-00.yml

Parameters:

appName:
Type: String
backetName:
Type: String
stageName:
Type: String
Default: dev
usagePlanQuotaLimit:
Type: Number
Default: 200
usagePlanQuotaPeriod:
Type: String
Default: MONTH
AllowedValues:
- DAY
- WEEK
- MONTH
usagePlanThrottleBurstLimit:
Type: Number
Default: 10
usagePlanThrottleRateLimit:
Type: Number
Default: 5
Resources:
Role00:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${appName}-aws-role-00
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Policy00:
Type: AWS::IAM::Policy
DependsOn:
- Role00
Properties:
PolicyName: !Sub ${appName}-aws-policy-00
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "*"
Roles:
- !Ref Role00
Key00:
Type: AWS::KMS::Key
DependsOn:
- Role00
Properties:
KeyPolicy:
Version: "2012-10-17"
Id: !Sub ${appName}-aws-apikey-00-keypolicy
Statement:
-
Sid: "Allow administration of the key"
Effect: "Allow"
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action: "*"
Resource: "*"
-
Sid: "Allow use of the key"
Effect: "Allow"
Principal:
AWS: !GetAtt Role00.Arn
Action:
- "kms:Encrypt"
- "kms:Decrypt"
- "kms:ReEncrypt*"
- "kms:GenerateDataKey*"
- "kms:DescribeKey"
Resource: "*"
Layer00:
Type: AWS::Lambda::LayerVersion
Properties:
LayerName: !Sub ${appName}-aws-layer-00
CompatibleRuntimes:
- python3.7
Content:
S3Bucket: !Ref backetName
S3Key: layer.zip
Function00:
Type: AWS::Lambda::Function
DependsOn:
- Key00
- Layer00
Properties:
FunctionName: !Sub ${appName}-aws-function-00
Code:
S3Bucket: !Ref backetName
S3Key: function.zip
Handler: lambda_function.lambda_handler
MemorySize: 512
Timeout: 60
Role: !GetAtt Role00.Arn
Runtime: python3.7
Layers:
- !Ref Layer00
Environment:
Variables:
en_access_token: DummyAccessToken
KmsKeyArn: !GetAtt Key00.Arn
Api00:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub ${appName}-aws-api-00
Resource00:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref Api00
ParentId: !GetAtt Api00.RootResourceId
PathPart: note
Resource01:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref Api00
ParentId: !GetAtt Api00.RootResourceId
PathPart: notes
Resource02:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref Api00
ParentId: !Ref Resource00
PathPart: '{noteguid}'
Method00:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: true
RestApiId: !Ref Api00
ResourceId: !Ref Resource00
HttpMethod: ANY
AuthorizationType: NONE
MethodResponses: []
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt Function00.Arn
- /invocations
IntegrationResponses: []
PassthroughBehavior: NEVER
Method01:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: true
RestApiId: !Ref Api00
ResourceId: !Ref Resource01
HttpMethod: ANY
AuthorizationType: NONE
MethodResponses: []
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt Function00.Arn
- /invocations
IntegrationResponses: []
PassthroughBehavior: NEVER
Method02:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: true
RestApiId: !Ref Api00
ResourceId: !Ref Resource02
HttpMethod: ANY
AuthorizationType: NONE
MethodResponses: []
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt Function00.Arn
- /invocations
IntegrationResponses: []
PassthroughBehavior: NEVER
LambdaPermission00:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName: !Ref Function00
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
SourceArn: !Join
- ''
- - 'arn:aws:execute-api:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref Api00
- /*/ANY/note
LambdaPermission01:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref Function00
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
SourceArn: !Join
- ''
- - 'arn:aws:execute-api:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref Api00
- /*/ANY/notes
LambdaPermission02:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref Function00
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
SourceArn: !Join
- ''
- - 'arn:aws:execute-api:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref Api00
- /*/ANY/note/*
Deployment00:
Type: AWS::ApiGateway::Deployment
DependsOn:
- Method00
- Method01
- Method02
Properties:
RestApiId: !Ref Api00
StageName: !Ref stageName
ApiKey00:
Type: AWS::ApiGateway::ApiKey
DependsOn:
- Deployment00
Properties:
Name: !Sub ${appName}-aws-apikey-00
Enabled: true
StageKeys:
- RestApiId: !Ref Api00
StageName: !Ref stageName
UsagePlan00:
Type: AWS::ApiGateway::UsagePlan
DependsOn:
- ApiKey00
Properties:
UsagePlanName: !Sub ${appName}-aws-usageplan-00
ApiStages:
- ApiId: !Ref Api00
Stage: !Ref stageName
Quota:
Limit: !Ref usagePlanQuotaLimit
Period: !Ref usagePlanQuotaPeriod
Throttle:
BurstLimit: !Ref usagePlanThrottleBurstLimit
RateLimit: !Ref usagePlanThrottleRateLimit
UsagePlanKey00:
Type: AWS::ApiGateway::UsagePlanKey
DependsOn:
- UsagePlan00
Properties:
KeyId: !Ref ApiKey00
KeyType: API_KEY
UsagePlanId: !Ref UsagePlan00


  • Dockerを利用してLambda Layer用のzipを作成する。

$ cd $workdir

$ docker build -t layer_image ${workdir}/${app_name}-aws-layer-00
$ docker run -v ${workdir}:/work/zipdir layer_image


  • Lambda関数コード用のzipを作成して、Layer用のzipと合わせてS3にアップロードする。

$ zip -j ${workdir}/function.zip ${workdir}/${app_name}-aws-function-00/*

$ aws s3 mb s3://$backet_name
$ aws s3 mv ${workdir}/function.zip s3://${backet_name}/function.zip
$ aws s3 mv ${workdir}/layer.zip s3://${backet_name}/layer.zip


  • CloudFormationでAWSにデプロイ。

$ aws cloudformation create-stack --stack-name ${app_name}-aws-stack-00 \

--template-body file://${workdir}/${app_name}-aws-stack-00.yml \
--capabilities CAPABILITY_NAMED_IAM \
--parameters ParameterKey=appName,ParameterValue=$app_name \
ParameterKey=backetName,ParameterValue=$backet_name


  • アクセストークン取得

EvernoteAPIを利用するためのサードパーティ用アクセストークンをOAuth認証で取得する をもとにEvernoteのサードパーティ用アクセストークンを取得する。


  • アクセストークン設定

ブラウザからAWS CloudFormationのコンソールを開く。

スタック一覧から「restfulapiforevernote-aws-stack-00」を選択し、リソース一覧から「restfulapiforevernote-aws-function-00」をクリック。

image.png

Lambda関数「restfulapiforevernote-aws-function-00」のページが開くので、[環境変数]でキー「en_access_token」の値に先程取得したアクセストークンを入力。

image.png

[暗号化の設定]で「伝送中の暗号化のためのヘルパーの有効化」にチェックを入れ、「保管時に暗号化する AWS KMS キー」と同じAWS KMSキーを選択し、「en_access_token」の[暗号化]をクリック。

image.png

アクセストークンが画面上で暗号化されたら[保存]をクリック。

image.png


API リファレンス

Restful API「restfulapiforevernote」の利用方法を記載する。


ホスト

ブラウザからAPI Gatewayのコンソールを開く。

[restfulapiforevernote-aws-api-00] -> [ステージ] -> [dev] -> [devステージエディター]よりAPIのURLを確認する。

このURLがAPI Gateway経由でRESTでアクセスする際のホストとなる。

image.png


認証


API Gateway経由でリクエストする場合

ブラウザからAPI Gatewayのコンソールを開く。

[APIキー] -> [restfulapiforevernote-aws-apikey-00]より[表示]をクリックしてAPIキーを確認する。

このAPIキーをAPI Gateway経由でRESTでアクセスする際にリクエストヘッダのx-api-keyに指定する。

image.png


boto3でリクエストする場合

ほかのLambda関数よりboto3でリクエストする場合は、Lambdaの実行ロールに最低でも以下の権限を持つポリシーをアタッチする。


Statement.json

{

"Action": [
"lambda:InvokeFunction"
],
"Resource": "*",
"Effect": "Allow"
}


各種API


ノート検索・複数取得

GET /notes


リクエストパラメータ

パラメータ
内容

必須
デフォルト値

q
ノート検索キーワード
String
no
キーワードによるフィルターなし

notebookguid
ノートを検索するノートブックのGUID
String
no

searchtype
ノートの検索タイプ(0:タイトル部分一致/1:タイトル完全一致/2:タイトル・本文部分一致)
Integer(0/1/2)
no
0

includecontent
レスポンスにノートのコンテンツを含めるかどうか(0:含めない/1:含める)
Integer(0/1)
no
0

maxnotescount
レスポンスするノートの最大数
Integer
no
1


利用例1 指定のノートブックからタイトルで検索


cURLからリクエストする場合

curl -H 'x-api-key: <APIキー>' \

"<ホスト>/notes?q=notetitle1&notebookguid=xxxxxxxx-9780-4e32-a99f-9e97cc98d299&searchtype=1&includecontent=1"


ほかのLambda関数(Python)からリクエストする場合

import boto3,json

def lambda_handler(event, context):

input_event = {
"httpMethod": "GET",
"resource": "/notes",
"queryStringParameters": {
"q": "notetitle1",
"notebookguid": "<ノートブックGUID>",
"searchtype": 1,
"includecontent": 1
}
}
Payload = json.dumps(input_event)

response = boto3.client('lambda').invoke(
FunctionName='restfulever-aws-function-00',
InvocationType='RequestResponse',
Payload=Payload
)

data = json.loads(json.loads(response['Payload'].read())['body'])
print(json.dumps(data, indent=2))


レスポンス例

[

{
"noteguid": "xxxxxxxx-5650-407e-a075-f7299a2d425a",
"title": "notetitle1",
"notebookguid": "xxxxxxxx-9780-4e32-a99f-9e97cc98d299",
"content": "<div>ノートの本文ですよ</div>"
}
]


利用例2 最新のノートを3つ取得


cURLからリクエストする場合

curl -H 'x-api-key: <APIキー>' \

"<ホスト>/notes?maxnotescount=3"


ほかのLambda関数(Python)からリクエストする場合

import boto3,json

def lambda_handler(event, context):

input_event = {
"httpMethod": "GET",
"resource": "/notes",
"queryStringParameters": {
"maxnotescount": 3
}
}
Payload = json.dumps(input_event)

response = boto3.client('lambda').invoke(
FunctionName='restfulever-aws-function-00',
InvocationType='RequestResponse',
Payload=Payload
)

data = json.loads(json.loads(response['Payload'].read())['body'])
print(json.dumps(data, indent=2))


レスポンス例

[

{
"noteguid": "xxxxxxxx-5650-407e-a075-f7299a2d425a",
"title": "notetitle1",
"notebookguid": "xxxxxxxx-9780-4e32-a99f-9e97cc98d299"
},
{
"noteguid": "xxxxxxxx-0d68-422f-8846-48174f1f6611",
"title": "FW: 配達完了:ご注文商品の配達が完了しました。",
"notebookguid": "xxxxxxxx-1b01-439b-994b-9b42640ad29a"
},
{
"noteguid": "xxxxxxxx-cef7-4028-a0f8-bdec52448db6",
"title": "2019/08/12 ラジオ_視聴",
"notebookguid": "xxxxxxxx-4d79-472d-8ba5-e7db3e354ac9"
}
]


ノート個別取得

GET /note/:noteguid


利用例


cURLからリクエストする場合

curl -H 'x-api-key: <APIキー>' \

"<ホスト>/note/xxxxxxxx-5650-407e-a075-f7299a2d425a"


ほかのLambda関数(Python)からリクエストする場合

import boto3,json

def lambda_handler(event, context):

pathParameters = {
"noteguid": "xxxxxxxx-5650-407e-a075-f7299a2d425a"
}

input_event = {
"httpMethod": "GET",
"resource": "/note/{noteguid}",
"pathParameters": json.dumps(pathParameters)
}
Payload = json.dumps(input_event)

response = boto3.client('lambda').invoke(
FunctionName='restfulever-aws-function-00',
InvocationType='RequestResponse',
Payload=Payload
)

data = json.loads(json.loads(response['Payload'].read())['body'])
print(json.dumps(data, indent=2))


レスポンス例

{

"noteguid": "xxxxxxxx-5650-407e-a075-f7299a2d425a",
"title": "notetitle1",
"notebookguid": "xxxxxxxx-9780-4e32-a99f-9e97cc98d299",
"content": "<div>ノートの本文ですよ</div>"
}


ノート作成

POST /note


データ

JSONキー
内容

必須
デフォルト値

notetitle
ノートタイトル
String
yes

notecontent
ノート本文
String
no

notebookguid
作成先ノートブックGUID
String
no
既定のノートブック

resource_ary
添付ファイル情報の配列(詳細は「ほかのLambda関数(Python)からリクエストする場合」を参照)
Array
no


利用例


cURLからリクエストする場合


添付なしのノートを作成する場合.sh

curl -H 'x-api-key: <APIキー>' \

-d '{"notetitle":"notetitle2","notecontent":"本文ですよ。","notebookguid":"xxxxxxxx-9780-4e32-a99f-bc9737c0afc6"}' \
"<ホスト>/note"


ほかのLambda関数(Python)からリクエストする場合


画像を添付して本文中に表示するノートを作成する場合.py

import json,hashlib,base64,boto3

import urllib.request

def lambda_handler(event, context):

resources = []
hexhash = ''
imgurl_ary = [
'https://www.contoso.com/image1.png',
'https://www.contoso.com/image2.png'
]

for item in imgurl_ary:
bodybinary = urllib.request.urlopen(item).read()
bodyhash = hashlib.md5(bodybinary).hexdigest()
resource = {
'databody' : str(base64.b64encode(bodybinary).decode()),
'bodyhash' : bodyhash,
'mimetype' : 'image/png'
}
hexhash += '<br /><en-media type="%s" hash="%s" />' % ('image/png',bodyhash)
resources.append(resource)

body = {
'notetitle' : 'notetitle2',
'notecontent' : '画像を添付して本文に表示してみた。' + '<br />' + hexhash,
'resource_ary' : resources
}

input_event = {
"httpMethod": "POST",
"resource": "/note",
"body": json.dumps(body)
}

response = boto3.client('lambda').invoke(
FunctionName = 'restfulever-aws-function-00',
InvocationType = 'RequestResponse',
Payload = json.dumps(input_event)
)

data = json.loads(json.loads(response['Payload'].read())['body'])
print(json.dumps(data, indent=2))



レスポンス例

{

"noteguid": "xxxxxxxx-456f-4d17-b6f9-9e97cc98d299",
"title": "notetitle2",
"notebookguid": "xxxxxxxx-9780-4e32-a99f-bc9737c0afc6"
}


「ほかのLambda関数(Python)からリクエストする場合」で作成したノート例


ノート更新

PATCH /note/:noteguid


データ

JSONキー
内容

必須
デフォルト値

notetitle
ノートタイトル
String
yes

notecontent
ノート本文
String
no
キーを指定しない場合はノート本文の更新なし

notebookguid
作成先ノートブックGUID
String
no
キーを指定しない場合はノートブックの移動なし


利用例


cURLからリクエストする場合

curl -X PATCH -H 'x-api-key: <APIキー>' \

-d '{"notetitle":"notetitle3","notecontent":"本文を更新してみる","notebookguid":"xxxxxxxx-1b01-439b-994b-a48174f1f6611"}' \
"<ホスト>/note/xxxxxxxx-5650-407e-a075-9b42640ad29"


ほかのLambda関数(Python)からリクエストする場合

import json,boto3

def lambda_handler(event, context):

body = {
"notebookguid": "xxxxxxxx-1b01-439b-994b-a48174f1f6611",
"notetitle": "notetitle3",
"notecontent": "本文を更新してみる"
}

pathParameters = {
"noteguid": "xxxxxxxx-5650-407e-a075-9b42640ad29"
}

input_event = {
"httpMethod": "PATCH",
"resource": "/note/{noteguid}",
"pathParameters": json.dumps(pathParameters),
"body": json.dumps(body)
}
Payload = json.dumps(input_event)

response = boto3.client('lambda').invoke(
FunctionName='restfulever-aws-function-00',
InvocationType='RequestResponse',
Payload=Payload
)

data = json.loads(json.loads(response['Payload'].read())['body'])
print(json.dumps(data, indent=2))


レスポンス例

{

"noteguid": "xxxxxxxx-5650-407e-a075-9b42640ad29",
"title": "notetitle3",
"notebookguid": "xxxxxxxx-1b01-439b-994b-a48174f1f6611"
}


ノート削除

未実装


参考

http://dev.evernote.com/doc/reference/

http://wararyo.blogspot.com/2015/07/evernote-python.html

https://qiita.com/r-wakatsuki/items/4076e3b8032d06f85aea

以上