Java para Web

Aula 07 : Express.js

Aula 07 - Express.js

Apresenta a biblioteca Express.js e sua aplicação na criação de serviços Web

Exemplos de aula

Web Service

O que é um web service?
Modelo de Web Service

Um Web Service, ou simplesmente Serviço Web, é normalmente um software/dispositivo que tem como objetivo prover dados para outras aplicações através do protocolo http/https. Isso significa que ao invés de fornecer recursos à usuários finais, cujo a informação tem fim em si mesma, os usuários de serviços web são normalmente outras aplicações, construídas para enviar e receber dados através desses serviços.

Um serviço web pode ser construído, por exemplo, para funcionar como uma camada intermediária entre os dados armazenados e uma interface web, sendo responsável pelo tratamento e entrega dos dados a serem apresentados. Como exemplo, podemos citar outras utilizações para um webservice:

  • Ler os dados de sensores e fornecer esses dados para um aplicativo mobile através da Internet;
  • Verificar e fornecer um serviço de autenticação para múltiplicas aplicações dentro da rede de uma empresa;
  • Enviar mensagens através de um BOT para mensageiros instantâneos;
  • Receber fotos de uma aplicação desktop através da Internet, armazenando-as de modo seguro.
O mais importante a ser destacado é que, como fica evidente no seu nome, um serviço web sempre se comunica através da Web e seus protocolos.

Um web service é geralmente considerado um tipo de API (Application Programming Interface - Interface de programação de aplicações), já que oferece um ponto de interface capaz de interagir com uma aplicação/código externo.

Exemplos de Web Services

Atualmente podemos encontrar milhares de API disponíveis de maneira pública na Internet. Alguns exemplos:

Algumas APIs requerem autenticação. Isso significa que para realizar requisições é necessário ter algum tipo de cadastro realizado previamente. Também

Protocolos na Web
modelo tcp/ip
Para grande parte das trocas de dados na web, precisamos utilizar alguns protocolos importantes na troca de mensagens. De um modo simplificado, protocolos são regras que definem como uma mensagem deve ser enviada e como deve ser interpretada. São utilizados geralmente com diferentes finalizades e de forma hierarquizada.

TCP e IP

modelo tcp/ip

Estes dois protocolos de camadas mais inferiores são geralmente utilizados como base para a troca de informações na web. Enquanto o protocolo IP (Camada Internet) provê o roteamento da mensagem através dos servidores, fazendo com que os pacotes cheguem ao destino correto, o protocolo TCP (Camada Transporte) garante a integridade e a entrega de todos os pacotes. Tipicamente, requisições que chegam nessa camada acima do MTU (Maximum Transmission Unit) são divididos em vários pacotes a serem entregues.

DNS

exemplo de requisição DNS

Todo dispositivo presente em uma rede de computadores da família TCP/IP é identificado virtualmente através de um endereço IP. Esse endereço é adequado para os computadores, porém de difícil memorização para seres humanos. Por isso, cada endereço IP pode ter um ou mais nomes de domínio atrelados que facilitam sua identificação e a organização dos serviços na web.

Quando acessamos um webservice através de um nome de domínio (exemplo.com.br), é necessário obter antes o endereço de IP do servidor que irá realizar a resposta da requisição. Esse processo também é conhecido como forward lookup.

Nesse processo, o cliente realiza um requisição para o servidor de DNS local (geralmente configurado na máquina ou pelo provedor de internet). Se este servidor DNS não possuir a informação do endereço IP correspondente aquele domínio, ele informa o endereço de outro servidor (geralmente um servidor mais próximo da raiz) onde aquele endereço poderá ser encontrado. Este mesmo servidor volta a realizar uma requisição (DNS Query) até que o endereço de IP referente àquele servidor seja encontrado.

Geralmente os nomes de domínio são gerenciados por agências locais (como o NIC.br no Brasil) que padronizam, definem e comercializam os nomes de domínio. Uma outra característica importante do DNS é que os nomes são organizados de maneira hierárquica, ou seja, a cada ponto temos um agrupamento de domínios que geralmente possuem um representante centralizado. Por exemplo, no domínio professor.venson.net.br, possuimos a seguinte hierarquia:

  1. .br- TLD (Top-Level Domain). Delegado pela IANA, administrado pelo NIC.br e comercializado no site registro.br
  2. .net.br- Subdivisão do domínio brasileiro. Responsabilidade do NIC.br
  3. venson.net.br - Nome de domínio registrado para uma pessoa física / júridica (whois)
  4. professor.venson.net.br - Subdivisão do domínio registrado. Administrado e configurado pelo dono do domínio.

Mais informações aqui

HTTP

exemplo de requisição http

O Hypertext Transfer Protocol é um protocolo do tipo ou requisição/resposta. É um protocolo sem estado, o que significa que ele não pode identificar relação entre duas requisições distintas. Isso pode ser um grande problema quando trabalhamos com aplicações que exigem, por exemplo, algum tipo de autenticação. Nesses casos, é necessário que o controle de sessão seja feito pela aplicação que usa o protocolo. Uma mensagem HTTP é geralmente composto por três partes:

  1. Linha de requisição, onde especificamos três parâmetros:
    • METHOD, que define a ação da requisição (por exemplo, GET significa que queremos obter algum dado/recurso);
    • HEADER, que define um dicionário contendo informações que serão de interesse de quem interpretará a mensagem (como por exemplo, User-Agent identifica o cliente/navegador usado na requisição);
    • BODY, o corpo da mensagem que pode conter strings especiais, texto plano, JSON, binários, etc. Esta parte da mensagem é geralmente apenas de interesse da aplicação. No caso de um website, o código-fonte da página é entregue dentro dessa área.

Por padrão, a transferência de mensagens do HTTP é realizada na porta 80, definida no protocolo TCP. É necessário, também, que a conexão com o servidor seja estabelecida de maneira previa ao envio da requisição HTTP, já que de fato é um protocolo que se encontra na camada de aplicação do modelo OSI.

Realizando conexões

Sempre que uma requisição HTTP é feita, é preciso que as camadas de rede/transporte realizem a conexão com o servidor antes de realizar qualquer requisição.

Em servidores configurados para tal, é possível especificar o cabeçalho Connection: Keep-Alive para garantir que a conexão TCP/IP não será encerrada pelo servidor após a resposta, garantindo assim que a conexão possa ser reutilizada para múltiplas requisições.

Isso

HTTPS

Mantendo suas principais características em relação à mensagem, o HTTPS se difere do HTTP por realizar a encriptação da mensagem HTTP antes da transmissão, descriptografando a mensagem após a sua chegada. Essa camada de segurança é conhecida como Transport Layer Security, ou TSL. As transações do TLS são negociadas usando um par de chaves públicas/privadas. A porta padrão utilizada pelo HTTPS é a 443.

O TSL é uma versão mais recente do Secure Sockets Layer (SSL), que também é usada para descrever a mesma camada. No entanto, é comum encontrar o termo SSL ou SSL/TLS que também possui o mesmo significado.

Formatos de Troca de Dados

A troca de mensagens através da Web permite criar requisições/respostas com diferentes tipos de conteúdo (que são carregados pelo corpo da mensagem). Podemos utilizar qualquer tipo de codificação, que geralmente é informada no cabeçalho da mensagem usando a propriedade Content-Type e o tipo MIME. Esses tipos podem ser listados abaixo (mas não se resumem a estes):

  • Texto plano genérico (text/plain). Mensagens enviadas em texto plano exigem apenas conhecer a codificação de caracteres para sua interpretação. Por padrão, a codificação mais utilizada na web é a UTF-8 (fonte 1, fonte 2).
  • Áudio (audio/mpeg). Inclui dados de áudio ou música, no formato especificado
  • Vídeo (video/mp4). Inclui dados de vídeo, no formato especificado
  • Imagem (image/png). Inclui dados de imagem, no formato especificado
  • Binário (application/octet-stream). Conteúdo em formato binário, sem uma codificação conhecida
  • PDF (application/pdf). Conteúdo em formato pdf
  • HTML (text/html). Conteúdo html, em forma de texto plano
  • Formulário (multipart/form-data). Conteúdo de um formulário HTML
  • JSON (application/json). Um arquivo de dados estruturado JSON

Mais sobre aqui

Biblioteca Express.js

Express.JS

O express é um framework utilizado em aplicações Node.js de carater minimalista que tem como objetivo oferecer um conjunto de recursos para o desenvolvimento de aplicações para a web em geral.

O framework express pode ser utilizado para construir desde servidores web com páginas estáticas até APIs (Application Programming Interfaces), oferecendo backend para outras aplicações.

O express.js pode ser instalado através de gerenciador de pacotes (como o NPM).

Site do Express.js

Primeira Aplicação Express

Dentro do seu projeto, crie um arquivo chamado index.js e inclua o seguinte código:

js index.js
var express = require('express');
var app = express();

A primeira linha importa os componentes do pacote express para dentro de uma variável chamadaexpress. Já a segunda linha é responsável por iniciar uma instância do express dentro da variável app.

Em seguida, precisamos adicionar um endpoint para nossa nova aplicação, ou seja, um caminho e uma resposta válida para atender as chamadas de um cliente HTTP:

js index.js
app.get('/', function (req, res) {
res.send('Olá Mundo!');
});

O código acima utiliza a função get() do express, passando como parâmetro um caminho de rota e uma função callback de resposta. Dessa forma, ao acessar o endereçolocalhost:3000/, o cliente deverá receber como corpo da resposta o Olá Mundo!, gerado pela funçãosend().

Por fim, é necessário que o script inicie um socket de rede em uma porta definida, para que nossa aplicação seja capaz de escutar requisições:

js index.js
app.listen(3000, function () {
    console.log('Aplicação exemplo escutando na porta 3000!');
});

A função listen() recebe como parametro uma porta escolhida e uma função callback que será executada caso tudo corra bem (e a aplicação estará finalmente aguardando por requisições).

Ao final, seu código deverá ficar assim:

js index.js
var express = require('express');
var app = express();

app.get('/', function (req, res) {
    res.send('Olá Mundo!');
});

app.listen(3000, function () {
    console.log('Aplicação exemplo escutando na porta 3000!');
});

Para executar a aplicação, use o comando:

node index.js

A aplicação deverá retornar o conteúdo Olá mundo (por padrão, em formato HTML) ao acessar a raiz do endereço na porta 3000. Para todos os outros tipos de requisição, a aplicação retorna automaticamente o status 404 (Não foi possível encontrar o recurso).

Requisições e Respostas no Express

A requisição e resposta de cada comunicação é tratada pelo express como um objeto, de modo que podem ser acessadas e manipuladas durante o processo.

Geralmente, manipulamos as requisições e respostas HTTP dentro das funções callback implementadas no fluxo de execução. Por padrão, utilizaremos os nomes de variável req eres, para nos referirmos respectivamente ao objeto de requisição e de resposta.

Valores de requisição

MétodoDescrição
req.bodyRetorna os dados do corpo da requisição.
req.ipRetorna o endereço IP remoto do remetente.
req.methodRetorna o tipo de requisição HTTP.
req.queryRetorna um objeto com a string de consulta de uma URI (ex.: o caminho/search?aprender=node+js retorna node jsquando acessamosreq.query.aprender).
req.paramsRetorna um objeto contendo os parametros definidos na rota (ex.: a rota/user/:id retorna 5 quando acessamos a rota usando o caminho /user/5).

Uma string de consulta é uma informação inserida em uma requisição HTTP como parte de uma URL, podendo ser, por exempl, parte de um formulário HTML. Uma query string é sempre inserida ao final da URL no formato ?propriedade=valor&outra_propriedade=valor. Uma URL contendo uma query string segue este exemplo: https://exemple.org/index.html?q=laranja

Métodos de resposta
MétodoDescrição
res.download()Solicita que seja efetuado o download de um arquivo.
res.end()Termina o processo de resposta.
res.json()Envia uma resposta JSON.
res.redirect()Redireciona uma solicitação.
res.send()Envia uma resposta de vários tipos.
res.sendFile()Envia um arquivo como um fluxo de octeto.
res.sendStatus()Configura o código do status de resposta e envia a sua representação em sequência de caracteres como o corpo de resposta.
Códigos de resposta

Outro atributo importante a ser usado em um objeto de resposta que permite definir o status da mensagem é o res.status(numero). O número da resposta é uma informação muito importante para o cliente e pode ser configurado antes do envio de uma mensagem, como no exemplo:

js index.js
res.status(404).send("Página não encontrada")

Para o protocolo HTTP, existe uma tabela padronizada de códigos de estados que deve ser utilizada para informar o cliente do tipo de resposta dada. Essa informação pode ser útil, por exemplo, para identificar que uma requisição falhou por falta de informações antes mesmo que o cliente precise ler o corpo da resposta.

Para mais código de estados, visite este site.

Middlewares no Express

Eventualmente em nossa aplicação temos a necessidade de adicionar funções que alteram os objetos de requisição e resposta. Isso pode ser utilizado, por exemplo, para adicionar autenticação ou desviar o fluxo de determinados tipos de requisições. Essas funções intermediárias são chamadas de middlewares.

O uso de um middleware é implementado de acordo com o seguinte exemplo:

js index.js
const app = express()
app.use(function(req, res, next) {
    console.log('Relógio:', Date.now())
    next()
})

No exemplo, utilizamos a função use(), que recebe uma função contendo a requisição (req), a resposta (res), e a próxima função de middleware (next).

Ao finalizar a execução da função de middleware, deve-se sempre adicionar o comandonext(), caso contrário a requisição será interrompida.

Podemos também adicionar um caminho específico para a montagem do middleware, como no exemplo:

js index.js
app.use('/relogio', function(req, res, next) {
    console.log('Relógio:', Date.now())
    next()
})

Dessa forma, definimos que a função callback implementada será executada apenas em requisições realizadas para o caminho /relogio.

Também é possível definir uma lista com funções middlewares, como no exemplo:

js index.js
app.use('/relogio', [funcao01, funcao02, funcao03])

O express permite funções middlewares de:

  • Aplicação (app.use ou app.METODO)
  • Roteamento (router.use)
  • Erro (app.use(err, req, res, next))
  • Nativo (express.static, express.json,express.urlencoded)
  • Terceiros (bodyparser, cookieparser)

Rotas no Express

A configuração de rotas refere-se à configuração de endpoints e suas respectivas respostas. A definição de uma nova rota segue a seguinte estrutura:

js index.js
app.METODO(CAMINHO, HANDLER)

Onde:

  • app é a instância de express
  • METODO é o tipo de solicitação HTTP (GET, POST, PUT...)
  • CAMINHO é o endereço no servidor
  • HANDLER é a função executada

É possível configurar diferentes métodos para diferentes caminhos:

js index.js
app.get('/', function (req, res) {
    res.send('Olá mundo!');
})

app.post('/', function (req, res) {
    res.send('POST RESULT');
})

O express suporta diversos métodos para atender os diferentes tipos de requisição HTTP (get,post, put, head, delete...). Além disso, há também o método all() que atende a todos as requisições independente do método HTTP utilizado.

É possível utilizar padrões de sequência para oferecer caminhos de rota que atendam critérios especiais, como:

js index.js
// Aceita bana, banana, ba234na, bababana...
app.get('/ba*na', function(req, res) {
    res.end()
})

// Aceita bana e banana
app.get('/ba(na)?na', function(req, res) {
    res.end()
})

// Aceita bana, baana, baaana...
app.get('/ba+na', function(req, res) {
    res.end()
})

// Exemplo de expressão regular (aceita butterfly, dragonfly...)
app.get(/.*fly$/, function(req, res) {
    res.send('/.*fly$/')
})
Conteúdo Estático no Express

Para fornecer arquivos estáticos através do express, podemos utilizar uma função middleware chamada express.static(). Para utilizar essa função, vamos carrega-la em nossa aplicação através da função use():

js index.js
app.use(express.static('public'))

A string public refere-se ao nome da pasta na raiz do projeto que corresponde aos arquivos que serão acessíveis estaticamente. Dessa forma, é possível por exemplo acessar o arquivo /public/img/logo.png usando a URI http://localhost:3000/img/logo.png

Pode-se usar múltiplicos diretorios estáticos, declarando diversas vezes o exemplo anterior:

js index.js
app.use(express.static('img'))
app.use(express.static('css'))
app.use(express.static('js'))

Também é possível passar como parametro o caminho de montagem, para acesso ao recurso estático:

js index.js
app.use('/static', express.static('public'))

Dessa forma, a URI utilizada para acessar o mesmo arquivo no exemplo anterior seriahttp://localhost:3000/static/img/logo.png, mesmo que o caminho virtualstatic não exista no sistema de arquivos.

Redirecionamento no Express

Um redirecionamento pode ser informado na resposta ao cliente. Repare que isso não entrega automaticamente o conteúdo do endereço redirecionado, apenas informa ao cliente para que realize outra requisição em um novo endereço.

js index.js
app.get('/', function(req, res, next) {
    res.redirect('manutencao.html')
})

Este redirecionamento é registrado nos clientes como temporário (código 302). Se você quer definir um redirecionamento definitivo, utilize o status 301, usando o comandosendStatus(301):

js index.js
res.redirect(301, 'outro.html');
Análise de Corpo de Mensagem no Express

O express não faz o parse automático de requisições HTTP, mas podemos configurá-lo para tratar as informações recebidas através das requisições, sejam elas objetos JSON ou informação de formulário. Para isso, precisamos utilizar os seguintes middlewares na aplicação:

js index.js
app.use(express.urlencoded({extended: true}));
app.use(express.json());

Lembre-se de que isso deve ser feito ANTES da declaração do middleware/rota que irá ler o corpo. A leitura do corpo da mensagem pode ser feito com req.body

Você também pode fazer isso diretamente através da biblioteca body-parser:

js index.js
var bodyParser = require('body-parser')
var jsonParser = bodyParser.json()
var urlencodedParser = bodyParser.urlencoded({ extended: false })

app.post('/usuarios', urlencodedParser, function (req, res) {
    //req.body.usuario
})

Outras Ferramentas

Insomnia
Insominia Logo

O Insomnia é um cliente para APIs open source capaz de gerar, organizar e documentar requisições REST, SOAP, GraphQL e GRPC.

Esta ferramenta é extramemente útil para realizar os testes de requisições para uma API sem a necessidade de gerar um cliente com essa finalidade.

Site Insominia

Mão na Massa

Exercício: Consultando Casa

Implemente uma aplicação que seja capaz de receber, através de query strings, uma palavra chave. O programa deverá abrir este arquivo json, e retornar a resposta com o objeto ao usuário. A pesquisa deve permitir inserir o nome de um personagem e retornar a casa ao qual ele pertence.

Exercício: Lista de Compras Express

Implemente uma aplicação utilizando Node e Express que seja capaz de gerar uma lista de compras de forma aleatória. A lista deverá conter no mínimo 1 e no máximo 15 itens, porém o usuário pode passar através de query strings o número exato de items que ele deseja receber na lista. Caso a query string seja inválida (número maior que 15 ou menor que 1 ou não seja um número), o programa deve retornar uma mensagem de erro com o status 400

Implemente também um midleware que seja capaz de salvar em um arquivo de texto todas as requisições e suas respectivas query strings de entrada.

Exercícios Complementares

Exercício: Service Cadastro de Pessoas

Implemente uma aplicação que possua dois métodos:

  • um do tipo GET, responsável por retornar uma lista com nome, email e telefone de pessoas.
  • um do tipo POST, responsável por inserir nesta lista um objeto

A lista deverá ser armazenada utilizando um método de persistência como repositório do pacoteLevelDB.

Dica: Utilize o pacote body-parser para receber os objetos e um formulário HTML para enviar os dados através do POST.

Exercício: Service Formulário Login

Crie três páginas HTML, com seu respectivo CSS:

  • O primeiro arquivo deverá se chamar login.html, e deverá conter um formulário HTML contendo campos de entrada de usuário e senha
  • O segundo arquivo deverá se chamar sucesso.html, e deverá conter uma tela com uma mensagem de sucesso no login.
  • O terceiro arquivo deverá se chamar 404.html, e deverá exibir uma mensagem de erro (recurso indisponível)

Exercício: Service Validação de Login

Implemente uma aplicação usando nodejs de realizar a validação do login:

  • O validador deverá ser implementado no lado do servidor e será responsável por validar apenas o usuário root com a senha unesc2019, enviando ao usuário a páginasucesso.html.
  • Caso a usuário ou senha não sejam validados, a página deverá retornar à páginalogin.html, apresentando uma mensagem de erro (que deverá ser tratada por um script no lado do cliente)
  • Para qualquer URI desconhecida, a aplicação deverá redirecionar para a página404.html.
  • Escreva um middleware que seja executado antes do express.static e impeça um usuário não logado de acessar a página sucesso.html.
  • Utilize um botão de deslogar na página sucesso.html.

Exercício: Service Lista Itens

Implemente um formulário HTML capaz de inserir produtos em uma lista e uma aplicação usando nodejs capaz de receber estes itens e salvá-los em um arquivo de texto. O formulário HTML também deve ser capaz de mostrar os itens armazenados no servidor. Utilize o método GET para receber dados do formulário fornecer os dados a serem inseridos.

Exercício: Retornando Imagens

Implemente uma aplicação em nodejs que retorne uma imagem armazenada no servidor caso o caminho indicado corresponda ao nome da imagem (ex.: http://localhost:3000/imagens/florestadeve retornar um arquivo chamado floresta.jpg para download).