Buscar

Implementação de um servidor rest

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 14 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 14 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 14 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Implementação de um servidor rest
Projetando uma API RESTful usando Flask-RESTful
Este é o terceiro artigo em que eu explorar diferentes aspectos da escrita RESTful APIs usando o Microframework Flask. Aqui está o primeiro (https://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask) e o segundo(https://blog.miguelgrinberg.com/post/writing-a-javascript-rest-client).
O servidor RESTful de exemplo que eu escrevi antes usava apenas Flask como uma dependência. Hoje vou mostrar-lhe como escrever o mesmo servidor usandoFlask-RESTful,(http://flask-restful.readthedocs.io/en/latest/ )uma extensão Flask que simplifica a criação de APIs.
OBS: Flask-RESTful é uma extensão para o Flask que adiciona suporte para criar rapidamente APIs REST. É uma abstração leve que funciona com o seu ORM / bibliotecas existentes. O Flask-RESTful incentiva as melhores práticas com uma configuração mínima. Se você estiver familiarizado com Flask, Flask-RESTful deve ser fácil de pegar.
O servidor RESTful
Como lembrete, aqui está a definição do serviço da Web ToDo List que tem servido como um exemplo em artigos RESTful:
	HTTP Method
	URI
	Action
	GET
	http://[hostname]/todo/api/v1.0/tasks
	Recupera lista de tarefas
	GET
	http://[hostname]/todo/api/v1.0/tasks/[task_id]
	Recupera uma tarefa
	POST
	http://[hostname]/todo/api/v1.0/tasks
	Cria uma tarefa
	PUT
	http://[hostname]/todo/api/v1.0/tasks/[task_id]
	Atualizar uma tarefa existente
	DELETE
	http://[hostname]/todo/api/v1.0/tasks/[task_id]
	Excluir uma tarefa
O único recurso exposto por este serviço é uma "tarefa", que tem os seguintes campos de dados:
Uri: URI exclusivo para a tarefa. Tipo de cadeia.
Title: descrição da tarefa curta. Tipo de cadeia.
description: descrição da tarefa longa. Tipo de texto.
Done: estado de conclusão da tarefa. Tipo booleano.
Routing
No meu primeiro exemplo de servidor RESTful (código-fonte aqui), usei funções regulares do Flask para definir todas as rotas.
	#!flask/bin/python
	
	import six
	
	from flask import Flask, jsonify, abort, request, make_response, url_for
	
	from flask.ext.httpauth import HTTPBasicAuth
	
	
	
	app = Flask(__name__, static_url_path="")
	
	auth = HTTPBasicAuth()
	
	
	
	
	
	@auth.get_password
	
	def get_password(username):
	
	 if username == 'miguel':
	
	 return 'python'
	
	 return None
	
	
	
	
	
	@auth.error_handler
	
	def unauthorized():
	
	 # return 403 instead of 401 to prevent browsers from displaying the default
	
	 # auth dialog
	
	 return make_response(jsonify({'error': 'Unauthorized access'}), 403)
	
	
	
	
	
	@app.errorhandler(400)
	
	def bad_request(error):
	
	 return make_response(jsonify({'error': 'Bad request'}), 400)
	
	
	
	
	
	@app.errorhandler(404)
	
	def not_found(error):
	
	 return make_response(jsonify({'error': 'Not found'}), 404)
	
	
	
	
	
	tasks = [
	
	 {
	
	 'id': 1,
	
	 'title': u'Buy groceries',
	
	 'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
	
	 'done': False
	
	 },
	
	 {
	
	 'id': 2,
	
	 'title': u'Learn Python',
	
	 'description': u'Need to find a good Python tutorial on the web',
	
	 'done': False
	
	 }
	
	]
	
	
	
	
	
	def make_public_task(task):
	
	 new_task = {}
	
	 for field in task:
	
	 if field == 'id':
	
	 new_task['uri'] = url_for('get_task', task_id=task['id'],
	
	 _external=True)
	
	 else:
	
	 new_task[field] = task[field]
	
	 return new_task
	
	
	
	
	
	@app.route('/todo/api/v1.0/tasks', methods=['GET'])
	
	@auth.login_required
	
	def get_tasks():
	
	 return jsonify({'tasks': [make_public_task(task) for task in tasks]})
	
	
	
	
	
	@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['GET'])
	
	@auth.login_required
	
	def get_task(task_id):
	
	 task = [task for task in tasks if task['id'] == task_id]
	
	 if len(task) == 0:
	
	 abort(404)
	
	 return jsonify({'task': make_public_task(task[0])})
	
	
	
	
	
	@app.route('/todo/api/v1.0/tasks', methods=['POST'])
	
	@auth.login_required
	
	def create_task():
	
	 if not request.json or 'title' not in request.json:
	
	 abort(400)
	
	 task = {
	
	 'id': tasks[-1]['id'] + 1,
	
	 'title': request.json['title'],
	
	 'description': request.json.get('description', ""),
	
	 'done': False
	
	 }
	
	 tasks.append(task)
	
	 return jsonify({'task': make_public_task(task)}), 201
	
	
	
	
	
	@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT'])
	
	@auth.login_required
	
	def update_task(task_id):
	
	 task = [task for task in tasks if task['id'] == task_id]
	
	 if len(task) == 0:
	
	 abort(404)
	
	 if not request.json:
	
	 abort(400)
	
	 if 'title' in request.json and \
	
	 not isinstance(request.json['title'], six.string_types):
	
	 abort(400)
	
	 if 'description' in request.json and \
	
	 not isinstance(request.json['description'], six.string_types):
	
	 abort(400)
	
	 if 'done' in request.json and type(request.json['done']) is not bool:
	
	 abort(400)
	
	 task[0]['title'] = request.json.get('title', task[0]['title'])
	
	 task[0]['description'] = request.json.get('description',
	
	 task[0]['description'])
	
	 task[0]['done'] = request.json.get('done', task[0]['done'])
	
	 return jsonify({'task': make_public_task(task[0])})
	
	
	
	
	
	@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['DELETE'])
	
	@auth.login_required
	
	def delete_task(task_id):
	
	 task = [task for task in tasks if task['id'] == task_id]
	
	 if len(task) == 0:
	
	 abort(404)
	
	 tasks.remove(task[0])
	
	 return jsonify({'result': True})
	
	
	
	
	
	if __name__ == '__main__':
	
	 app.run(debug=True)
Flask-RESTful fornece uma classe base Resource que pode definir o roteamento para um ou mais métodos HTTP para um determinado URL. Por exemplo, para definir um recurso de usuário com métodos GET, PUT e DELETE você escreveria:
from flask import Flask
from flask.ext.restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class UserAPI(Resource):
 def get(self, id):
 pass
 def put(self, id):
 pass
 def delete(self, id):
 pass
api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
A função add_resource registra as rotas com o framework usando o determinado nó de extremidade. Se um nó de extremidade não for dado, então o Flask-RESTful gera um para você a partir do nome da classe, mas como às vezes o nó de extremidade é necessário para funções como url_for eu prefiro torná-lo explícito.
Minha API To Do define dois URLs: /todo/api/v1.0/tasks para a lista de tarefas e /todo/api/v1.0/tasks/ <int: id> para uma tarefa individual. Uma vez que a classe Flask-RESTful Resource pode envolver uma única URL, este servidor precisará de dois recursos:
class TaskListAPI(Resource):
 def get(self):
 pass
 def post(self):
 pass
class TaskAPI(Resource):
 def get(self, id):
 pass
 def put(self, id):
 pass
 def delete(self, id):
 pass
api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint = 'tasks')
api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint = 'task')
Observe que, enquanto as exibições de método da TaskList API não recebem nenhum argumento, os que estão no TaskAPIrecebem todos o id, conforme especificado na URL sob a qual o recurso está registrado.
Solicitação de análise e validação
Quando eu implementei este servidor no artigo anterior eu fiz a minha própria validação dos dados do pedido. Por exemplo, observe quanto tempo o manipulador PUT está nessa versão:
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['PUT'])
@auth.login_required
def update_task(task_id):
 task = filter(lambda t: t['id'] == task_id, tasks)
 if len(task) == 0:
 abort(404)
 if not request.json:
 abort(400)
 if 'title' in request.json and type(request.json['title']) != unicode:
 abort(400)
 if 'description' in request.json and type(request.json['description']) is not unicode:
 abort(400)
 if 'done' in request.json and type(request.json['done']) is not bool:
 abort(400)
 task[0]['title'] = request.json.get('title', task[0]['title'])
 task[0]['description'] = request.json.get('description', task[0]['description'])
 task[0]['done'] = request.json.get('done', task[0]['done'])
 return jsonify( { 'task': make_public_task(task[0]) } )
Aqui eu tenho que ter certeza que os dados fornecidos com o pedido é válido antes de usá-lo, e isso torna a função muito longa.
Flask-RESTful fornece uma maneira muito melhor de lidar com isso com a classe RequestParser. Esta classe funciona de forma semelhante a argparse para argumentos de linha de comando.
Primeiro, para cada recurso eu defino os argumentos e como validá-los:
from flask.ext.restful import reqparse
class TaskListAPI(Resource):
 def __init__(self):
 self.reqparse = reqparse.RequestParser()
 self.reqparse.add_argument('title', type = str, required = True,
 help = 'No task title provided', location = 'json')
 self.reqparse.add_argument('description', type = str, default = "", location = 'json')
 super(TaskListAPI, self).__init__()
 # ...
class TaskAPI(Resource):
 def __init__(self):
 self.reqparse = reqparse.RequestParser()
 self.reqparse.add_argument('title', type = str, location = 'json')
 self.reqparse.add_argument('description', type = str, location = 'json')
 self.reqparse.add_argument('done', type = bool, location = 'json')
 super(TaskAPI, self).__init__()
 # ...
No recurso TaskListAPI o método POST é o único que recebe os argumentos. O argumento de title é necessário aqui, então eu incluí uma mensagem de erro que Flask-RESTful enviará como uma resposta para o cliente quando o campo está faltando. O campo de description é opcional e, quando ele estiver faltando, um valor padrão de uma seqüência vazia será usado. Um aspecto interessante da classe RequestParser é que, por padrão, ele procura campos em request.values, então o argumento opcional location deve ser definido para indicar que os campos estão vindo em request.json.
O analisador de solicitações para o TaskAPI é construído de forma semelhante, mas tem algumas diferenças. Neste caso é o método PUT que precisará analisar argumentos, e para este método todos os argumentos são opcionais, incluindo o campo done que não fazia parte da solicitação no outro recurso.
Agora que os analisadores de solicitação são inicializados, analisar e validar um pedido é bastante fácil. Por exemplo, observe quanto mais simples o método TaskAPI.put () se torna:
def put(self, id):
 task = filter(lambda t: t['id'] == id, tasks)
 if len(task) == 0:
 abort(404)
 task = task[0]
 args = self.reqparse.parse_args()
 for k, v in args.iteritems():
 if v != None:
 task[k] = v
 return jsonify( { 'task': make_public_task(task) } )
Um benefício lateral de deixar Flask-RESTful fazer a validação é que agora não há necessidade de ter um manipulador para o código de pedido erro 400 errado, isso é tudo cuidado pela extensão.
Gerando respostas
Meu servidor REST original gera as respostas usando a função auxiliar jsonify do Flask. Flask-RESTful automaticamente manipula a conversão para JSON, então em vez disso:
return jsonify( { 'task': make_public_task(task) } )
Eu posso fazer isso:
return { 'task': make_public_task(task) }
Flask-RESTful também suporta a devolução de um código de status personalizado quando necessário:
return { 'task': make_public_task(task) }, 201
Mas há mais. O make_public_task wrapper do servidor original converteu uma tarefa de sua representação interna para a representação externa esperada pelos clientes. A conversão incluiu remover o campo id e adicionar um campo uri em seu lugar. Flask-RESTful fornece uma função auxiliar para fazer isso de uma forma muito mais elegante que gera não apenas o uri, mas também faz a conversão de tipo nos campos restantes:
from flask.ext.restful import fields, marshal
task_fields = {
 'title': fields.String,
 'description': fields.String,
 'done': fields.Boolean,
 'uri': fields.Url('task')
}
class TaskAPI(Resource):
 # ...
 def put(self, id):
 # ...
 return { 'task': marshal(task, task_fields) }
A estrutura task_fields serve como um modelo para a função marshal. O tipo fields.Url é um tipo especial que gera um URL. O argumento que leva é o ponto final (lembre-se de que eu usei pontos de extremidade explícitos quando registrei os recursos especificamente para que eu possa me referir a eles quando necessário).
Autenticação
As rotas no servidor REST estão protegidas com autenticação básica HTTP. No servidor original, a proteção foi adicionada usando o decorador fornecido pela extensão Flask-HTTPAuth.
Como a classe Resouce herda do MethodView do Flask, é possível anexar decoradores aos métodos definindo uma variável de classe decorators:
from flask.ext.httpauth import HTTPBasicAuth
# ...
auth = HTTPBasicAuth()
# ...
class TaskAPI(Resource):
 decorators = [auth.login_required]
 # ...
class TaskAPI(Resource):
 decorators = [auth.login_required]
 # ...
Conclusão
A implementação completa do servidor baseada em Flask-RESTful está disponível no meu projeto REST-tutorial no github (https://github.com/miguelgrinberg/REST-tutorial). aqui ficam todos os códigos:
index.html
https://github.com/miguelgrinberg/REST-tutorial/blob/master/static/index.html
.gitignore
https://github.com/miguelgrinberg/REST-tutorial/blob/master/.gitignore
license
https://github.com/miguelgrinberg/REST-tutorial/blob/master/LICENSE
readme
https://github.com/miguelgrinberg/REST-tutorial/blob/master/README.md
Instale o Python 2.7 eo git.
Execute setup.sh (Linux, OS X, Cygwin) ou setup.bat (Windows)
Execute ./rest-server.py para iniciar o servidor (no Windows use flask \ Scripts \ python rest-server.py em vez disso)
Como alternativa, execute ./rest-server-v2.py para iniciar a versão RESTful Flask do servidor.
Abra http: // localhost: 5000 / index.html no seu navegador da Web para executar o cliente
requirements.txt
https://github.com/miguelgrinberg/REST-tutorial/blob/master/requirements.txt
rest-server-v2.py
https://github.com/miguelgrinberg/REST-tutorial/blob/master/rest-server-v2.py
rest-server.py
https://github.com/miguelgrinberg/REST-tutorial/blob/master/rest-server.py
setup.bat
https://github.com/miguelgrinberg/REST-tutorial/blob/master/setup.bat
setup.sh
https://github.com/miguelgrinberg/REST-tutorial/blob/master/setup.sh
virtualenv.py
https://github.com/miguelgrinberg/REST-tutorial/blob/master/virtualenv.py
O arquivo com o servidor Flask-RESTful é rest-server-v2.py. (https://github.com/miguelgrinberg/REST-tutorial/blob/master/rest-server-v2.py)
servidor rest-server-v2.py
	#!flask/bin/python
	
	
	
	"""Alternative version of the ToDo RESTful server implemented using the
	
	Flask-RESTful extension."""
	
	
	
	from flask import Flask, jsonify, abort, make_response
	
	from flask.ext.restful import Api, Resource, reqparse, fields, marshal
	
	from flask.ext.httpauthimport HTTPBasicAuth
	
	
	
	app = Flask(__name__, static_url_path="")
	
	api = Api(app)
	
	auth = HTTPBasicAuth()
	
	
	
	
	
	@auth.get_password
	
	def get_password(username):
	
	 if username == 'miguel':
	
	 return 'python'
	
	 return None
	
	
	
	
	
	@auth.error_handler
	
	def unauthorized():
	
	 # return 403 instead of 401 to prevent browsers from displaying the default
	
	 # auth dialog
	
	 return make_response(jsonify({'message': 'Unauthorized access'}), 403)
	
	
	
	tasks = [
	
	 {
	
	 'id': 1,
	
	 'title': u'Buy groceries',
	
	 'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
	
	 'done': False
	
	 },
	
	 {
	
	 'id': 2,
	
	 'title': u'Learn Python',
	
	 'description': u'Need to find a good Python tutorial on the web',
	
	 'done': False
	
	 }
	
	]
	
	
	
	task_fields = {
	
	 'title': fields.String,
	
	 'description': fields.String,
	
	 'done': fields.Boolean,
	
	 'uri': fields.Url('task')
	
	}
	
	
	
	
	
	class TaskListAPI(Resource):
	
	 decorators = [auth.login_required]
	
	
	
	 def __init__(self):
	
	 self.reqparse = reqparse.RequestParser()
	
	 self.reqparse.add_argument('title', type=str, required=True,
	
	 help='No task title provided',
	
	 location='json')
	
	 self.reqparse.add_argument('description', type=str, default="",
	
	 location='json')
	
	 super(TaskListAPI, self).__init__()
	
	
	
	 def get(self):
	
	 return {'tasks': [marshal(task, task_fields) for task in tasks]}
	
	
	
	 def post(self):
	
	 args = self.reqparse.parse_args()
	
	 task = {
	
	 'id': tasks[-1]['id'] + 1,
	
	 'title': args['title'],
	
	 'description': args['description'],
	
	 'done': False
	
	 }
	
	 tasks.append(task)
	
	 return {'task': marshal(task, task_fields)}, 201
	
	
	
	
	
	class TaskAPI(Resource):
	
	 decorators = [auth.login_required]
	
	
	
	 def __init__(self):
	
	 self.reqparse = reqparse.RequestParser()
	
	 self.reqparse.add_argument('title', type=str, location='json')
	
	 self.reqparse.add_argument('description', type=str, location='json')
	
	 self.reqparse.add_argument('done', type=bool, location='json')
	
	 super(TaskAPI, self).__init__()
	
	
	
	 def get(self, id):
	
	 task = [task for task in tasks if task['id'] == id]
	
	 if len(task) == 0:
	
	 abort(404)
	
	 return {'task': marshal(task[0], task_fields)}
	
	
	
	 def put(self, id):
	
	 task = [task for task in tasks if task['id'] == id]
	
	 if len(task) == 0:
	
	 abort(404)
	
	 task = task[0]
	
	 args = self.reqparse.parse_args()
	
	 for k, v in args.items():
	
	 if v is not None:
	
	 task[k] = v
	
	 return {'task': marshal(task, task_fields)}
	
	
	
	 def delete(self, id):
	
	 task = [task for task in tasks if task['id'] == id]
	
	 if len(task) == 0:
	
	 abort(404)
	
	 tasks.remove(task[0])
	
	 return {'result': True}
	
	
	
	
	
	api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint='tasks')
	
	api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint='task')
	
	
	
	
	
	if __name__ == '__main__':
	
	 app.run(debug=True)
Você também pode baixar todo o projeto, incluindo implementações de servidor e um cliente javascript para testá-lo:
na pasta em anexo.

Continue navegando

Outros materiais