0% acharam este documento útil (0 voto)
33 visualizações22 páginas

05 - Serviços Web em Python (1 Parte)

Este documento descreve como criar serviços web RESTful em Python usando o framework Flask. Aborda conceitos como recursos, URLs, operações HTTP e o formato JSON. Explica também como desenvolver uma aplicação simples 'Olá Mundo' em Flask.

Enviado por

avarela
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
33 visualizações22 páginas

05 - Serviços Web em Python (1 Parte)

Este documento descreve como criar serviços web RESTful em Python usando o framework Flask. Aborda conceitos como recursos, URLs, operações HTTP e o formato JSON. Explica também como desenvolver uma aplicação simples 'Olá Mundo' em Flask.

Enviado por

avarela
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 22

Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Serviços Web em Python1

1. Serviços RESTful
REST significa Representational State Transfer e foi um termo cunhado por Roy Fielding na sua
dissertação de doutoramento para descrever um estilo de arquitetura simples, orientada a
recursos que sustentam a web.

Este trabalho consistiu na procura duma maneira de generalizar o funcionamento do HTTP e da


web. Mais especificamente, utilizar as páginas web como forma de conter os dados fornecidos
e relativos a um pedido (request) de um cliente, onde este mantém o estado atual de uma
interação (pedido/resposta). Com base nesta informação de estado, o cliente faz um pedido do
próximo item de dados relevantes, enviando todas as informações necessárias para identificar
os dados a serem fornecidos com o pedido. Sendo assim, os pedidos são independentes e não
fazem parte da interação/conversa que eventualmente esteja a decorrer (o qual mantem o seu
próprio estado).

Deve-se notar que, embora o Fielding tivesse como objetivo criar uma forma de descrever o
padrão de comportamento dentro da web, ele também mirava em produzir serviços “mais
leves” baseados na web (em contrapartida, aos serviços que usam o Enterprise frameworks de
integração ou serviços baseados em SOAP). Estes serviços web (mais leves), baseados em HTTP
tornaram-se muito populares e agora são amplamente utilizados em muitas áreas. Os sistemas
que seguem esses princípios são chamados de serviços RESTful. Um aspeto chave de um serviço
RESTful é que todas as interações entre um cliente (quer se trata de código JavaScript a rodar
num browser ou de uma aplicação autónoma) são realizadas usando operações simples
baseadas no protocolo HTTP.

O HTTP suporta quatro operações (ou métodos), sendo elas: o HTTP Get; o HTTP Post; o HTTP
Put e o HTTP Delete. Estas podem ser usadas como palavras para indicar o tipo de ação que está
sendo solicitada. Normalmente são usadas como segue:

• Procurar/ler informação (HTTP Get);


• Criar informação (HTTP Post);
• Atualizar informação (HTTP Put);
• Remover/apagar informação (HTTP Delete).

Deve-se notar que o REST não é um padrão da mesma forma que o HTML. Em vez disso, trata-
se de um padrão de design que pode ser usado para criar aplicações web que podem ser
invocados por HTTP e que dão sentido ao uso dos métodos Get, Post, Put e Delete em relação a
um recurso específico (ou tipo de dados).

As vantagens de usar serviços RESTful como tecnologia, em comparação com algumas outras
abordagens (como serviços baseados em SOAP que também podem ser invocados por HTTP)
são as seguintes:

• As implementações tendem a ser mais simples;

• A manutenção é mais fácil de realizar;

1
(Baseado no livro: J. Hunt, Advanced Guide to Python 3 Programming, Undergraduate Topics in Computer Science)

1
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

• São executados através dos protocolos HTTP e HTTPS padrão e

• Não requerem infraestruturas e licenças comerciais caras para serem usadas.

O que significa que os custos são mais baixos, quer do lado do cliente, quer do lado do servidor.
Significa, também, uma menor dependência quer das licenças comercial, quer da tecnologia,
uma vez que os programadores não precisam saber nada sobre os detalhes de implementação
ou tecnologias que estão sendo usadas para criar os serviços.

2. . Uma API em RESTful


1. Uma API RESTful é aquela na qual se deve primeiro determinar os principais conceitos ou
recursos que vão ser representados ou geridos.

2. Podem ser livros, produtos numa loja, reservas de quartos em hotéis etc. (por exemplo, um
serviço relacionado com uma biblioteca que pode fornecer informações sobre recursos como
livros, CDs, DVDs, etc. Neste serviço, os livros são apenas um tipo de recurso. Para simplificar,
no exemplo que de seguida iremos fazer referência, iremos ignorar os outros recursos, como
DVDs e CDs, etc.

3. Com base na ideia de se considerar um livro como um recurso, identificaremos URLs


adequados para esses serviços RESTful. Note-se que, embora os URLs sejam frequentemente
usados para descrever uma página da Web - esta é apenas um tipo de recurso. Por exemplo,
poderíamos desenvolver um recurso como
/bookservice/book

a partir daqui, poderíamos desenvolver uma API baseada em URL, como:


/bookservice/book/<isbn>

Onde o ISBN (International Standard Book Number) indica um número único a ser usado para
identificar um livro específico cujos detalhes serão devolvidos usando esse URL.

Vamos precisar de conceber a representação ou os formatos que o serviço pode fornecer. Estes
podem incluir texto simples, JSON, XML, etc. Padrões JSON para o JavaScript Object Notation
representam uma maneira concisa de descrever dados que devem ser transferidos de um
serviço a correr num servidor para um cliente que se executa num browser. Este é o formato
que de seguida iremos fazer uso. Como parte disso, podemos identificar uma série de operações
a serem fornecidas pelos nossos serviços, com base no tipo de Método HTTP usado para invocar
o serviço e o respetivo conteúdo do URL fornecido. Por exemplo, para um simples BookService
pode ser:

• GET /book/<isbn> — usado para procurar/ler um livro para um determinado ISBN.

• GET /book/list — usado para procurar/ler todos os livros atuais no formato JSON.

• POST /book (JSON no corpo da mensagem) — que suporta a criação/introdução de


um novo livro.

• PUT /book (JSON no corpo da mensagem) — usado para atualizar os dados mantidos
em um livro que já existe.

• DELETE /book/<isbn> — usado para indicar que gostaríamos de remover um livro da


lista de livros existentes.

2
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Note-se que o parâmetro isbn nas URLs acima indicados, na verdade faz parte do caminho do
URL.

3. Python Web Frameworks


Existem muitas frameworks e bibliotecas disponíveis em Python que permitem criar serviços
Web baseados em JSON; sendo o número de opções disponíveis elevado. Por exemplo, e para
citar apenas alguns, podem-se referenciar as seguintes:

• Flask,

• Django,

• Web2py e

• CherryPy

Estas frameworks e bibliotecas oferecem diferentes conjuntos de facilidades e níveis de


sofisticação. Por exemplo, o Django é uma framework web full-stack; pois é destinado a
desenvolver não apenas serviços da web, mas também sites completos. No entanto, para aquilo
que de momento nos serve, usá-lo seria provavelmente um pouco excessivo. Claramente, isso
não significa que não poderíamos usar o Django para criar os nossos serviços de biblioteca;
porém existem disponíveis opções mais simples. O Web2py é outra framework web full stack
que, pelos mesmos motivos do Django, não iremos considerar.

Em contrapartida, o Flask e o CherryPy são consideradas frameworks não full-stack (embora


com eles se possa criar aplicações Web full stack), significando que são mais leves e mais
rápidas para iniciarmos o estudo de serviços web. Vamos nos concentrar no Flask, pois é uma
das frameworks mais usadas no desenvolvimento de serviços RESTful “leves” em Python.

4. Flask
Flask é uma framework de desenvolvimento web para Python. É também caracterizada como
uma micro framework para Python, que segundo a Flask está relacionado com o objetivo
principal de manter o núcleo do Flask simples, mas extensível. Ao contrário do Django, esta não
inclui recursos destinados a ajudar a integrar as aplicações com base de dados, por exemplo. Em
vez disso, o Flask concentra-se na funcionalidade principal exigido de uma estrutura de serviços
web e permite que extensões sejam incluídas e usadas quando necessário para a inclusão de
funcionalidades adicionais.

O Flask representa também uma convenção para a estrutura de configuração; isto é, se forem
seguidas as convenções padrão, então não será necessário lidar com muitas informações de
configurações adicionais (embora se for o caso de se desejar seguir um conjunto diferente de
convenções, então poder-se-á fornecer outras informações de configuração para alterar os
padrões iniciais). Quanto mais as pessoas (pelo menos inicialmente) seguirem essas convenções,
mais fácil e rapidamente se torna a obtenção de aplicações a funcionar.

5. “Hello World” em Flask


Como é tradicional em todas as linguagens de programação, começaremos com uma simples
aplicação do tipo “Hello World”. Esta aplicação permite-nos criar um serviço web muito simples
que mapeia uma URL específica para uma função que retornará dados no formato JSON.

3
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Usaremos o formato de dados JSON, pois é muito utilizado no desenvolvimento e


implementação de serviços baseados na web.

5.1. Uso do JASON


Os padrões JSON para JavaScript Object Notation permitem intercâmbio de dados com um
formato “leve” que também é fácil para os humanos lerem e escreverem. Embora seja derivado
de um subconjunto da linguagem de programação JavaScript; ele é de fato uma linguagem
completamente independente e é atualmente suportada por muitas outras linguagens e
frameworks que automaticamente processam os próprios formatos de e para JSON. Isto torna-
o ideal para o desenvolvimento e implementação Webservices RESTful.

O JSON baseia-se em algumas estruturas básicas. Por exemplo:

• Uma coleção de pares nome/valor em que o nome e o valor são separados por dois
pontos ':' e cada par pode ser separado por uma vírgula ','.

• Uma lista ordenada de valores que estão entre parêntesis retos ('[]').

Isso torna muito fácil construir estruturas que representam qualquer conjunto de dados. Por
exemplo, um livro com ISBN, título (title), autor (author) e preço (price) pode ser representado
por:
{
"author": "Phoebe Cooke",
"isbn": 2,
"price": 12.99,
"title": "Java"
}

Por sua vez, uma lista de livros pode ser representada por um conjunto de livros separados por
vírgulas dentro de parênteses retos. Por exemplo:
[ {"author": "Gryff Smith","isbn": 1, "price": 10.99, "title":
"XML"},
{"author": "Phoebe Cooke", "isbn": 2, "price": 12.99, "title":
"Java"}
{"author": "Jason Procter", "isbn": 3, "price": 11.55, "title":
"C#"}]

5.2. Implementação de um Serviço Web com o Flask


Existem várias etapas envolvidas na criação de um serviço web Flask, são elas:

1. Importaçao do Flask.

2. Inicializar a aplicação Flask.

3. Implementar uma ou mais funções (ou métodos) para dar suporte aos serviços que
se pretende publicar.

4. Fornecer informações de roteamento para rotear da URL para uma função (ou
método).

5. Iniciar o serviço web colocando-o em execução.

Seguidamente iremos ver com mais detalhe a implementação destas etapas.

4
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

5.3. Exemplo de Implementação de um Serviço Web simples


Agora vamos criar o nosso serviço web hello world. Para isso, devemos primeiro importar o
módulo do Flask. Neste exemplo usaremos a classe Flask e jsonify() como função de
elementos do módulo.

Em seguida, precisamos criar o objeto da aplicação principal que é uma instância da classe
Flask:

O argumento passado para o construtor Flask() é o nome do módulo ou pacote da aplicação.


Sendo este um exemplo simples, usaremos o __name__ atributo do módulo que neste caso
será ‘__main__’. Dentro de aplicações maiores e mais complexas, com vários pacotes e
módulos, poderá ser preciso escolher o nome apropriado do pacote.

O objeto de aplicativo Flask implementa o WSGI (Web Server Gateway Interface) padrão para
Python. Ele fornece uma simples convenção de como os servidores da Web devem lidar com
solicitações das aplicações. O objeto de aplicação Flask é o elemento que pode rotear um pedido
de URL para uma função Python.

5.4. Fornecimento de Informação de Roteamento


Agora podemos definir informações de roteamento para o objeto de aplicação Flask. Estas
informações irão mapear uma URL para uma função Python. Quando esse URL é, por exemplo,
inserido no campo de URL de um browser da Web, o objeto da aplicação Flask receberá esse
pedido e invocará de seguida a função apropriada

Para fornecer informações de mapeamento de rotas, usamos o decorador @app.route numa


função (ou método).

Por exemplo, no código seguinte, o decorador @app.route mapeia a URL ‘/hello’ para a
função welcome() para solicitações HTTP Get:

Há duas coisas a serem observadas sobre esta definição de função:

• Primeiramente, o decorador @app.route é usado para especificar declarativamente


informação de roteamento para a função. Isso significa que a URL ‘/hello’ será mapeado
para a função welcome(). O decorador também especifica o método HTTP que é suportado;
neste caso, solicitações GET são suportadas (o que, na verdade, é o padrão, portanto e em
princípio, não precisaria de ser incluído aqui, mas é útil do ponto de vista de documentação).

• A segunda coisa é que vamos retornar os nossos dados usando o formato JSON; portanto,
usamos a função jsonify() e passamos-lhe uma estrutura do Dicionário Python com um único par

5
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

chave/valor. Neste caso a chave é ‘msg’ e os dados a esta associados é 'Hello Flask
World'. A função jsonify() converterá esta estrutura de dados Python numa estrutura
JSON equivalente.

5.5. A Execução do Serviço


Estamos agora prontos para executar nossa aplicação. Para isso, invocamos o método run()
do objeto da aplicação Flask:

Opcionalmente, este método tem um parâmetro de depuração de palavra-chave que pode ser
definido como True. Se for esta a opção escolhida, durante o arranque da aplicação, algumas
informações de depuração são geradas permitindo visualizar o que está a acontecer. Isto pode
ser útil na fase de desenvolvimento, mas normalmente não é usado na fase de produção.

O programa completo é o seguinte:

Quando este programa é executado, o output inicial gerado é a mostrado abaixo:

É claro que ainda não visualizamos nenhuma saída do nosso próprio programa. Isto acontece
porque ainda não invocamos a função welcome() por meio da URL ‘/hello’.

5.6. Invocação do Serviço


Usaremos um browser para aceder ao serviço web que acabámos de desenvolver. Para isso,
devemos inserir o URL completo que encaminhará a solicitação para a nossa aplicacação em
execução e para o a função welcome(). Na verdade a URL é composta por dois elementos:
a primeira parte refere-se à máquina na qual a aplicação está a ser executada e a segunda parte
indica o nº do porto em que a aplicação fica à escuta de solicitações. Com efeito, esta informação
encontra-se listada na saída acima – é só conferir a linha que começa com ‘Running on…’. O que

6
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

significa que a URL deve começar com http://127.0.0.1:5000, indicando que a


aplicação está a ser executado no computador com o endereço IP 127.0.0.1 e à escuta na porta
5000. Opcionalmente, também poderíamos usar a expressão localhost em vez de 127.0.0.1.

O restante do URL deve fornecer as informações que permitirão ao Flask fazer o roteamento do
computador e porto para as funções que queremos executar.

Assim, o URL completo é http://127.0.0.1:5000/hello e, portanto, é usado num


browser como exemplificado na figura seguinte:

Como se pode constatar, o resultado retornado é o texto que fornecemos à função


jsonify(), mas agora no formato JSON simples e exibido no browser. É também possível ver
no output da consola que uma solicitação foi recebida pela estrutura Flask para o método GET
mapeado para a URL ‘/hello’:

Uma característica útil desta abordagem é que se for feita uma alteração no programa a
framework Flask anotará essa alteração quando se executa no modo de desenvolvimento
(development mode), podendo reiniciar o serviço web com as alterações de código implantadas.
Caso isso seja feito, o output notificará a ocorrência da alteração do programa:

Isto permite que as alterações sejam feitas em tempo real e os seus efeitos podem ser
imediatamente vistos.

5.7. A Solução Final


Podemos organizar um pouco este exemplo definindo uma função que pode ser usada para criar
o objeto de aplicação Flask e garantir que só executamos a aplicação se o respetivo código está
a ser executado como módulo principal (main module):

7
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Um recurso que adicionamos a este programa é o uso do test_request_context(). O


objeto de contexto de solicitação de teste retornado implementa o protocolo gestor de contexto
e, portanto, pode ser usado por meio de uma instrução with; isto é útil para fins de depuração
de erros. Pode ainda ser empregue para conferir o URL usado para qualquer função com
informações de roteamento especificadas. Neste caso, a saída da instrução print é
'/hello' pois este é o URL definido pelo decorador @app.route.

6. Referências

• https://wiki.python.org/moin/WebFrameworks for a very extensive list of web frameworks for


Python.

• https://www.djangoproject.com/ for information on Django.

• http://www.web2py.com/ Web2py web framework documentation.

• https://cherrypy.org/ For documentation on the CherryPy web framework.

• http://flask.pocoo.org/ For information and examples on the Flask web development


framework.

• http://flask.pocoo.org/docs/1.0/foreword/#what-does-micro-mean Flasks explanation of


what micro means.

• https://www.json.org/ Information on JSON.

• https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface WSGI Web Server Gateway


Interface standard.

• https://curl.haxx.se/ Information on the curl command line tool.

• https://developer.mozilla.org/en-US/docs/Web/HTTP/Status HTTP Response Status Codes.

8
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Exemplo de Criação Serviço Web Bookshop


1. Implementação do Serviço Bookshop com a Framework Flask
Anteriormente foi mostrada a estrutura básica de uma aplicação que implementa um serviço
web muito simples. Estamos agora em condições de explorar a criação de um conjunto de
serviços web para algo um pouco mais realista: uma aplicação que implementa um serviço web
de uma livraria (Bookshop).

Implementaremos o conjunto de serviços web descritos anteriormente no enunciado anterior


para uma livraria muito simples. Isso significa que vamos definir serviços para lidar não apenas
com pedidos GET, mas também com pedidos de tipo PUT, POST e DELETE para a API RESTful da
Bookshop.

2. O Design
Antes de olharmos para a implementação da API RESTful Bookshop, vamos considerar quais
elementos temos para os serviços de serviços.

Uma questão que muitas vezes causa alguma confusão é como os serviços web se relacionam
com abordagens de design tradicionais, como o design orientado a objetos. A abordagem
adotada aqui é que a API do serviço web fornece uma maneira de implementar uma interface
para funções, objetos e métodos apropriados usados para implementar a aplicação e centrado
no modelo de domínio1.

Isto significa que ainda teremos que definir classes que representarão a Livraria (Bookshop) e os
Livros (Books) mantidos na livraria. Por sua vez, as funções que implementam os serviços web
acederão à livraria para procurar/pesquisar, alterar, atualizar e remover livros da livraria.

O projeto geral é mostrado abaixo:

Pode-se observar que um objeto Book terá como atributos, um isbn, um título (title), um autor
(author) e um preço (price), respetivamente.

Por sua vez, o objeto Bookshop terá um atributo books que poderá ter zero ou mais Books. O
atributo books irá conter uma Lista (List), considerando que a lista de livros muda
dinamicamente à medida que novos livros são adicionados ou livros antigos são removidos.

A Bookshop também definirá os três seguintes métodos que irão permitir que um livro seja:

1
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

• Obtido através do seu isbn,

• Adicionado à lista de livros e

• Removido (com base em seu isbn).

As informações de roteamento serão fornecidas para um conjunto de funções que invocarão


métodos apropriados do objeto Bookshop. As funções a serem decoradas com @app.route e
os mapeamentos a serem usados são as seguintes:

• get_books() que mapeia para a URL /book/list usando o método HTTP Get

• get_book(isbn) que mapeia para a URL /book/<isbn> onde isbn é um parâmetro


de URL que será passado para a função. Aqui também se usará o método HTTP Get.

• create_book() que mapeia para a URL /book usando o método HTTP Post.

• update_book() que mapeia para a URL /book mas usando o método HTTP Put.

• delete_book() que mapeia para a URL /book/<isbn> mas usando o método HTTP
Delete.

3. O Modelo de Domínio
O modelo de domínio compreende as classes Book e Bookshop. Estas são apresentados a seguir.

A classe Book é uma classe simples classe de tipo Value (ou seja, é orientada a dados sem
comportamento próprio):

class Book:
def __init__(self, isbn, title, author, price):
self.isbn = isbn
self.title = title
self.author = author
self.price = price
def __str__(self):
return self.title + ' by ' + self.author + ' @ ' + str(self.price)
A classe Bookshop contém uma lista de livros e fornece um conjunto de métodos para aceder a
livros, atualizar livros e remover livros:

class Bookshop:
def __init__(self, books):
self.books = books
def get(self, isbn):
if int(isbn) > len(self.books):
abort(404)
return list(filter(lambda b: b.isbn == isbn, self.books))[0]
def add_book(self, book):
self.books.append(book)
def delete_book(self, isbn):
self.books = list(filter(lambda b: b.isbn != isbn, self.books))

2
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

No código acima, o atributo books contém a lista de livros atualmente acessível.


O método get() retorna um livro com um ISBN especificado.
O método add_book() adiciona/insere um objeto livro à lista de livros.
O método delete_book() remove um livro com base em seu ISBN.
A variável global bookshop contém o objeto Bookshop inicializado com um conjunto padrão
de livros:

bookshop = Bookshop(
[Book(1, 'XML', 'Gryff Smith', 10.99),
Book(2, 'Java', 'Phoebe Cooke', 12.99),
Book(3, 'Scala', 'Adam Davies', 11.99),
Book(4, 'Python', 'Jasmine Byrne', 15.99)])

4. Conversão da classe Books em JSON


Embora a função jsonify() saiba converter tipos como strings, inteiros, listas, dicionários
etc. no formato JSON; mas não sabe como fazer isso para tipos definidos pelo programador,
como, por exemplo, um livro (Book).
Precisamos, portanto, de definir uma maneira de converter um Book no JSON.
Uma maneira de fazer isso seria definir um método que pode ser chamado para converter uma
instância da classe Book em um formato JSON. Poderíamos chamar isso método to_json().
Por exemplo:

class Book:
""" Represents a book in the bookshop"""
def __init__(self, isbn, title, author, price):
self.isbn = isbn
self.title = title
self.author = author
self.price = price
def __str__(self):
return self.title + ' by ' + self.author + ' @ ' + str(self.price)
def to_json(self):
return {
'isbn': self.isbn,
'title': self.title,
'author': self.author,
'price': self.price
}

Sendo assim, podemos usar este método com a função jsonify() para converter um Book
no formato JSON:

jsonify({'book': book.to_json()})

Esta abordagem fornece uma maneira simples de converter um Book em JSON, significando
que sempre que precisarmos de fazer a conversão temos que nos lembrar de chamar o método
to_json(). Em alguns casos, isso significa que também teremos que escrever um código um

3
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

pouco complicado. Para exemplo, consideremos que pretendemos retornar uma lista de livros
(books) da Livraria (Bookshop) como uma lista JSON, para tal podemos escrever:

jsonify({'books': [b.to_json() for b in bookshop.books]})

Aqui usamos uma compreensão de lista para gerar uma lista contendo versões de JSON dos
books (livros) mantidos na Bookshop (livraria), o que poderá começar a parecer
excessivamente complexo, fácil de esquecer e, provavelmente, propenso à ocorrência de erros.

O próprio Flask usa codificadores para codificar tipos em JSON. Oferecendo uma maneira de se
criar codificadores próprios para serem usados na conversão de um tipo personalizado, como a
classe Book, em JSON. Tal codificador pode ser usado automaticamente pela função
jsonify().
Para fazer isso, devemos implementar uma classe de codificador; a classe estenderá a
superclasse flask.json.JSONEncoder.
A classe deve definir um método default(self, obj). cuja função é a de pegar um objeto
e retornar a representação JSON desse objeto.
Podemos, portanto, escrever um codificador para a classe Book da seguinte forma:

class BookJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Book):
return {
'isbn': obj.isbn,
'title': obj.title,
'author': obj.author,
'price': obj.price
}
else:
return super(BookJSONEncoder, self).default(obj)

O método default() desta classe verifica se o objeto que lhe é passado é uma instância da classe
Book e, caso o seja, criará uma versão JSON de Book. Essa estrutura JSON é baseada nos
atributos isbn, title, author e price. Se não for uma instância da classe Book, então esse objeto
é passado à classe pai.
Agora podemos registar este codificador no objeto de aplicação Flask para que ser usado sempre
que um Book precisar de ser convertido em JSON. Isto é feito atribuindo o codificador
personalizado ao objeto da aplicação Flask por meio do atributo app.json_encoder:

app = Flask(__name__)
app.json_encoder = BookJSONEncoder

Se desejarmos codificar um único livro (Book) ou uma lista de livros (books), isso pode ser feito
automaticamente sem precisarmos de fazer mais nada. Para o efeito, e em relação aos exemplos
anteriormente descritos, podemos simplesmente referenciar o atributo book ou o atributo
bookshop.books da seguinte maneira:

jsonify({'book': book})
jsonify({'books': bookshop.books})

4
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

5. Configuração do Serviço GET


Vamos configurar os dois serviços que irão suportar pedidos GET, sendo eles respetivamente:

• O serviço /book/list e o serviço /book<isbn>.

As funções esses URLs mapeiam são as seguintes:

@app.route('/book/list', methods=['GET'])
def get_books():
return jsonify({'books': bookshop.books})
@app.route('/book/<int:isbn>', métodos=['GET'])
def get_book(isbn):
livro = livraria.get(isbn)
return jsonify({'book': book})

A primeira função retorna a lista de livros (books) da livraria (bookshop) numa estrutura JSON
usando a chave books.
A segunda função recebe um número isbn como parâmetro. Este é um parâmetro URL; que
constitui uma parte da URL usada para invocar esta função e assume valores variáveis a serem
passados para à função. Por conseguinte, um utilizador pode solicitar detalhes de livros com
diferentes ISBNs, alterando apenas o elemento ISBN do URL. Por exemplo:
• /book/1 indica que queremos obter informações sobre o livro com ISBN 1.
• /book/2 indica que queremos obter informações sobre o livro com ISBN 2.

No Flask para indicar que algo é um parâmetro de URL usam-se os símbolos (<>). Estes delimitam
o nome do parâmetro URL e permitem que o mesmo seja passado para a função mantendo o
mesmo nome.

No exemplo anterior, também foi (opcionalmente) indicado o tipo do parâmetro. Por omissão,
o tipo será uma string; no entanto, sabemos que o ISBN é, na verdade, um inteiro e, portanto,
tivemos de explicitar o tipo int seguido de : (dois pontos) antes do nome do parâmetro (‘isbn’).
Neste contexto existem várias outras opções disponíveis, tais como:
• string (por omissão),
• int (para valores inteiros),
• float (para valores positivos em vírgula flutuante)
• uuid (para strings de tipo uuid2)
• path (que não gosta de string, mas aceita barras).

Podemos usar novamente um browser para visualizar os resultados da chamada desses serviços.
desta vez os URLs são, respetivamente:

• http://127.0.0.1:5000/book/list e

• http:/127.0.0.1:5000/book/1

Por exemplo:

2
https://pt.wikipedia.org/wiki/Identificador_%C3%BAnico_universal

5
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Como se pode ver, as informações do(s) livro(s) são retornadas no formato JSON como um
conjunto de pares chave/valor.

6. Remoção de um livro (serviço DELETE)

O serviço web de remoção de um livro é muito semelhante ao serviço GET (para obtenção de
informações de um livro), pois usa um isbn como parâmetro de caminho de URL. No entanto,
este caso, ele apenas retorna uma confirmação de que o livro foi removido com sucesso da
livraria:

@app.route('/book/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
bookshop.delete_book(isbn)
return jsonify({'result': True})

Com um browser não é possível testar se o livro foi removido, uma vez que este utiliza apenas
HTTP Get para todos os URLs nele inseridos. No entanto, o serviço web relativo à remoção está
associado ao pedido do método HTTP Delete.
Para invocar a função delete_book(), precisamos garantir que o pedido enviado usa o método
DELETE. Isto pode ser feito a partir de um cliente capaz de poder indicar o tipo de método que
pretende usar no pedido HTTP. Exemplos que concretizem isto podem ser a implementação de
outro programa em Python, um site implementado em JavaScript, etc.

Aqui e para fins de teste, vamos usar programa cur3l, que se encontra disponível na maioria dos
sistemas Linux e Mac e pode ser facilmente instalado noutros sistemas operativos.

O curl é uma ferramenta de linha de comando e biblioteca que pode ser usada para enviar e
receber dados pela internet. Suporta uma ampla gama de protocolos e padrões e, em particular,
suporta os protocolos HTTP e HTTPS e pode ser usado para enviar e receber dados por HTTP/S
usando diferentes métodos associados a pedidos destes protocolos.

3
https://pt.wikipedia.org/wiki/CURL#cURL

6
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Por exemplo, para invocar a função delete_book() usando a URL /book/2 e o método HTTP
Delete podemos usar o curl da seguinte forma:

curl http://localhost:5000/book/2 -X DELETE


Este comando indica que queremos invocar a URL (http://localhost:5000/book/2) e que
desejamos usar um método específico no pedido (ou seja, não o GET padrão), que é, neste caso,
o DELETE (como indicado pela opção −X). O resultado retornado pelo comando é fornecido
abaixo indicando que o livro foi removido com sucesso.

"result": true

Podemos verificar a ocorrência de remoção do livro, através de um browser com o URL /book/list
:

Confirmando de facto que o livro com o ISBN igual a 2 foi removido.

7. Inserção de um Livro

Também vamos precisar de inserir de um novo livro (book) na Livraria (BookShop). Os detalhes
de um novo livro podem ser passados ao URL como parâmetros de caminho de URL; porém
como a quantidade de dados a serem adicionados cresce, esta operação se tornaria cada vez
mais difícil de manter e verificar. De facto, embora historicamente houvesse um limite de 2083
caracteres no Internet Explore (IE) da Microsoft, que teoricamente foi removido desde o IE8, na
prática, normalmente ainda há limites no tamanho da URL. A maioria dos web servers têm um
limite de 8 KB (ou 8192 bytes) para o campo URL, embora isso seja normalmente configurável.

Também pode haver limites do lado do cliente (como os impostos pelo IE ou o Safari da Apple,
(que geralmente têm um limite de 2 KB). Geralmente, se num browser ou servidor esse limite
for excedido, os caracteres fora do limite serão truncados (em alguns casos sem qualquer aviso).

7
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Normalmente, esses dados são enviados no corpo do pedido HTTP como parte de um HTTP Post
Request. O limite do tamanho de uma URL no corpo de uma mensagem HTTP Post Request é
muito superior (geralmente até 2 GB). Isso significa que é uma maneira muito mais confiável e
seguro de transferir dados para um serviço web.

No entanto, deve-se notar que isso não significa que os dados estão mais seguros do que através
do URL; apenas que são enviados de uma maneira diferente.

Do ponto de vista das funções Python que são invocadas como resultado de um pedido do
método HTTP Post, significa que os dados não estão disponíveis como parâmetro para a URL e,
portanto, para a função. Em vez disso, dentro da função é necessário obter o objeto do pedido
e, em seguida, usá-lo para obter as informações mantidas dentro do corpo do pedido.

Um atributo-chave no pedido de objeto, disponível quando uma solicitação HTTP contém dados
JSON, é o request.json. Este contém um dicionário como estrutura com os valores associados às
chaves da estrutura de dados JSON.

Isso é mostrado abaixo para a função create_book():


from flask import request, abort
@app.route('/book', methods=['POST'])
def create_book():
print('create book')
if not request.json or not 'isbn' in request.json:
abort(400)
book = Book(request.json['isbn'],
request.json['title'],
request.json.get('author', ""),
float(request.json['price']))
bookshop.add_book(book)
return jsonify({'book': book}), 201

A função acima acede ao objeto flask.request que representa o atual pedido HTTP. A função
primeiro verifica se contém dados JSON e que o ISBN do livro a ser adicionado faz parte dessa
estrutura JSON. Em caso negativo, então é chamada a função flask.abort() passando como
resposta um adequado HTTP Status Code. Neste caso em concreto, foi passado à resposta o
código de erro de Bad Request (código de erro HTTP 400).

Se, no entanto, os dados JSON estiverem presentes e contiverem um número ISBN, os valores
para as chaves isbn, title, author e price são obtidos. Convém lembrar que o JSON é uma
estrutura tipo dicionário composta de chaves e valores, que facilita a extração dos dados que
uma estrutura JSON contém. Significa também que podemos ter tipos de acesso baseados quer
em métodos, quer em chaves. Isto é mostrado acima onde utilizamos o método get( ) junto com
um valor padrão a ser usado, se um autor (author) não for especificado.

8
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Finalmente, como queremos tratar o preço como um número de tipo vírgula flutuante (floating
point), devemos usar o função float( ) para converter o formato string fornecido pelo JSON em
um float.

Usando os dados extraídos podemos criar uma nova instância Book que pode ser inserido na
livraria. Como é comum em web services, retorna-se o novo objeto livro criado como resultado
da criação do livro junto com a resposta HTTP Code Staus Nº 201, que indica a criação bem-
sucedida de um recurso.

Agora podemos testar este serviço usando o programa de linha de comando curl:
curl -H "Content-Type: application/json" -X POST -d
'{"title":"Read a book", "author":"Bob ","isbn":"5", "price":"3.44"}'
http://localhost:5000/book

As opções usadas neste comando indicam o tipo de dados que está a ser enviado no corpo (body)
do pedido (-H) juntamente com os dados a incluir no corpo do pedido (-d). O resultado da
execução deste comando é:

{
"book": {
"author": "Bob",
"isbn": "5",
"price": "3.44",
"title": "Read a book"
}
}

Mostra que o novo livro de Bob foi inserido na livraria.

8. Atualização de dados de um livro

A atualização de um livro que já está em posse do objeto livraria é muito semelhante à operação
de inserção, exceto que o método usado pelo pedido é o HTTP Put.

Mais uma vez, a função que implementa o comportamento requerido deve usar um pedido flask
para aceder aos dados enviados junto com o pedido PUT. Contudo, neste caso, o número ISBN
especificado é usado para localizar o livro que se pretende atualizar, em vez de especificar um
livro completamente novo. O código da função update_book() é o seguinte:

@app.route('/book', methods=['PUT'])
def update_book():
if not request.json or not 'isbn' in request.json:
abort(400)
isbn = request.json['isbn']
book = bookshop.get(isbn)
book.title = request.json['title']
book.author = request.json['author']
book.price = request.json['price']
return jsonify({'book': book}), 201

9
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

Esta função altera o title, author e price de um livro da livraria, retornando-o atualizado como
resultado da execução da função.

O programa curl pode ser usado novamente para invocar esta função, sendo que desta vez o
método usado é o HTTP Put:
curl -H "Content-Type: application/json" -X PUT -d

'{"title":"Read a Python Book", "author":"Bob Jones", "isbn":"5", "price":"3.44"}'


http://localhost:5000/book

O output deste comando é:


{
"book": {
"author": "Bob Jones",
"isbn": "5",
"price": "3.44",
"title": "Read a Python Book"
}

Mostrando que o livro 5 foi atualizado com novas informações.

9. O que Acontece se Erramos na Especificação do Livro?

O código apresentado para os serviços web da livraria não faz as devidas validações, pois é
possível tentar inserir um novo livro com o mesmo ISBN de um já existente. Não obstante, pode-
se verificar se um número ISBN já foi atribuído, usando as funções create_book( ) e
update_book( ). Caso um número ISBN não foi atribuído? Em ambas as funções chamamos a
função flask.abort( ). Se isso acontecer, por omissão, uma mensagem de erro será enviada de
volta ao cliente.

Por exemplo, no comando a seguir, esquecemos de incluir o número do ISBN:


curl -H "Content-Type: application/json" -X POST -d

'{"title":"Read a book", "author":"Tom Andrews", "price":"13.24"}'


http://localhost:5000/book
O que gera o seguinte output assinalando um erro:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">

<title>400 Bad Request</title>


<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could
not understand.</p>

10
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

O estranho aqui é que o output do erro está no formato HTML, que não é o que esperávamos,
pois estamos a cria um serviço web e a trabalhar com o JSON. O problema é que o Flask tem
como padrão gerar uma página HTML de erro que espera ser mostrado num browser.
Podemos superar esta situação definindo uma nossa função personalizada de tratamento de
erros que será uma função decorada com um decorador @app.errorhandler( ) que fornece o
código de status de resposta que ele manipula. Por exemplo:
@app.errorhandler(400)

def not_found(error):
return make_response(jsonify({'book': 'Not found'}), 400)
Agora, quando um código 400 é gerado através da função flask.abort( ), a função not_found( )
será invocada e uma resposta JSON será gerada com as informações fornecidas pela função
flask.make_response(). Por exemplo:
curl -H "Content-Type: application/json" -X POST -d

'{"title":"Read a book", "author":"Tom Andrews", "price":"13.24"}'


http://localhost:5000/book
O output deste comando é:
{

"book": "Not found"


}

11
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

10. Listagem do Código do Serviço Web BookShop

12
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

13
Sistemas Distribuídos 3º Ano LEIT ISECMAR (UTA)

14

Você também pode gostar