[목차]
[참조]
REST 는 Representational State Transfer 의 약자로 네트워크 아키텍처 원리의 모음이다. 여기서 ‘네트워크 아키텍처 원리’란 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는다.
REST 는 로이 필딩(Roy Fielding)의 박사 학위 논문 에서 제안되었고, 이 원칙을 따르는 시스템을 RESTful 하다고 지칭한다.
이 글에서는 REST, REST API, RESTful 그리고 REST 확장 모듈인 Flask-RESTful 의 활용예를 살펴본다.
다양한 멀티플랫폼이 존재하는 현재 상황에서 가장 간결하면서도 명료한 통신이 가능하도록 한다
다음은 REST 아키텍처에 적용되는 6가지 제한 조건이다.
참조 : Flask-RESTful 공식 문서 번역
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
이것을 저장한 후 실행하면 된다.
Flask debugging mode
를 활성화하면 코드 리로딩과 더 나은 에러 메시지를 보여 준다.
다만, 실제 운영 환경에서 디버그 모드는 절대 활성화되면 안 된다!
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
if __name__ == '__main__':
app.run(debug=True)
add_resource
부분에 추가한 리소스 정의에 따라 URI 에는 str 타입의 고유한 id 가 포함되어야 한다.
REST 원리에 따라 각 HTTP 메서드 동작에 맞는 메서드가 호출된다.
Flask-RESTful 은 view 메서드로부터 반환되는 여러 종류의 return 값을 이해한다.
Flask 와 유사하게, iterable 하고 response 객체로 변환 가능한 모든 결과값을 Flask response object 에 담아서 있는 그대로 반환할 수 있다.
또한, 다중 return 값을 활용해서 response code 와 response headers 를 세팅할 수 있다. 아래 코드를 보자.
class Todo1(Resource):
def get(self):
# Default to 200 OK
return {'task': 'Hello world'}
class Todo2(Resource):
def get(self):
# Set the response code to 201
return {'task': 'Hello world'}, 201
class Todo3(Resource):
def get(self):
# Set the response code to 201 and return custom headers
return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}
add_resource()
메서드에 투입함으로써 당신의 API 는 여러 종류의 URL 을 가질 수 있다.api.add_resource(HelloWorld,
'/',
'/hello')
또한 당신의 resource methods 에 특정 변수를 경로의 endpoint 부분으로 매치시킬 수 있다.
api.add_resource(Todo,
'/todo/<int:todo_id>', endpoint='todo_ep')
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()
여기서 reqparse.RequestParser.parse_args() 는 파이썬 dict 타입 값을 반환한다.
reqparse 모듈을 사용하면 정상적인 에러 메시지가 제공된다.
인자(매개변수)가 유효성 검증에 실패하면, Flask-RESTful 은 400 Bad Request 와 함께 error에 대한 response highlighting 를 respond 할 것이다.
inputs 모듈은 inputs.date(), inputs.url() 과 같이 여러 종류의 common conversion 함수를 제공한다.
또한, strict=True
인자와 함께 parse_args() 메서드가 호출되면 당신의 parser 에 정의되지 않은 arguments 가 request 에 포함된 경우 에러가 thrown 된다.
args = parser.parse_args(strict=True)
from flask_restful import fields, marshal_with
resource_fields = {
'task': fields.String,
'uri': fields.Url('todo_ep')
}
class TodoDao(object):
def __init__(self, todo_id, task):
self.todo_id = todo_id
self.task = task
# This field will not be sent in the response
self.status = 'active'
class Todo(Resource):
@marshal_with(resource_fields)
def get(self, **kwargs):
return TodoDao(todo_id='my_todo', task='Remember the milk')
위 예제는 파이썬 객체와 그것의 preparses 가 직렬화되는 과정을 보여준다. marshal_with() decorator 는 resource_fields 에 의해 describe 된 변환 결과를 apply 할 것이다. 객체로부터 추출된 유일한 field 는 ‘task’ 이다. fields.Url field 는 endpoint 이름을 포함하고, response 에서 그 endpoint 를 위한 URL 을 생성하는 특별한 field 이다. 당신이 필요한 수많은 field 타입들은 이미 포함되어 있다. 이를 위해 fields 가이드를 참조할 것.
from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
app = Flask(__name__)
api = Api(app)
TODOS = {
'todo1': {'task': 'build an API'},
'todo2': {'task': '?????'},
'todo3': {'task': 'profit!'},
}
def abort_if_todo_doesnt_exist(todo_id):
if todo_id not in TODOS:
abort(404, message="Todo {} doesn't exist".format(todo_id))
parser = reqparse.RequestParser()
parser.add_argument('task')
# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
def get(self, todo_id):
abort_if_todo_doesnt_exist(todo_id)
return TODOS[todo_id]
def delete(self, todo_id):
abort_if_todo_doesnt_exist(todo_id)
del TODOS[todo_id]
return '', 204
def put(self, todo_id):
args = parser.parse_args()
task = {'task': args['task']}
TODOS[todo_id] = task
return task, 201
# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
def get(self):
return TODOS
def post(self):
args = parser.parse_args()
todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
todo_id = 'todo%i' % todo_id
TODOS[todo_id] = {'task': args['task']}
return TODOS[todo_id], 201
##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')
if __name__ == '__main__':
app.run(debug=True)