AWS Chalice - Tutorial 기초 학습 정리
목적
이 문서는 AWS Chalice Github README 문서를 학습하며 개인적인 학습정리를 위하여 작성되었다.
문서의 내용을 따라한 내용들을 정리한 내용이 주를 이루며 추후 학습이 더 필요한 부분은 그린으로 표시하였다.
테스트 환경
-
OS : macOS Mojave 10.14.6
-
Chalice 1.11.0
-
Python 3.6.5
-
darwin 18.7.0
학습에 필요한 사전 정보
AWS Lambda
다음과 같은 문서의 내용의 숙지가 선행되어야 한다.
-
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/with-userapp.html
-
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/getting-started-create-function.html
HTTP
원할한 테스트를 위하여 다음과 같이 http 라이브러리를 설치한다.
$ pip install http
CURL
원할한 테스트를 위하여 다음과 같이 curl 라이브러리를 설치한다.
$ brew install curl
학습 진행
Quick Start
다음과 같이 Quick start를 위한 디렉터리를 구성한다.
$ mkdir chalice-demo
$ cd chlice-demo
다음과 같이 Virtual env를 구성하고 활성화 한다.
$ virtualenv --python=3.6 .venv
$ source .venv/bin/activate
Chalice를 설치하고, chalice 프로젝트를 생성한다.
$ pip install chalice
$ chalice new-project andro-test-chalice
Tutorial: Local Mode
로컬 빌드를 위하여 다음과 같이 app.py를 수정한다.
from chalice import Chalice
app = Chalice(app_name='helloworld')
@app.route('/')
def index():
return {'hello': 'world'}
다음과 같은 명령어로 로컬 빌드를 한다.
$ chalice local --port=8080
Restarting local dev server.
Serving on http://127.0.0.1:8080
다음과 같은 명령어로 로컬 빌드의 동작을 확인한다.
$ http localhost:8080
HTTP/1.1 200 OK
Content-Length: 17
Content-Type: application/json
Date: Tue, 17 Sep 2019 06:12:51 GMT
Server: BaseHTTP/0.6 Python/3.6.5
{
"hello": "world"
}
Deploying
배포를 위하여 다음과 같이 config.json 파일을 수정한다. {iam_role_arn}은 각자 람다 사용을 위해 생성한 iam의 arn를 입력한다.
$ vim .chalice/config.json
{
"version": "2.0",
"app_name": "andro-test-chalice",
"api_gateway_stage": "api",
"lambda_timeout": 120,
"lambda_memory_size": 1024,
"stages": {
"qa": {
"api_gateway_stage": "qa",
"autogen_policy": false,
"iam_role_arn": "{iam_role_arn}"
},
"prod": {
"api_gateway_stage": "prod",
"autogen_policy": false,
"iam_role_arn": "{iam_role_arn}"
}
},
"manage_iam_role":false,
"iam_role_arn":"{iam_role_arn}"
}
다음과 같이 배포한다.
$ chalice deploy
Creating deployment package.
Updating lambda function: andro-test-chalice-dev
Updating rest API
Resources deployed:
- Lambda ARN:
- Rest API URL: {lambda_url}
배포된 lambda가 동작하는지 다음과 같이 확인한다.
$ curl -XGET {lambda_url}
{"hello":"world"}%
Tutorial: URL Parameters
경로에 따라 다르게 보이기 위하여 다음과 같이 app.py를 수정한다.
from chalice import Chalice
app = Chalice(app_name='helloworld')
CITIES_TO_STATE = {
'seattle': 'WA',
'portland': 'OR',
}
@app.route('/')
def index():
return {'hello': 'world'}
@app.route('/cities/{city}')
def state_of_city(city):
return {'state': CITIES_TO_STATE[city]}
배포한다.
배포된 lambda가 동작하는지 다음과 같이 확인한다.
$ curl -XGET {lambda_url}
{"hello":"world"}%
$ curl -XGET {lambda_url}cities/seattle
{"state":"WA"}%
$ curl -XGET {lambda_url}cities/portland
{"state":"OR"}%
$ curl -XGET {lambda_url}cities/vancouver
{"Code":"InternalServerError","Message":"An internal server error occurred."}%
Tutorial: Error Messages
에러가 발생하였을 때 처리하기 위하여는 디버그 모드를 활성화 해야한다.
또한 에러가 발생할 것으로 예측되는 함수나 구문에서 try~except 구문으로 예외처리한다.
위의 app.py를 다음과 같이 수정한다.
from chalice import Chalice
from chalice import BadRequestError
app = Chalice(app_name='helloworld')
app.debug = True
CITIES_TO_STATE = {
'seattle': 'WA',
'portland': 'OR',
}
@app.route('/')
def index():
return {'hello': 'world'}
@app.route('/cities/{city}')
def state_of_city(city):
try:
return {'state': CITIES_TO_STATE[city]}
except KeyError:
raise BadRequestError("Unknown city '%s', valid choices are: %s" % (
city, ', '.join(CITIES_TO_STATE.keys())))
배포한다.
배포된 lambda가 동작하는지 다음과 같이 확인한다.
$ curl -XGET {lambda_url}
{"hello":"world"}%
$ curl -XGET {lambda_url}cities/seattle
{"state":"WA"}%
$ curl -XGET {lambda_url}cities/portland
{"state":"OR"}%
$ curl -XGET {lambda_url}cities/vancouver
{"Code":"BadRequestError","Message":"BadRequestError: Unknown city 'vancouver', valid choices are: seattle, portland"}%
Tutorial: Additional Routing
GET method이외에 PUT, POST를 추가해본다.
데코레이션 /myview는 method에 따라 다른 응답을 제공하고자 한다.
다음과 같이 app.py를 수정한다.
from chalice import Chalice
app = Chalice(app_name='helloworld')
@app.route('/')
def index():
return {'hello': 'world'}
@app.route('/resource/{value}', methods=['PUT'])
def put_test(value):
return {"value": value}
@app.route('/myview/{value}', methods=['POST'])
def myview_post(value):
return {"value": value, "method", "post"}
@app.route('/myview/{value}', methods=['PUT'])
def myview_put(value):
return {"value": value, "method", "put"}
배포한다.
배포된 lambda가 동작하는지 다음과 같이 확인한다.
$ curl -XPUT {lambda_url}/resource/foo
{"value":"foo"}%
$ curl -XPUT {lambda_url}/myview/foo
{"value":"foo","mothod":"put"}%
$ curl -XPOST {lambda_url}/myview/foo
{"value":"foo","mothod":"post"}%
Tutorial: Request Metadata
메타데이터를 활용하기 위한 섹터이다.
우선 메소드 종류에 따라 다른 응답을 위하여 다음과 같이 app.py를 수정한다.
from chalice import Chalice
from chalice import NotFoundError
app = Chalice(app_name='helloworld')
@app.route('/')
def index():
return {'hello': 'world'}
OBJECTS = {
}
@app.route('/objects/{key}', methods=['GET', 'PUT'])
def myobject(key):
request = app.current_request
if request.method == 'PUT':
OBJECTS[key] = request.json_body
elif request.method == 'GET':
try:
return {key: OBJECTS[key]}
except KeyError:
raise NotFoundError(key)
배포한다.
배포된 lambda의 동작을 확인하기 위하여 다음과 같이 커널 명령으로 확인한다.
$ curl -XGET {lambda_url}/objects/banana
{"Code":"NotFoundError","Message":"NotFoundError: banana"}%
$ curl -X PUT -H "Content-Type: application/json; charset=utf-8" -d '"yellow"' {lambda_url}/objects/banana
null%
$ curl -XGET {lambda_url}/objects/banana
{"banana":"yellow"}%
위의 코드에서 app.current_request 인스턴스에 관하여 다음과 같은 속성들이 존재하고 속성들에 대한 설명은 Github AWS-Chalice README 페이지를 참고한다.
-
current_request.query_params
-
current_request.headers
-
current_request.uri_params
-
current_request.method
-
current_request.json_body
-
current_request.raw_body
-
current_request.context
-
current_request.stage_vars
current_request.to_dict()를 이용하여 메타데이터를 확인하기 위하여 다음과 같이 app.py를 수정한다.
from chalice import Chalice
app = Chalice(app_name='helloworld')
@app.route('/')
def index():
return {'hello': 'world'}
@app.route('/introspect')
def introspect():
return app.current_request.to_dict()
배포한다.
배포된 lambda의 동작을 확인하기 위하여 다음과 같이 커널 명령으로 확인한다.
$ http '{lambda_url}/introspect?query1=value1&query2=value2' 'X-TestHeader: Foo'
Tutorial: Request Content Types
컨텐트 타입에 따른 응답을 지정할 수 있다.
app.py를 다음과 같이 수정한다.
import sys
from chalice import Chalice
if sys.version_info[0] == 3:
# Python 3 imports.
from urllib.parse import urlparse, parse_qs
else:
# Python 2 imports.
from urlparse import urlparse, parse_qs
app = Chalice(app_name='helloworld')
@app.route('/', methods=['POST'],
content_types=['application/x-www-form-urlencoded'])
def index():
parsed = parse_qs(app.current_request.raw_body.decode())
return {
'states': parsed.get('states', [])
}
배포한다.
배포된 lambda는 다음과 같이 확인한다.
### Content-Type application/json
$ http POST {lambda_url} states=WA states=CA --debug
### ContentType application/x-www-form-urlencoded
$ http --form POST {lambda_url} states=WA states=CA --debug
- -form은 Content-Type 헤더를 application/x-www-form-urlencoded로 세팅하는 것의 의미한다.
app.current_request.json_body는 ContentType application/json에서 정상적인 dict 객체가 존재하고, ContentType application/json 아닐 경우는 None이다.
Tutorial: Customizing the HTTP Response
커스터 마이징한 응답으로 응답할 수 있다.
app.py를 다음과 같이 수정한다.
from chalice import Chalice, Response
app = Chalice(app_name='custom-response')
@app.route('/')
def index():
return Response(body='hello world!',
status_code=200,
headers={'Content-Type': 'text/plain'})
}
배포한다.
배포된 lambda는 다음과 같이 확인한다.
$ http {lambda_url}
Tutorial: GZIP compression for json
이 섹터는 사용빈도가 낮을 것으로 판단되어 스킵한다.
Tutorial: CORS Support
이 섹터는 전반적으로 추후 내용 보충이 필요하다.
CORS(Cross-Origin Resource Share)의 개념은 HTTP 접근 (CORS)을 참고하면 된다.
다음과 같이 app.py를 수정한다.
from chalice import CORSConfig
cors_config = CORSConfig(
allow_origin='https://foo.example.com',
allow_headers=['X-Special-Header'],
max_age=600,
expose_headers=['X-Special-Header'],
allow_credentials=True
)
@app.route('/custom_cors', methods=['GET'], cors=cors_config)
def supports_custom_cors():
return {'cors': True}
배포한다.
배포한 lambda를 확인하면 다음과 같다.
$ http {lambda_url}custom_cors
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key,X-Special-Header
Access-Control-Allow-Origin: https://foo.example.com
Access-Control-Expose-Headers: X-Special-Header
Access-Control-Max-Age: 600
...
중략
...
{
"cors": true
}
다음과 같이 app.py를 수정한다.
from chalice import Chalice, Response
app = Chalice(app_name='multipleorigincors')
_ALLOWED_ORIGINS = set([
'http://allowed1.example.com',
'http://allowed2.example.com',
])
@app.route('/cors_multiple_origins', methods=['GET', 'OPTIONS'])
def supports_cors_multiple_origins():
method = app.current_request.method
if method == 'OPTIONS':
headers = {
'Access-Control-Allow-Method': 'GET,OPTIONS',
'Access-Control-Allow-Origin': ','.join(_ALLOWED_ORIGINS),
'Access-Control-Allow-Headers': 'X-Some-Header',
}
origin = app.current_request.headers.get('origin', '')
if origin in _ALLOWED_ORIGINS:
headers.update({'Access-Control-Allow-Origin': origin})
return Response(
body=None,
headers=headers,
)
elif method == 'GET':
return 'Foo'
배포한다.
배포한 lambda를 확인하면 다음과 같다. Access-Control-Allow-*를 살펴보면 된다.
$ http OPTIONS {lambda_url}cors_multiple_origins
HTTP/1.1 200 OK
Access-Control-Allow-Headers: X-Some-Header
Access-Control-Allow-Method: GET,OPTIONS
Access-Control-Allow-Origin: http://allowed2.example.com,http://allowed1.example.com
...
중략
...
null
Tutorial: Policy Generation
다음과 같이 app.py를 수정한다.
import json
import boto3
from botocore.exceptions import ClientError
from chalice import NotFoundError, Chalice
app = Chalice(app_name="boto3_test")
S3 = boto3.client('s3', region_name='us-east-1')
BUCKET = {bucket}
PATH = 'andro-chalice-test'
@app.route('/objects/{key}', methods=['GET', 'PUT'])
def s3objects(key):
fullpath_key = "/".join([PATH, key])
request = app.current_request
if request.method == 'PUT':
S3.put_object(Bucket=BUCKET, Key=fullpath_key,
Body=json.dumps(request.json_body))
elif request.method == 'GET':
try:
response = S3.get_object(Bucket=BUCKET, Key=fullpath_key)
return json.loads(response['Body'].read())
except ClientError as e:
raise NotFoundError(key)
로컬 빌드나 배포하여 확인하나 기능상 차이가 없기에 로컬 빌드로 확인한다. 로컬로 빌드한다.
다음과 같이 로컬 chalice의 동작을 확인한다.
$ echo '{"foo":"bar"}' | http PUT localhost:8080/objects/banana
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/json
Date: Tue, 17 Sep 2019 06:38:28 GMT
Server: BaseHTTP/0.6 Python/3.6.5
null
$ http localhost:8080/objects/banana
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: application/json
Date: Tue, 17 Sep 2019 06:38:33 GMT
Server: BaseHTTP/0.6 Python/3.6.5
{
"foo": "bar"
}
aws cli로 실제 버킷에 저장되었는지 확인한다.
$ s3 ls s3://{s3_address}/andro-chalice-test --recursive
2019-09-17 15:38:28 14 andro-chalice-test/banana
배포한다.
배포한 lambda의 동작을 확인한다.
$ echo '"red"' | http PUT {lambda_url}objects/apple
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 4
Content-Type: application/json
...
하략
null
$ http {lambda_url}objects/apple
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 3
Content-Type: application/json
Date: Tue, 17 Sep 2019 07:13:51 GMT
...
하략
수동으로 정책을 제공할 수 있다. 다음과 같은 경로에 정책에 관하여 정의해주면 된다. 이 부분은 추후 학습하여 수정할 예정이다.
<projectdir>/.chalice/policy.json
예) andro-chalice-test/policy-dev.json
Tutorial: Using Custom Authentication
문서에서 소개하는 인증방법은 다음과 같고, 이 중 Cognito User Pools를 제외한 내용을 실습한다.
-
API Key
-
AWS IAM
-
Cognito User Pools
-
Custom Auth Handler
app.py를 다음과 같이 수정한다.
from chalice import Chalice, IAMAuthorizer, CustomAuthorizer
app = Chalice(app_name="boto3_test")
iam_authorizer = IAMAuthorizer()
custom_authorizer = CustomAuthorizer(
'MyCustomAuth', header='Authorization',
authorizer_uri=('arn:aws:apigateway:region:lambda:path/2015-03-31'
'/functions/arn:aws:lambda:region:account-id:'
'function:FunctionName/invocations'))
@app.route('/authenticated', methods=['GET'], api_key_required=True)
def authenticated():
return {"secure": True}
@app.route('/iam-role', methods=['GET'], authorizer=iam_authorizer)
def authenticated():
return {"secure": True}
@app.route('/custom-auth', methods=['GET'], authorizer=custom_authorizer)
def authenticated():
return {"secure": True}
다음과 같이 로컬 chalice의 동작을 확인한다.
$ http localhost:8080/authenticated
HTTP/1.1 200 OK
Content-Length: 15
Content-Type: application/json
Date: Tue, 17 Sep 2019 08:57:24 GMT
Server: BaseHTTP/0.6 Python/3.6.5
{
"secure": true
}
$ http localhost:8080/iam-role
HTTP/1.1 200 OK
Content-Length: 15
Content-Type: application/json
Date: Tue, 17 Sep 2019 08:57:35 GMT
Server: BaseHTTP/0.6 Python/3.6.5
{
"secure": true
}
$ http localhost:8080/custom-auth
HTTP/1.1 200 OK
Content-Length: 15
Content-Type: application/json
Date: Tue, 17 Sep 2019 08:57:40 GMT
Server: BaseHTTP/0.6 Python/3.6.5
{
"secure": true
}
참고 자료
- AWS Chalice 기본 학습 공유
댓글남기기