Sans serveur en combinant Amazon API Gateway et AWS Lambda J'écrirai sur la réalisation de l'architecture. En Python.
API Gateway
API Gateway est un service qui agit comme un wrapper pour fournir l'API Web Restful. Il semble qu'il soit également combiné avec CloudFront, ou mis en œuvre en tant que wrapper pour celui-ci. CloudFront a l'image du CDN, mais comme AWS WAF est également combiné avec CloudFront, il se positionne comme un service réseau de couche 7, non limité au CDN. Cela peut avoir été. Avec CDN seul, il y a de nombreuses compétitions et, dans un sens, il semble qu'il deviendra une marchandise.
API Gateway Comme c'est un gros problème, j'ai créé une API à partir de Python. Modifiez la région, la fonction et le rôle au début selon vos besoins.
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 Comment déployer Lambda est hors de question, donc juste le 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)
Si vous exécutez createapi.py ci-dessus, vous verrez deux URL comme celle-ci (s'il n'y a pas de bogues).
createapi.Résultat d'exécution de py
OK : https://xxxxx.execute-api.somewhere.amazonaws.com/snapshot/hoge
NG : https://xxxxx.execute-api.somewhere.amazonaws.com/snapshot/fuga
Lorsque vous accédez à la personne OK
OK
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/hoge
{"message": "hogehoge"}
Si vous êtes une personne NG, vous vous fâcherez.
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
Vous devez spécifier l'URI du backend Lambda. C'est compliqué, mais il semble avoir une spécification fixe, qui est décidée par la région et l'ARN de la fonction Lambda. Si vous omettez les dernières "invocations", il renverra "500 erreur de serveur interne". Un point d'ajustement sobre.
Comment créer un URI
uri = 'arn:aws:apigateway:' + region + ':lambda:path/2015-03-31/functions/' + function + '/invocations'
Faites une demande (json) pour Lambda à l'aide du modèle Velocity. Voir Référence (http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) pour les variables disponibles. Bien qu'il soit codé en dur dans l'exemple, je pense qu'il est préférable de sortir en tant que fichier modèle en fonctionnement réel.
request_template.vm
{
"pathParams": {
#foreach ($key in $input.params().path.keySet())
"$key": "$util.escapeJavaScript($input.params().path.get($key))"#if ($foreach.hasNext),#end
#end
}
}
Dans le cas de Python, les données converties au format ci-dessus sont incluses dans event en tant que dict.
Obtenir la demande
hoge = event['pathParams']['hoge']
Utilisez return ou rise pour renvoyer une réponse de Lambda à API Gateway.
Réussite
return {'message': 'hogehoge'}
Terminaison anormale
raise NotHogeError(hoge)
Comme expliqué ci-après, si vous le retournez avec retour, il sera retourné au client avec le code de statut 200. (Pour être exact, c'est le code de statut par défaut ...)
Integration Response Convertit la réponse de Lambda en réponse client.
Si vous ne spécifiez pas de modèle (définissez-le sur «»), la réponse de lambda sera renvoyée telle quelle. Dans ce cas, définissez le modèle de réponse sur Empty dans Method Response.
Il est facile de renvoyer le code d'état avec 200, mais il est également nécessaire de le renvoyer avec une erreur client (série 400), une erreur serveur (série 500) ou une redirection (série 300). Pour cela, il est nécessaire d'associer à une expression régulière le type de réponse et le code d'état à utiliser.
Réécrivons maintenant l'exemple Lambda.
Quand j'essaye de revenir
else:
#raise NotHogeError(hoge)
return '[400:BadRequest] {0} is not hoge'.format(hoge)
Reviendra à 200
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/fuga -w '%{http_code}\n'
"[400:BadRequest] fuga is not hoge"200
Le code d'état est devenu 200. .. .. En fait, vous ne regardez pas la valeur de retour du gestionnaire Lambda.
C'est aussi
return {'message' : '[400:BadRequest] {0} is not hoge'.format(hoge)}
Ce n'est pas bien
return {'errorMessage' : '[400:BadRequest] {0} is not hoge'.format(hoge)}
C'est ce à quoi je suis arrivé de différentes manières.
exception.py
class NotHogeError(Exception):
def __init__(self, hoge):
self.hoge = hoge
def __str__(self):
"""ici!"""
return '[400:BadRequest] {0} is not hoge'.format(self.hoge)
Ainsi, il semble que vous devriez lever une exception lorsque vous souhaitez modifier le code d'état.
Dans le cas d'un système normal, la réponse de Lambda doit être renvoyée par pass-through, mais si elle est renvoyée en XML ou HTML au lieu de json, elle doit être convertie. De plus, dans le cas d'un système anormal, s'il s'agit d'un pass-through, StackTrace sera également renvoyé et ce sera pitoyable, donc seul le message d'erreur doit être renvoyé.
À l'origine, un modèle de réponse appelé Erreur est défini, créez donc un modèle en fonction de celui-ci.
response_template.vm
#set($data = $input.path('$'))
{
"message" : "${data.errorMessage}"
}
Je pensais qu'API Gateway n'était pas si simple, mais j'avais envie d'y penser si je voulais concevoir correctement une API en premier lieu.
Recommended Posts