Serverlos durch Kombination von Amazon API Gateway und AWS Lambda Ich werde über die Realisierung der Architektur schreiben. In Python.
API Gateway
API Gateway ist ein Dienst, der als Wrapper für die Bereitstellung der Restful Web API fungiert. Es scheint, dass es auch mit CloudFront kombiniert oder als Wrapper dafür implementiert ist. CloudFront hat das Image von CDN, aber da AWS WAF auch mit CloudFront kombiniert wird, wird es als Layer 7-Netzwerkdienst positioniert, nicht nur für CDN. Es kann gewesen sein. Allein mit CDN gibt es viele Wettbewerbe, und in gewissem Sinne scheint es, dass es eine Ware werden wird.
API Gateway Da es eine große Sache ist, habe ich eine API aus Python erstellt. Ändern Sie die Region, Funktion und Rolle am Anfang entsprechend.
createapi.py
# -*- coding: utf-8 -*-
import boto3
client = boto3.client('apigateway')
region = 'ap-northeast-1'
function = 'arn:aws:lambda:ap-northeast-1:AWS_ACCOUNT_ID:function:YOUR_LAMBDA_FUNCTION'
role = 'arn:aws:iam::AWS_ACCOUNT_ID:role/YOUR_IAM_ROLE_FOR_INVOCATION'
def create_api():
rest_api = client.create_rest_api(
name='sample01',
description='sample api',
)
return rest_api['id']
def create_resource(rest_api_id):
for resource in client.get_resources(
restApiId=rest_api_id
)['items']:
if resource['path'] == '/':
path_root_id = resource['id']
new_resource = client.create_resource(
restApiId=rest_api_id,
parentId=path_root_id,
pathPart='{hoge}',
)
return new_resource['id']
def setup_method(rest_api_id, resource_id):
client.put_method(
restApiId=rest_api_id,
resourceId=resource_id,
httpMethod='GET',
authorizationType='NONE',
)
uri = 'arn:aws:apigateway:' + region + ':lambda:path/2015-03-31/functions/' + function + '/invocations'
client.put_integration(
restApiId=rest_api_id,
resourceId=resource_id,
httpMethod='GET',
type='AWS',
integrationHttpMethod='POST',
uri=uri,
credentials=role,
requestTemplates={
'application/json': get_request_template()
},
)
client.put_integration_response(
restApiId=rest_api_id,
resourceId=resource_id,
httpMethod='GET',
statusCode='200',
responseTemplates={
'application/json': '',
},
)
client.put_method_response(
restApiId=rest_api_id,
resourceId=resource_id,
httpMethod='GET',
statusCode='200',
responseModels={
'application/json': 'Empty',
},
)
client.put_integration_response(
restApiId=rest_api_id,
resourceId=resource_id,
httpMethod='GET',
statusCode='400',
selectionPattern='^\[400:.*',
responseTemplates={
'application/json': get_response_template(),
},
)
client.put_method_response(
restApiId=rest_api_id,
resourceId=resource_id,
httpMethod='GET',
statusCode='400',
responseModels={
'application/json': 'Error',
},
)
def get_request_template():
"""
ref. http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
"""
return """
{
"pathParams": {
#foreach ($key in $input.params().path.keySet())
"$key": "$util.escapeJavaScript($input.params().path.get($key))"#if ($foreach.hasNext),#end
#end
}
}
"""
def get_response_template():
"""
ref. http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
"""
return """
#set($data = $input.path('$'))
{
"message" : "${data.errorMessage}"
}
"""
def deploy(rest_api_id):
client.create_deployment(
restApiId=rest_api_id,
stageName='snapshot',
stageDescription='snapshot stage',
)
client.update_stage(
restApiId=rest_api_id,
stageName='snapshot',
patchOperations=[
{
'op': 'replace',
'path': '/*/*/logging/loglevel',
'value': 'INFO',
},
],
)
if __name__ == '__main__':
rest_api_id = create_api()
resource_id = create_resource(rest_api_id)
setup_method(rest_api_id, resource_id)
deploy(rest_api_id)
api_url = 'https://' + rest_api_id + '.execute-api.' + region + '.amazonaws.com/snapshot/'
print 'OK : {0}'.format(api_url + 'hoge')
print 'NG : {0}'.format(api_url + 'fuga')
Lambda Wie Lambda bereitgestellt wird, kommt nicht in Frage, also nur der Code.
lambdemo.py
# -*- coding: utf-8 -*-
def lambda_handler(event, context):
hoge = event['pathParams']['hoge']
if hoge == 'hoge':
return {'message': 'hogehoge'}
else:
raise NotHogeError(hoge)
class NotHogeError(Exception):
def __init__(self, hoge):
self.hoge = hoge
def __str__(self):
return '[400:BadRequest] {0} is not hoge'.format(self.hoge)
Wenn Sie die Datei createapi.py oben ausführen, werden zwei URLs wie diese angezeigt (sofern keine Fehler vorliegen).
createapi.Ausführungsergebnis von py
OK : https://xxxxx.execute-api.somewhere.amazonaws.com/snapshot/hoge
NG : https://xxxxx.execute-api.somewhere.amazonaws.com/snapshot/fuga
Wenn Sie auf die OK-Person zugreifen
OK
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/hoge
{"message": "hogehoge"}
Wenn Sie eine NG-Person sind, werden Sie wütend.
NG
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/fuga -w '%{http_code}\n'
{
"message" : "[400:BadRequest] fuga is not hoge"
}
400
Integration
Sie müssen den URI des Backend-Lambda angeben. Es ist kompliziert, aber es scheint eine feste Spezifikation zu haben, die von der Region und der Lambda-Funktion ARN festgelegt wird. Wenn Sie die letzten "Aufrufe" weglassen, wird "500 interner Serverfehler" zurückgegeben. Ein nüchterner Anpassungspunkt.
So erstellen Sie eine URI
uri = 'arn:aws:apigateway:' + region + ':lambda:path/2015-03-31/functions/' + function + '/invocations'
Stellen Sie mithilfe der Velocity-Vorlage eine Anfrage (json) für Lambda. Die verfügbaren Variablen finden Sie unter Referenz (http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html). Obwohl es im Beispiel fest codiert ist, denke ich, dass es besser ist, im tatsächlichen Betrieb als Vorlagendatei auszugehen.
request_template.vm
{
"pathParams": {
#foreach ($key in $input.params().path.keySet())
"$key": "$util.escapeJavaScript($input.params().path.get($key))"#if ($foreach.hasNext),#end
#end
}
}
Im Fall von Python werden die im obigen Format konvertierten Daten als Diktat in event aufgenommen.
Anfrage erhalten
hoge = event['pathParams']['hoge']
Verwenden Sie return oder raise, um eine Antwort von Lambda an API Gateway zurückzugeben.
Erfolgreiche Fertigstellung
return {'message': 'hogehoge'}
Abnormale Beendigung
raise NotHogeError(hoge)
Wie im Folgenden erläutert, wird es mit dem Statuscode 200 an den Client zurückgegeben, wenn Sie es mit return zurückgeben. (Um genau zu sein, ist es der Standardstatuscode ...)
Integration Response Konvertiert die Antwort von Lambda in eine Client-Antwort.
Wenn Sie keine Vorlage angeben (auf '' setzen), wird die Antwort von Lambda unverändert zurückgegeben. Setzen Sie in diesem Fall das Antwortmodell in Method Response auf Empty.
Es ist einfach, den Statuscode mit 200 zurückzugeben, aber es ist auch erforderlich, ihn mit einem Clientfehler (Serie 400), einem Serverfehler (Serie 500) oder einer Umleitung (Serie 300) zurückzugeben. Zu diesem Zweck muss einem regulären Ausdruck zugeordnet werden, welche Art von Antwort und welcher Statuscode verwendet werden soll.
Schreiben wir nun das Beispiel Lambda neu.
Wenn ich versuche zurückzukehren
else:
#raise NotHogeError(hoge)
return '[400:BadRequest] {0} is not hoge'.format(hoge)
Kommt um 200 zurück
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/fuga -w '%{http_code}\n'
"[400:BadRequest] fuga is not hoge"200
Der Statuscode ist 200 geworden. .. .. Tatsächlich sehen Sie nicht den Rückgabewert des Lambda-Handlers.
Das ist auch
return {'message' : '[400:BadRequest] {0} is not hoge'.format(hoge)}
Das ist nicht gut
return {'errorMessage' : '[400:BadRequest] {0} is not hoge'.format(hoge)}
Darauf bin ich auf verschiedene Weise gekommen.
exception.py
class NotHogeError(Exception):
def __init__(self, hoge):
self.hoge = hoge
def __str__(self):
"""Hier!"""
return '[400:BadRequest] {0} is not hoge'.format(self.hoge)
Es scheint also, dass Sie eine Ausnahme auslösen sollten, wenn Sie den Statuscode ändern möchten.
Bei einem normalen System sollte die Antwort von Lambda per Pass-Through zurückgegeben werden. Wenn sie jedoch von XML oder HTML anstelle von json zurückgegeben wird, muss sie konvertiert werden. Im Falle eines abnormalen Systems wird StackTrace ebenfalls zurückgegeben und ist erbärmlich, sodass nur die Fehlermeldung zurückgegeben werden sollte.
Ursprünglich ist ein Antwortmodell mit dem Namen "Fehler" definiert. Erstellen Sie daher eine entsprechende Vorlage.
response_template.vm
#set($data = $input.path('$'))
{
"message" : "${data.errorMessage}"
}
Ich dachte, dass API Gateway nicht so einfach ist, aber ich wollte darüber nachdenken, ob ich eine API überhaupt richtig entwerfen wollte.