Serviços Web

Propaganda
05/04/2017
STD29006 – Sistemas Distribuı́dos
Laboratório 3: Desenvolvendo Serviços Web em Java e Python
Professor: Emerson Ribeiro de Mello
i
http://docente.ifsc.edu.br/mello/std
Nota:
O objetivo deste laboratório é mostrar como desenvolver: (1) cliente e Serviços Web SOAP e
REST em Java, fazendo uso do Netbeans IDE; e (2) um Serviço Web REST em Python, sendo
acessado por meio do aplicativo curl.
Requisitos:
• Ter o Netbeans IDE 8.2 com Java EE e servidor de aplicação Glassfish.
• Ter instalado o ambiente python e o framework Flask (http:// flask.pocoo.org )
1
SOAP
Para o desenvolvimento do cliente e Serviços Web SOAP será feito uso da API Java para Web
services XML (JAX-WS) para desenvolvimento de cliente e Serviço Web SOAP. O JAX-WS faz uso
de Anotações Java1 visando simplificar o desenvolvimento e implantação de clientes e serviços.
O JAX-WS faz uso do JAXB 2.0, que permite mapear classes Java em XML. Ou seja, é possı́vel
empacotar objetos Java em documentos XML para que possam ser transmitidos pela rede. Para isto
as classes Java são marcadas com Anotações Java, como por exemplo, @XmlRootElement. A Figura 1
ilustra como será o desenvolvimento de cliente e Serviço Web.
Figura 1: Comunicação entre cliente e Serviço Web SOAP usando o JAX-WS
Neste laboratório serão desenvolvidos:
• Serviço Web SOAP (calculadora) com dois métodos:
– Somar: public int somar(int a, int b)
– Subtrair: public int subtrair(int a, int b)
• Cliente Desktop que invoca os métodos da Calculadora
1
https://docs.oracle.com/javase/tutorial/java/annotations
1
IFSC – Campus São José
1.1
Construindo o Serviço Web: Calculadora SOAP
1. Crie um novo projeto dentro da categoria “Java Web” escolha “Aplicação Web”.
• Dê o nome LabWebService para o projeto e clique em próximo
• Escolha servidor de aplicação Glassfish e Java EE 7. Clique em Finalizar
2. Crie um novo arquivo e dentro da categoria “Web Services” escolha “Web Service”
• Dê o nome Calculadora e para o pacote de o nome engtelecom.std. Clique em
Finalizar
3. Siga as instruções abaixo para criar os métodos somar e subtrair. Ambos terão 2
inteiros como parâmetro e retornarão um inteiro com o resultado da operação.
• Na janela com o código fonte, clique na aba DESIGN. Clique no botão Adicionar
Operação
• Clique novamente na aba código Fonte e faça a implementação dos métodos somar e
subtrair, como apresentado na Listagem 1.
4. Faça a implantação no serviço no servidor de aplicação
• Clique com botão direito sobre o nó do projeto e escolha Implantar
5. Faça um teste do serviço com um cliente simples do Netbeans
• Abra a pasta Web Services dentro do projeto e clique com botão direito sobre o
serviço Calculadora e escolha Testar Web Service
Listagem 1: Código da Calculadora SOAP
1
2
3
4
package engtelecom.std;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
5
6
7
@WebService(serviceName = "Calculadora")
public class Calculadora {
8
@WebMethod(operationName = "somar")
public int somar(@WebParam(name = "a") int a, @WebParam(name = "b") int b) {
int c = a + b;
return c;
}
9
10
11
12
13
14
@WebMethod(operationName = "subtrair")
public int subtrair(@WebParam(name = "a") int a, @WebParam(name = "b") int b) {
int c = a - b;
return c;
}
15
16
17
18
19
20
}
2
IFSC – Campus São José
1.2
Consumindo o Serviço Web: Calculadora SOAP com cliente desktop
1. Crie um novo projeto dentro da categoria “Java” escolha “Aplicação Java”.
• Dê o nome ClienteWebService para o projeto e coloque o nome da classe principal como
“engtelecom.std.Principal”. Clique em finalizar
2. Crie um novo arquivo e dentro da categoria “Web Services” escolha “Cliente para Web
Service”
3. Na localização do WSDL procure pelo projeto e encontre o serviço Calculadora
• Depois de indicar o WSDL, informe engtelecom.std como nome do pacote e clique em
Finalizar
4. Abra o arquivo Principal no editor de código e no navegador de projetos abra a pasta
“Referência de Web Services” e suas subpastas até visualizar as operações
5. Clique em cima da operação somar e arraste para o código fonte da classe Principal,
logo abaixo do método main
• Faça o mesmo para o método subtrair
• Dentro do método main faça as invocações para somar e subtrair
6. Clique com botão direito no nó do projeto e escolha “Limpar e Construir” e depois
execute o projeto
Listagem 2: Código fonte do cliente desktop invocando um serviço SOAP
22
23
package engtelecom.std;
public class Principal {
24
public static void main(String[] args) {
int i = somar(10,2);
int j = subtrair(10,2);
25
26
27
28
System.out.println("i: "+i+", j: "+j);
29
}
30
31
private static int somar(int a, int b) {
engtelecom.std.Calculadora_Service service = new engtelecom.std.Calculadora_Service();
engtelecom.std.Calculadora port = service.getCalculadoraPort();
return port.somar(a, b);
}
32
33
34
35
36
37
private static int subtrair(int a, int b) {
engtelecom.std.Calculadora_Service service = new engtelecom.std.Calculadora_Service();
engtelecom.std.Calculadora port = service.getCalculadoraPort();
return port.subtrair(a, b);
}
38
39
40
41
42
43
44
}
3
IFSC – Campus São José
2
REST
A API Java para RESTful Web Services (JAX-RS) também faz uso de Anotações Java para
o desenvolvimento de clientes e serviços REST. O Jersey é a implementação de referência para a
especificação JSR 311 e está presente no Netbeans 8.0 com JDK 7 ou superior. JAX-RS provê suporte
para criação de mensagens XML e JSON através do JAXB. A Tabela 1 apresenta as Anotações REST
do JAX-RS.
Tabela 1: Anotações do JAX-RS
Descrição
Define o caminho base da aplicação
Indica que o método abaixo da anotação irá responder
pedidos HTTP PUT
@GET
Indica que o método abaixo da anotação irá responder
pedidos HTTP GET
@POST
Indica que o método abaixo da anotação irá responder
pedidos HTTP POST
@DELETE
Indica que o método abaixo da anotação irá responder
pedidos HTTP DELETE
@Produces(MediaType.
Define o tipo MIME que será entregue por um método
TEXT PLAIN[, outros tipos])
que tiver sido anotado com @GET. Outros exemplos:
“application/xml”, “application/json”
@Consumes(type[, outros tipos])
Define o tipo MIME que será consumido pelo método
@PathParam
Para obter valores de URL e inserir como parâmetro
em um método
Anotação
@PATH(caminho)
@PUT
Neste laboratório serão desenvolvidos:
• Serviço Web REST com os seguintes recursos:
– olamundo
∗ GET – retorna “Olá Mundo” em TEXT PLAIN
∗ GET – retorna “Olá Mundo” em XML
∗ GET – retorna “Olá Mundo” em JSON
– olamundo/mensagem
∗ GET – retorna a “mensagem” em XML
∗ POST – consome uma mensagem codificada como parâmetro de formulário HTML e
retorna a “mensagem” em XML
• Cliente Desktop que consome os recursos dos Serviço REST
• Sintaxe de como usar o aplicativo curl para consumir serviço REST
4
IFSC – Campus São José
2.1
Construindo o Serviço Web REST: Ola Mundo
1. Crie um novo projeto dentro da categoria “Java Web” escolha “Aplicação Web”.
• Dê o nome LabREST para o projeto e clique em próximo
• Deixe o servidor de aplicação Glassfish e a versão do Java EE 7. Clique em Finalizar
2. Crie um novo arquivo e dentro da categoria “Web Services” escolha “Web Service RESTful a partir dos Padrões”
• Escolha Recurso Raiz Simples e clique em próximo
• Em pacote do recurso dê o nome: engtelecom.recursos
• Em caminho dê o nome olamundo
• Em nome da Classe deixe OlaMundo
• Clique em Finalizar
3. Deixe o código fonte do recurso (classe) OlaMundo conforme listagem abaixo
46
47
48
49
@Path("olamundo")
public class OlaMundo {
@Context
private UriInfo context;
50
public OlaMundo() {
}
51
52
53
@GET
@Produces(MediaType.TEXT_PLAIN)
public String olaTexto() {
return "Ola Mundo";
}
54
55
56
57
58
59
@GET
@Produces("application/xml")
public String olaXML() {
return "<?xml version=\"1.0\"?>" + "<mensagem>Ola Mundo" + "</mensagem>";
}
60
61
62
63
64
65
@GET
@Produces("application/json")
public String olaJSON() {
return "{ \"mensagem\": \"Ola Mundo\" }";
66
67
68
69
70
}
@Path("/{nome}")
@GET
@Produces("application/xml")
public String olaOutroXML(@PathParam("nome") String n) {
return "<?xml version=\"1.0\"?>" + "<mensagem>"+ n + "</mensagem>";
}
@POST
@Consumes("application/x-www-form-urlencoded")
@Produces("application/xml")
public String recebeParametroViaFORM(@FormParam("nome") String nome){
return "<?xml version=\"1.0\"?>" + "<mensagem>"+ nome + "</mensagem>";
}
71
72
73
74
75
76
77
78
79
80
81
82
83
84
}
4. Faça a implantação no serviço no servidor de aplicação
• Clique com botão direito sobre o nó do projeto e escolha Implantar
5
IFSC – Campus São José
5. Faça um teste do serviço com um cliente simples do Netbeans
• Abra a pasta Web Services dentro do projeto e clique com botão direito sobre o
nó do projeto e escolha Testar Web Service RESTful
2.2
Consumindo o Serviço Web RESTful: Cliente desktop
1. Crie um novo projeto dentro da categoria “Java” escolha “Aplicação Java”.
• Dê o nome ClienteRESTFul para o projeto e coloque o nome da classe principal como
“engtelecom.std.Principal”. Clique em finalizar
2. Crie um novo arquivo e dentro da categoria “Web Services” escolha “Cliente RESTful
Java”
• Informe ClienteRest como nome da classe
• Informe engtelecom.std.ws como nome do pacote
• Em “Selecione o recurso REST” clique no botão procurar e escolha o OlaMundo e
clique em Finalizar
3. Abra o arquivo Principal no editor de código, crie uma instância da classe ClienteRest e
invoque seus métodos.
public class Principal {
86
87
public static void main(String[] args) {
ClienteRest c = new ClienteRest();
88
89
90
System.out.println(c.olaJSON());
System.out.println(c.olaTexto());
System.out.println(c.olaXML());
System.out.println(c.olaOutroXML("STD"));
c.close();
91
92
93
94
95
}
96
97
98
2.3
99
}
Usando o curl para consumir o serviço RESTful
curl -v http://localhost:8080/LabREST/OlaMundo/olamundo/
100
101
curl -v http://localhost:8080/LabREST/OlaMundo/olamundo/joao
102
103
curl -v -X POST http://localhost:8080/LabREST/OlaMundo/olamundo -d 'nome=joao'
6
IFSC – Campus São José
3
Exercı́cio: Criando uma classe Contato para ser servida via REST
1. Crie uma classe com o nome Contato e com os atributos: nome, telefone e email.
• Crie 2 construtores: 1 construtor padrão; 1 construtor que inicia todos os atributos da
classe
• Crie métodos getters e setters para todos os atributos
2. Coloque a anotação @XmlRootElement uma linha acima da declaração da classe (public class. . . )
3. Modifique os métodos olaXML, olaJSON e olaOutroXML
• olaXML – Modifique o tipo de retorno para Response. Crie um objeto Contato com valores
estáticos e retorne este objeto
• olaOutroXML – Modifique o tipo de retorno para Response. Crie um objeto Contato com
valor para nome recebido via parâmetro e os demais estáticos. Retorne este objeto
• olaJSON – Modifique o tipo de retorno para Response. Crie um objeto Contato com valores
estáticos e retorne este objeto da seguinte forma:
104
105
Contato c = new Contato(...);
return Response.ok(c).build();
7
IFSC – Campus São José
4
Serviço REST em Python fazendo uso do framework Flask
Pacotes necessários:
• Se desejar instalar o flask em todo o sistema:
sudo apt-get install python-pip (ou sudo easy_install pip)
sudo pip install flask
sudo pip install flask-httpauth
106
107
108
• Se desejar fazer a instalação no ambiente local do usuário:
sudo apt-get install python-virtualenv
mkdir projeto-rest
cd projeto-rest
virtualenv flask
flask/bin/pip install flask
flask/bin/pip install flask-httpauth
109
110
111
112
113
114
– Para este caso, na 1a. linha do arquivo .py, deve-se colocar:
#!flask/bin/python
i
Nota:
Alguns materiais de referência sobre a linguagem python:
• https:// www.python.org/ doc/
• https:// developers.google.com/ edu/ python/
• https:// www.codecademy.com/ articles/ glossary-python
A Listagem 3 apresenta um serviço REST em Python que permite interagir com uma lista de livros
armazenada em memória e também um exemplo de como exigir a autenticação do usuário ao acessar
um recurso do Serviço Web. O serviço REST permite:
• Listar todos os livros na lista;
• Consultar dados de um único livro;
• Alterar dados de um livro;
• Excluir um livro da lista.
Na Listagem 3 acima de cada função, em forma de comentário, são apresentadas sintaxes do
aplicativo curl para interagir com cada recurso do Serviço Web.
Listagem 3: Aprendendo a usar os verbos HTTP com Flask e Python
#!/usr/bin/python
from flask import Flask, jsonify
from flask import abort
from flask import make_response
from flask import request
from flask import url_for
from flask.ext.httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
8
IFSC – Campus São José
app = Flask(__name__)
livros = [
{
'id' : 1,
'titulo' : 'Linguagem de Programacao C',
'autor' : 'Dennis Ritchie'
},
{
'id' : 2,
'titulo' : 'Java como programar',
'autor' : 'Deitel & Deitel'
}
]
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/livros
#
@app.route('/livros', methods=['GET'])
def obtem_livros():
return jsonify({'livros': livros})
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/livros/1
#
@app.route('/livros/<int:idLivro>', methods=['GET'])
def detalhe_livro(idLivro):
resultado = [resultado for resultado in livros if resultado['id'] == idLivro]
if len(resultado) == 0:
abort(404)
return jsonify({'livro': resultado[0]})
# Como invocar na linha de comando
#
# curl -i -X DELETE http://localhost:5000/livros/2
#
@app.route('/livros/<int:idLivro>', methods=['DELETE'])
def excluir_livro(idLivro):
resultado = [resultado for resultado in livros if resultado['id'] == idLivro]
if len(resultado) == 0:
abort(404)
livros.remove(resultado[0])
return jsonify({'resultado': True})
# Como invocar na linha de comando
#
# curl -i -H "Content-Type: application/json" -X POST -d '{"titulo":"O livro","autor":"Joao"}'
http://localhost:5000/livros
#
@app.route('/livros', methods=['POST'])
def criar_livro():
if not request.json or not 'titulo' in request.json:
abort(400)
livro = {
'id': livros[-1]['id'] + 1,
'titulo': request.json['titulo'],
'autor': request.json.get('autor', "")
}
livros.append(livro)
return jsonify({'livro': livro}), 201
# Como invocar na linha de comando
#
9
IFSC – Campus São José
# curl -i -H "Content-Type: application/json" -X PUT -d '{"titulo":"Novo Titulo"}' http://
localhost:5000/livros/2
#
@app.route('/livros/<int:idLivro>', methods=['PUT'])
def atualizar_livro(idLivro):
resultado = [resultado for resultado in livros if resultado['id'] == idLivro]
if len(resultado) == 0:
abort(404)
if not request.json:
abort(400)
if 'titulo' in request.json and type(request.json['titulo']) != unicode:
abort(400)
if 'autor' in request.json and type(request.json['autor']) is not unicode:
abort(400)
resultado[0]['titulo'] = request.json.get('titulo', resultado[0]['titulo'])
resultado[0]['autor'] = request.json.get('autor', resultado[0]['autor'])
return jsonify({'livro': resultado[0]})
#### Autenticacao simples ####
# Como invocar na linha de comando
#
# curl -u aluno:senha123 -i http://localhost:5000/livrosautenticado
#
@app.route('/livrosautenticado', methods=['GET'])
@auth.login_required
def obtem_livros_autenticado():
return jsonify({'livros': livros})
# Autenticacao simples
@auth.get_password
def get_password(username):
if username == 'aluno':
return 'senha123'
return None
@auth.error_handler
def unauthorized():
return make_response(jsonify({'erro': 'Acesso Negado'}), 403)
##############################
# Para apresentar erro 404 HTTP se tentar acessar um recurso que nao existe
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'erro': 'Recurso Nao encontrado'}), 404)
if __name__ == "__main__":
print "Servidor no ar!"
app.run(host='0.0.0.0', debug=True)
10
IFSC – Campus São José
Ao listar todos os livros seria interessante que já fosse apresentada a URI do recurso para obter
detalhes sobre cada livro. A Listagem 4 ilustra um exemplo de como listar todos os arquivos do
diretório corrente, onde está sendo executado o servidor, e apresenta uma solução para apresentar a
URI para mais detalhes sobre cada arquivo.
Listagem 4: Listando e transferindo arquivos com Flask e Python
#!/usr/bin/python
from flask import
from flask import
from flask import
from flask import
from flask import
from flask import
Flask, jsonify
abort
make_response
request
url_for
send_file
import os
app = Flask(__name__)
### Como listar arquivos em um diretorio e em seus subdiretorios ###
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/listaarquivos
#
@app.route('/listaarquivos', methods=['GET'])
def obtem_arquivos():
lista = []
#https://docs.python.org/2/library/os.html
for root, dirs, files in os.walk('.'):
for nome in files:
linha = {}
linha['nome'] = nome
linha['tipo'] = 'arquivo'
linha['caminho'] = root
lista.append(linha)
return jsonify({'arquivos': lista}), 201
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/listaarquivos/{extensao-desejada}
# curl -i http://localhost:5000/listaarquivos/pdf
#
@app.route('/listaarquivos/<extensao>', methods=['GET'])
def obtem_arquivos2(extensao):
lista = []
#https://docs.python.org/2/library/os.html
for root, dirs, files in os.walk('.'):
for nome in files:
if nome.endswith(extensao):
path = os.path.join(root, nome)
size = os.stat(path).st_size # in bytes
linha = {}
linha['tamanho'] = size
linha['caminho'] = root
linha['nome'] = nome
lista.append(linha)
return jsonify({'arquivos': lista}), 201
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/detalhesarquivo/nome.extensao
#
11
IFSC – Campus São José
@app.route('/detalhesarquivo/<busca>', methods=['GET'])
def detalhe_arquivo(busca):
lista = []
#https://docs.python.org/2/library/os.html
for root, dirs, files in os.walk('.'):
for nome in files:
path = os.path.join(root, nome)
size = os.stat(path).st_size # in bytes
linha = {}
linha['tamanho'] = size
linha['caminho'] = root
linha['nome'] = nome
lista.append(linha)
resultado = [resultado for resultado in lista if resultado['nome'] == busca]
if len(resultado) == 0:
abort(404)
return jsonify({'arquivo': resultado[0]})
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/detalhesarquivo
#
@app.route('/detalhesarquivo', methods=['GET'])
def lista_arquivos():
lista = []
for root, dirs, files in os.walk('.'):
for nome in files:
path = os.path.join(root, nome)
size = os.stat(path).st_size # in bytes
linha = {}
linha['tamanho'] = size
linha['caminho'] = root
linha['nome'] = nome
lista.append(linha)
return jsonify({'arquivos': [tornar_caminho_navegavel(arquivo) for arquivo in lista]})
# tornando os links navegaveis
def tornar_caminho_navegavel(arquivo):
novo_arquivo = {}
for field in arquivo:
if field == 'nome':
novo_arquivo['uri'] = url_for('detalhe_arquivo', busca=arquivo['nome'], _external=True)
novo_arquivo[field] = arquivo[field]
return novo_arquivo
# Como invocar (Use seu navegador web, p.e. Firefox)
#
# http://localhost:5000/obterarquivo/{caminho/nome.extensao}
# http://localhost:5000/obterarquivo/foto.png
#
@app.route('/obterarquivo/<path:filename>')
def download_file(filename):
if '..' in filename or filename.startswith('/'):
abort(404)
return send_file(filename,None,False) # True envia como anexo e False como embutido.
# Para apresentar erro 404 HTTP se tentar acessar um recurso que nao existe
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
if __name__ == "__main__":
print "Servidor no ar!"
app.run(host='0.0.0.0', debug=True)
12
IFSC – Campus São José
5
Como invocar um comando externo com Flask e Python
A Listagem 5 mostra como invocar um aplicativo externo fazendo: (1) uma chamada não bloqueante; (2) fazendo uma chamada bloqueante.
Listagem 5: Invocando aplicativos do sistema operacional com Flask e Python
#!/usr/bin/python
from flask import
from flask import
from flask import
from flask import
import subprocess
import os
Flask, jsonify
abort
make_response
request
app = Flask(__name__)
# Como invocar na linha de comando
# Invocando o leitor de PDFs xpdf tem um bug no ubuntu
# Editar sudo vi /usr/bin/xpdf +27 e acrescenter -exec no case
# -z|-g|-geometry|-remote|-rgb|-papercolor|-eucjp|-t1lib|-ps|-paperw|-paperh|-upw|-exec)
#
# curl -i http://localhost:5000/xpdf/{arquivo.pdf}
#
@app.route('/xpdf/<nome>', methods=['GET'])
def abre_pdf(nome):
# Chamada de processo nao bloqueante - fica em background
parametro = '-remote projetor ' + nome
p1 = os.spawnlp(os.P_NOWAIT,"xpdf","xpdf",parametro)
return jsonify({'resultado': True}), 201
# Como invocar na linha de comando
#
# curl -i http://localhost:5000/xpdf/{arquivo.pdf}/2
#
@app.route('/xpdf/<nome>/<int:pagina>', methods=['GET'])
def avanca_paginas(nome,pagina):
pagina = 'gotoPage('+str(pagina)+')'
# chamada de processo bloqueante, porem o xpdf termina logo apos a execucao
subprocess.call(['xpdf','-remote','projetor','-exec',pagina])
return jsonify({'resultado': True}), 201
# Para apresentar erro 404 HTTP se tentar acessar um recurso que nao existe
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'erro': 'Recurso Nao encontrado'}), 404)
if __name__ == "__main__":
print "Servidor no ar!"
app.run(host='0.0.0.0', debug=True)
Referências
1. http://docs.oracle.com/javaee/6/tutorial/doc/bnayn.html
2. https://netbeans.org/kb/docs/websvc/jax-ws pt BR.html
3. http://docs.oracle.com/javaee/6/tutorial/doc/gijqy.html
4. http://www.vogella.com/tutorials/REST/article.html
5. https://netbeans.org/kb/docs/websvc/rest pt BR.html
6. https://netbeans.org/kb/docs/web/jsf20-crud pt BR.html
13
IFSC – Campus São José
7. http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
8. http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
9. http://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful
10. http://blog.miguelgrinberg.com/post/writing-a-javascript-rest-client
14
IFSC – Campus São José
Download