redes integradas de telecomunicaÇÕes ii 2019 / 2020

22
Departamento de Engenharia Eletrotécnica REDES INTEGRADAS DE TELECOMUNICAÇÕES II 2019 / 2020 Mestrado Integrado em Engenharia Eletrotécnica e de Computadores 4º ano 8º semestre 1º Trabalho prático: Procurador HTTP Versão 1.1 http://tele1.dee.fct.unl.pt Luis Bernardo

Upload: others

Post on 22-Mar-2022

0 views

Category:

Documents


0 download

TRANSCRIPT

Departamento de Engenharia Eletrotécnica

REDES INTEGRADAS DE TELECOMUNICAÇÕES II

2019 / 2020

Mestrado Integrado em Engenharia Eletrotécnica

e de Computadores

4º ano

8º semestre

1º Trabalho prático:

Procurador HTTP

Versão 1.1

http://tele1.dee.fct.unl.pt Luis Bernardo

ÍNDICE

1. OBJETIVOS............................................................................................ 3

2. COMPLEMENTOS SOBRE HTTP .............................................................. 3

2.1.1. O método GET 4

2.1.2. O método CONNECT 7

2.2 Autenticação ................................................................... 7

2.3 Controlo de caching ........................................................ 8

3. COMPLEMENTOS SOBRE JAVA ................................................................ 9

3.1. IPv6 ................................................................................ 9

3.2 Sockets TCP ................................................................... 10

3.2.1. A classe ServerSocket 10

3.2.2. A classe Socket 11

3.2.3. Comunicação em sockets TCP 12

3.2.4. Exemplo de aplicação - TinyFileServ 12

3.2.5. Exemplo de cliente - HTTPClient 13

3.3 Strings ............................................................................. 15

3.3.1. Separação em componentes 15

3.3.2. Datas compatíveis com o protocolo HTTP 15

3.3.3. URLs 16

3.4 Ficheiros .......................................................................... 16

3.5 Estruturas de dados adicionais ...................................... 17

3.6 Descodificação em base64............................................. 17

4. ESPECIFICAÇÕES .................................................................................. 18

4.1 Especificação do funcionamento .................................... 18

4.2 Tarefas a realizar ............................................................ 18

4.2.1 Procurador básico inverso 19

4.2.2 Procurador básico direto 20

4.2.3 Autenticação de clientes do procurador 20

© Luis Bernardo 2

4.2.4 Reutilização da ligação browser-procurador 20

4.2.5 Caching de pedidos 20

4.3 Testes .............................................................................. 20

4.4 Desenvolvimento do trabalho ......................................... 21

Postura dos Alunos ............................................................... 21

DATAS LIMITE............................................................................................ 21

BIBLIOGRAFIA ............................................................................................ 21

© Luis Bernardo 3

1. OBJETIVOS

Familiarização com os protocolos Web e com a programação de aplicações

em Java baseadas em sockets TCP. O trabalho consiste no desenvolvimento de um

procurador Web de camada dupla (dual-stack) multi-tarefa que suporta HTTP. Este procurador

só deve realizar o comando GET e um subconjunto limitado das funcionalidades do protocolo

HTTP (relacionadas com o caching de páginas e autenticação), permitindo a sua realização num

número reduzido de horas. Pretende-se que o procurador Web satisfaça um conjunto de requisitos:

• Seja compatível com HTTP 1.1;

• Funcione como procurador Web direto ou inverso (reverse proxy);

• Suporte pedidos HTTP (GET);

• Guarde em disco uma página sempre que as regras o permitam, evitando novas

ligações ao servidor Web quando existirem novos pedidos da mesma página, mas que

seja capaz de verificar a frescura da página guardada;

• Opcionalmente, pretende-se que seja configurado um servidor Apache, para se

realizarem os testes na máquina local ao trabalho desenvolvido, e seja utilizado o

analisador de protocolos Wireshark, para validar a comunicação entre o procurador e

o servidor web.

Este trabalho complementa a aprendizagem sobre Java realizada em ST e RIT1,

acrescentando novas classes da biblioteca, introduzidas na terceira secção deste documento.

Antes, na segunda secção é apresentado um resumo das caraterísticas do protocolo HTTP

relevantes para este trabalho. Na quarta secção é apresentada a especificação completa do

procurador a realizar. É também apresentada uma descrição do projeto Netbeans que é fornecido

e das classes que deve completar.

2. COMPLEMENTOS SOBRE HTTP

Os protocolos HTTP 1.0 e HTTP 1.1 definem uma interação do tipo pedido-resposta entre

um cliente e um servidor, onde as mensagens trocadas contêm texto legível. Neste trabalho devem

ser suportadas duas versões do protocolo: HTTP 1.0 [RFC1945] e HTTP 1.1 [RFC2616]. A

mensagem de pedido, do cliente para o servidor tem a seguinte estrutura (sp = espaço ; cr lf =

“\r\n” = mudança de linha). O URL (Uniform Resource Locator) corresponde ao nome de um

ficheiro local (GET num servidor/procurador Web indireto) ou um URL completo (e.g.

http://www.fct.unl.pt/) num GET para um procurador direto:

No trabalho vai ser realizado o método GET1, destinado a ler documentos através de um

procurador indireto e indireto HTTP.

1 Embora não seja usado no trabalho, o protocolo HTTP 1.x também define o método CONNECT, destinado a criar

um túnel através de um procurador direto para pedidos HTTPS.

Método sp URL sp Versão cr lf

Nome campo cabeçalho : valor cr lf

Nome campo cabeçalho : valor cr lf

cr lf

Corpo da mensagem

Pedido

Linhas cabeçalho …

© Luis Bernardo 4

2.1.1. O método GET

Um pedido GET pode incluir diversas linhas de cabeçalhos opcionais2, que definem a data

de acesso ao servidor (Date), o nome do servidor acedido (Host) (obrigatório quando o servidor

HTTP suporta máquinas virtuais), informação de autenticação para o procurador (Proxy-

Authorization), informação de autenticação para o servidor (Authorization), lista de procuradores

visitados (Via), o número máximo de procuradores que podem ser atravessados (Max-Forwards),

o tipo de browser e sistema operativo usado (User-Agent), os formatos de dados suportados

(Accept), a língua pretendida (Accept-Language), as codificações suportadas (Accept-Encoding),

o formato de carater suportado (Accept-Charset), um teste se o ficheiro foi modificado desde

último acesso (If-Modified-Since e If-None-Match), para HTTP1.1 a indicação se pretende manter

a ligação aberta com o procurador (Proxy-Connection) ou com o servidor (Connection) e durante

quanto tempo (Keep-Alive), o tipo dos dados no corpo da mensagem (Content-Type), o número

de bytes da mensagem (Content-Length), dados de sessão (Cookie ou Cookie2), etc. Depois,

separando o cabeçalho de um corpo de mensagem opcional, existe uma linha em branco (“\r\n”).

Os vários modos de funcionamento de um procurador estão descritos no RFC 7230. No modo

direto (ou simplesmente, proxy), reencaminha o pedido memorizando o percurso, podendo

introduzir algumas transformações ao conteúdo. No modo inverso (ou, gateway), o procurador

pode esconder a identidade do cliente, funcionando como um servidor web normal para o browser.

Um exemplo de pedido a um procurador direto (mensagem 1 representada na figura) é:

GET http://tele1.dee.fct.unl.pt/page.html HTTP/1.1

Host: tele1.dee.fct.unl.pt

User-Agent: Mozilla/5.0 Gecko/20080208 Fedora/2.0.0.12

Accept-language: pt-pt,pt

Keep-Alive: 300

Proxy-Connection: keep-alive // ligação browser-proxy

O pedido HTTP gerado pelo procurador direto ao servidor Web (em tele1.dee.fct.unl.pt)

poderia ser (mensagem 2): GET /page.html HTTP/1.1

Host: tele1.dee.fct.unl.pt

User-Agent: Mozilla/5.0 Gecko/20080208 Fedora/2.0.0.12

Accept-language: pt-pt;pt

Max-Forwards: 10

Via: 1.1 Demo_RIT2

Connection: close // ligação proxy-servidor

Um procurador indireto difere do procurador direto porque esconde a identidade do cliente

inicial, substituindo todos os campos do pedido com a identidade do cliente (e.g. User-Agent)

por valores neutros.

No caso de um procurador inverso, o pedido na mensagem 1 seria

2 Pode consultar a lista exaustiva de cabeçalhos do HTTP 1.1 em https://www.w3.org/Protocols/rfc2616/rfc2616-

sec14.html

© Luis Bernardo 5

GET /page.html HTTP/1.1

Host: tele1.dee.fct.unl.pt

User-Agent: Mozilla/5.0 Gecko/20080208 Fedora/2.0.0.12

Accept-language: pt-pt,pt

Keep-Alive: 300

Connection: keep-alive // ligação browser-proxy

O pedido 1 é igual ao pedido realizado a um servidor web. Como se pretende desenvolver um

procurador inverso que esconde a identidade do utilizador, a mensagem 2 gerada pelo procurador

deve suprimir os dados pessoais3. Poderia ser:

GET /page.html HTTP/1.1

Host: tele1.dee.fct.unl.pt

User-Agent: RIT2 Proxy Demo

Accept-language: pt-pt;pt

Connection: close // ligação proxy-servidor

As mensagens de resposta têm uma estrutura semelhante aos pedidos:

O campo mais importante da resposta é o código de estado, que define o que se passou com

o pedido. Os códigos de estado devolvidos podem ser:

Código

Tipo Exemplo de razões

1xx Informação Recebido pedido, continua processamento

2xx Sucesso Ação terminada com sucesso

3xx Redirecção Necessárias mais ações para completar

4xx Erro do cliente Pedido errado, não pode ser executado

5xx Erro do servidor Servidor falhou com pedido válido

Alguns exemplos de códigos de estado úteis para o trabalho são:

• 200 OK: Sucesso - informação retornada no corpo da mensagem;

• 301 Moved Permanently: Moveu-se para URL contido no campo de cabeçalho

'Location:';

• 304 Not Modified: Ficheiro não foi modificado;

• 400 Bad Request: Pedido não entendido pelo servidor;

• 401 Unauthorized: Dados de autenticação omissos ou inválidos para servidor;

• 403 Forbidden: O ficheiro pedido não pode ser acedido;

• 404 Not found: O ficheiro pedido não existe;

3 Recomenda-se que experimente com browsers reais, com o analisador de protocolos Wireshark, para

compreender quais os campos de cabeçalho mais relevantes.

Versão sp Código estado sp Frase cr lf

Nome campo cabeçalho : valor cr lf

Nome campo cabeçalho : valor cr lf

cr lf

Corpo da mensagem

Pedido

Linhas cabeçalho …

© Luis Bernardo 6

• 407 Proxy Authentication Required: Dados de autenticação omissos ou inválidos para

procurador;

• 501 Not implemented: Pedido não suportado pelo servidor.

As respostas incluem alguns cabeçalhos dos pedidos (Content-Type, Content-Length,

Connection, Proxy-Connection, Date, etc.), acrescentando outros opcionais específicos, como a

indicação da última modificação do ficheiro (Last-Modified), do valor de hash do ficheiro (ETag),

informação complementar sobre o pedido de informação de autenticação do servidor (WWW-

Authenticate) ou do procurador (Proxy-Authenticate), a definição de um estado no cliente (Set-

Cookie ou Set-Cookie2), e o controlo de cache (Cache-Control em HTTP 1.1 ou Pragma em

HTTP 1.0). Observe-se que o cabeçalho Content-Type é obrigatório sempre se devolverem dados,

indicando o tipo de dados MIME4, de forma ao browser saber como interpretar os dados. Ficheiros

HTML são codificados no formato “text/html”, enquanto ficheiros de imagens podem ser

codificados em “image/gif” ou “image/jpeg”, os dados binários como “application/octet-stream”,

etc. No caso de ficheiros HTML, também é importante o cabeçalho Content-Encoding, com o

formato de codificação. Recomenda-se que seja SEMPRE usado o formato "ISO-8859-1" em

todos os ficheiros de texto no procurador. Um exemplo de uma resposta de um servidor ao pedido

representado anteriormente do procurador é:

HTTP/1.1 200 OK

Date: Wed, 27 Feb 2020 12:00:00 GMT

Server: Apache/2.2.8 (Fedora)

Last-Modified: Thu, 28 Set 2019 19:00:00 GMT

ETag: “405990-4ac-4478e4405c6c0”

Content-Length: 6821

Connection: close

Content-Type: text/html; charset=ISO-8859-1

… { dados html } …

A resposta enviada pelo procurador (direto) ao browser a partir desta resposta poderia ser:

HTTP/1.1 200 OK

Date: Wed, 27 Feb 2008 12:00:00 GMT

Server: Apache/2.2.8 (Fedora)

Last-Modified: Thu, 28 Set 2003 19:00:00 GMT

ETag: “405990-4ac-4478e4405c6c0”

Content-Length: 6821

Content-Type: text/html; charset=ISO-8859-1

Cache-Control: public,max-age=600

Via: 1.1 Demo_RIT2

… { dados html } …

No protocolo HTTP 1.1 são usados os campos de cabeçalho (Connection e Keep-Alive) no

pedido a um servidor Web e na resposta para definir se a ligação TCP é fechada ou se se mantém

a ligação TCP aberta e por quanto tempo, após o pedido. Se tanto o pedido como a resposta

contiverem um campo de cabeçalho Connection com um valor diferente de close, a ligação TCP

não é desligada, podendo ser enviados vários pedidos através dessa ligação. Um exemplo de

valores para um cabeçalho de pedido para manter a ligação ao servidor durante 60 segundos é:

Connection: keep-alive

Keep-Alive: 60

4 Ver página 599 de [RFC2616] com lista de tipos

© Luis Bernardo 7

A resposta pode rejeitar (com Connection: close), ou definir um valor igual ou inferior ao

proposto (no caso 15 seg):

Connection: keep-alive

Keep-Alive: timeout=15, max=100

Relativamente a um procurador, a ligação é mantida aberta caso o pedido contenha um campo

de cabeçalho Proxy-Connection. Um exemplo de valores para um cabeçalho de pedido para

manter a ligação ao procurador durante 60 segundos seria:

Proxy-Connection: keep-alive

Keep-Alive: 60

Uma ligação HTTPS usa a mesma troca de mensagens que o protocolo HTTP [RFC2818],

exceto que esta troca ocorre sobre um canal cifrado SSL/TLS, em vez de ocorrer num canal aberto.

Como o campo de cabeçalho Keep-Alive não é usado por todos os browsers (e.g. Internet

Explorer), no trabalho não deve ser usado.

2.1.2. O método CONNECT

Outro modo de funcionamento de um procurador, não usado no trabalho, é como um túnel.

Um pedido CONNECT é específico de um procurador, e pede a criação de um túnel até um

servidor remoto. Para não quebrar a segurança na ligação SSL a um servidor remoto, o procurador

age como um túnel reenviando transparentemente todas as mensagens recebidas do browser ou

do servidor. Desta forma, toda a informação passa através do procurador cifrada.

Associado ao pedido CONNECT é comum enviar um subconjunto dos campos de cabeçalho

referidos na secção anterior, incluindo-se os campos User-Agent, Host, Proxy-Connection, e

Proxy-Authorization. Um exemplo de pedido num browser Firefox é:

CONNECT tele1.dee.fct.unl.pt:443 HTTP/1.1

User-Agent: Mozilla/5.0 Gecko/20080208 Fedora/2.0.0.12

Proxy-Connection: keep-alive

Host: tele1.dee.fct.unl.pt

Caso o pedido seja aceite, a resposta não inclui campos de cabeçalho, como está representado

a seguir. A partir do momento em que a resposta é enviada, tudo o que for recebido de qualquer

das ligações tem de ser enviado transparentemente entre o browser e o servidor.

HTTP/1.0 200 Connection Established

Caso não seja aceite, então deve ser enviado um código de erro (e.g. 407 Proxy Authentication

Required). Pode encontrar mais informações sobre a utilização do método CONNECT em

[RFC2817].

2.2 Autenticação

O suporte de autenticação de clientes numa ligação HTTP foi definido numa norma externa

[RFC2617] ao HTTP. São definidos dois modos de funcionamento: Basic e Digest. No modo

Basic o nome de utilizador e palavra de passe são enviados codificados em Base64, podendo ser

facilmente descodificados por qualquer pessoa nas redes atravessadas pelos pacotes IP da ligação.

No modo Digest, é utilizado um método criptográfico para cifrar estes dados. No trabalho vai ser

realizado o modo Basic.

Um pedido transporta dados de autenticação para o servidor através do campo Authorization.

Um servidor requer a autenticação do cliente devolvendo o valor 401 (Unauthorized) na resposta

© Luis Bernardo 8

e um campo WWW-Authenticate com a indicação do tipo de autenticação pretendido e o nome do

domínio de segurança. Um exemplo de resposta seria:

HTTP/1.1 401 Unauthorized

WWW-Authenticate: Basic Realm="RIT2 Server Demo domain"

Em resposta, o browser abre uma janela a pedir o nome de utilizador e a palavra de passe, e

codifica em base64 a cadeia de carateres resultante da concatenação do nome (uid) e da palavra

de passe (pwd), reenviando o pedido. Um exemplo, com a string "rit2:demo" codificada:

Authorization: Basic cml0MjpkZW1v

A autenticação com um procurador direto decorre de forma semelhante, exceto que o pedido

usa o campo Proxy-Authorization e a resposta devolve o valor 407 (Proxy Authentication

Required) e usa o campo Proxy-Authenticate.

2.3 Controlo de caching

Quando um browser ou um procurador recebem um ficheiro, costumam guardá-lo em

memória local, designada de cache. Para que o ficheiro possa ser guardado, ele não deverá ter

campos de autenticação, cookies, métodos POST ("cgi-bin" ou "?" no URL), ou outros

campos com controlo explícito de caching que o proíbam. Também não devem ser guardados

num procurador ficheiros vindos através de HTTPS. O HTTP define vários métodos para controlar

se o ficheiro continua atual. Os mais comuns usam os campos de cabeçalho “If-Modified-Since”

e "If-None-Match". No primeiro caso, no segundo pedido, acrescenta-se o cabeçalho com a data

da última modificação, no segundo caso o cabeçalho com a assinatura digital do ficheiro (recebida

no campo "ETag"). Caso o ficheiro seja atual responde-se com o código 304, e com os campos

Date, Server, ETag, e os campos de controlo de ligação, caso existam. Caso contrário, deve

ignorar-se o campo de cabeçalho, e devolver o novo ficheiro.

PEDIDO 1:

GET /somedir/page.html HTTP/1.0

RESPOSTA 1:

HTTP/1.0 200 OK

Last-Modified: Thu, 23 Oct 2018 12:00:00 GMT

ETag:"a4137-7ff-42068cd3"

PEDIDO 2:

GET /somedir/page.html HTTP/1.0

If-Modified-Since: Thu, 23 Oct 2018 12:00:00 GMT

If-None-Match: "a4137-7ff-42068cd3"

RESPOSTA 2:

HTTP/1.0 304 Not Modified

Outros métodos consistem na utilização de campos de controlo de caching. Em HTTP 1.0

pode ser usado o campo de cabeçalho “Pragma: no-cache” tanto nos pedidos (não usar para

resposta valores em cache) como nas respostas (não guardar em cache). Em HTTP 1.1 [RFC2616,

sec. 14] deve ser usado o campo de cabeçalho “Cache-Control” que pode ter um subconjunto de

vários valores possíveis. O valor "no-cache" tem o mesmo significado que anteriormente. O valor

"no-store" no pedido ou resposta significa que a resposta não deve ser guardada em memória não

volátil, ou ser apagado o mais depressa possível. O valor "max-age=n" define o tempo máximo

© Luis Bernardo 9

de vida da resposta, ou o tempo máximo que o ficheiro pode estar em cache para ser válido. O

valor "s-maxage=n" numa resposta indica qual é validade máxima de um ficheiro num

procurador. Num ficheiro obtido a partir de um procurador: o campo "Age" indica o tempo que

decorreu desde que o ficheiro foi obtido do servidor; o valor "private" indica que a resposta apenas

pode ser reutilizada para o cliente original; O valor "public" significa que pode ser partilhado (o

valor por omissão). O valor "must-revalidate" e "proxy-revalidation" indicam que o valor em

cache deve ser sempre revalidado antes de responder a pedidos, o segundo especificamente para

procuradores. O valor "no-transform" no pedido ou resposta indicam que as mensagens não

devem ser modificadas por nenhum procurador. Observe-se que alguns servidores substituem o

valor “max-age=6” pelo campo de cabeçalho “Expires: Thu, 23 Oct 2007 12:00:00 GMT“ – a

diferença é que o primeiro define o número de segundos de validade, o segundo a data limite de

validade do ficheiro, tanto para browsers como para procuradores.

Outro aspeto importante no desenho de um procurador prende-se com a gestão dos ficheiros

na cache. Geralmente usa-se um buffer finito com N posições, ordenado pelos últimos ficheiros

acedidos (o mais recentemente acedido é o primeiro, etc.). Caso se exceda a capacidade do buffer,

é excluído do buffer o ficheiro que tinha sido acedido há mais tempo.

3. COMPLEMENTOS SOBRE JAVA

Nesta secção admite-se que os alunos já leram e conhecem sobre a linguagem de programação

Java de ST e RIT1. Para facilitar, vão ser revistas algumas informações sobre as bibliotecas a usar.

Este trabalho vai ser desenvolvido no mesmo ambiente (NetBeans), que é distribuído

gratuitamente juntamente com o ambiente Java.

3.1. IPv6

O protocolo IPv6 é suportado de uma forma transparente na linguagem Java, a partir da versão

Java 1.4. A classe InetAddress tanto permite lidar com endereços IPv4 como IPv6. Esta classe

tem como subclasses Inet4Address e Inet6Address, que suportam respetivamente as funções

específicas relativas aos endereços IPv4 e IPv6. Esta arquitetura permite suportar endereços IPv6

de uma forma transparente nas restantes classes do pacote java.net, uma vez que as restantes

funções usam parâmetros da classe InetAddress nos argumentos e nas variáveis dos objetos das

classes.

É possível obter o endereço local da máquina IPv4 ou IPv6 usando a função da classe

getLocalHost:

try {

InetAddress addr = InetAddress.getLocalHost();

} catch (UnknownHostException e) {

// tratar excepção

}

O método getHostAddress permite obter o endereço em formato string. O nome

associado ao endereço pode ser obtido com o método getHostName.

De forma semelhante, os métodos InetAddress.getByName ou

InetAddress.getAllByName permitem obter os endereços (IPv4 ou IPv6) associados a um

nome de domínio.

© Luis Bernardo 10

3.2 Sockets TCP

Em Java, a interface para sockets TCP é realizada através de duas classes: ServerSocket e

Socket.

3.2.1. A classe ServerSocket

A classe ServerSocket define um objecto servidor que pode receber e manter várias ligações

abertas. Quando se cria um objecto, define-se o porto onde vai escutar. O método accept()

bloqueia o objecto até que seja recebida uma ligação ao porto, criando um objecto da classe Socket

que permite comunicar com o cliente.

Os construtores da classe ServerSocket permitem definir o porto, o número de novas ligações

pendentes que são aceites pelo objecto e o endereço IP (a interface) a que se faz a associação. Em

Java 1.4 surgiu um quarto construtor sem parâmetros, que não inicializa o porto, permitindo usar

a função setReuseAddress(), antes de definir o porto com a função bind().

// Public Constructors

public ServerSocket(int port) throws IOException;

public ServerSocket(int port, int backlog) throws IOException;

public ServerSocket(int port, int backlog, InetAddress bindAddr) throws

IOException;

public ServerSocket() throws IOException; // apenas para Java 1.4

As duas funções principais permitem receber ligações e terminar o servidor.

public Socket accept() throws IOException;

public void close() throws IOException;

Outras funções permitem ter acesso às variáveis do objecto:

public InetAddress getInetAddress();

public int getLocalPort();

public synchronized int getSoTimeout() throws IOException;

public synchronized void setSoTimeout(int timeout) throws

SocketException;

Para permitir comunicar com os clientes e aceitar novas ligações em paralelo é comum usar

múltiplas tarefas (threads) para lidar com cada ligação.

Aplicação Servidora

ServerSocket

Socket

Socket

Socket Aplicação Cliente

Socket

Aplicação Cliente

Socket

Nova ligação

© Luis Bernardo 11

3.2.2. A classe Socket

A classe Socket define um objecto de intercomunicação em modo feixe. Pode ser criado

através de um construtor ou a partir da operação accept. O construtor permite programar clientes:

especifica-se o endereço IP e porto a que se pretende ligar, e o construtor estabelece a ligação.

public Socket(String host, int port) throws UnknownHostException,

IOException;

public Socket(InetAddress address, int port) throws IOException;

public Socket(String host, int port, InetAddress localAddr,

int localPort) throws IOException;

public Socket(InetAddress address, int port, InetAddress localAddr,

int localPort) throws IOException;

As operações de escrita e leitura do socket são realizadas através de objectos do pacote java.io

(InputStream e OutputStream), descritos na próxima secção, retornados por duas funções da

classe. Existem ainda funções para fechar o socket e para obter informações sobre a identidade da

ligação.

public InputStream getInputStream() throws IOException;

public OutputStream getOutputStream() throws IOException;

public synchronized void close() throws IOException;

public InetAddress getInetAddress();

public InetAddress getLocalAddress();

public int getLocalPort();

public int getPort();

public isConnected(); // Java 1.4

public isClosed(); // Java 1.4

Várias operações de configuração dos parâmetros do protocolo TCP podem ser realizadas

através de métodos desta classe. As funções getReceiveBufferSize, setReceiveBufferSize,

getSendBufferSize e setSendBufferSize permitem modificar a dimensão dos buffers usados no

protocolo TCP. As funções getTCPNoDelay e setTCPNoDelay controlam a utilização do

algoritmo de Nagle (false = desligado). As funções getSoLinger e setSoLinger controlam o que

acontece quando se fecha a ligação: se está ligada o valor define o número de segundos que se

espera até tentar enviar o resto dos dados que estão no buffer TCP e ainda não foram enviados.

Caso esteja ativo, pode originar perda de dados não detetável pela aplicação.

public int getReceiveBufferSize() throws SocketException;

public synchronized void setReceiveBufferSize(int size) throws

SocketException;

public int getSendBufferSize() throws SocketException; // 1.4

public synchronized void setSendBufferSize(int size) throws

SocketException; // 1.4

public boolean getTcpNoDelay() throws SocketException;

public void setTcpNoDelay(boolean on) throws SocketException;

public int getSoLinger() throws SocketException;

public void setSoLinger(boolean on, int val) throws SocketException;

public synchronized int getSoTimeout() throws SocketException;

public synchronized void setSoTimeout (int timeout) throws

SocketException;

As funções getSoTimeout e setSoTimeout permitem configurar o tempo máximo que uma

operação de leitura pode ficar bloqueada, antes de ser cancelada. Caso o tempo expire é gerada

uma exceção SocketTimeoutException. Estas funções também existem para as classes

ServerSocket e DatagramSocket.

© Luis Bernardo 12

3.2.3. Comunicação em sockets TCP

Os objetos da classe Socket oferecem métodos para obter um objeto da classe InputStream

(getInputStream) para ler do socket, e para obter um objeto da classe OutputStream

(getOutputStream). No entanto, estas classes apenas suportam a escrita de arrays de bytes. Assim,

é comum usar outras classes do pacote java.io para envolver estas classes base, obtendo-se uma

maior flexibilidade:

Para ler strings a partir de um socket é possível trabalhar com:

• A classe InputStreamReader processa a cadeia de bytes interpretando-a como uma

sequência carateres (convertendo o formato de carater).

• A classe BufferedReader armazena os carateres recebidos a partir de um feixe do tipo

InputStreamReader, suportando o método readLine() para esperar pela receção de uma

linha completa.

Para escrever strings num socket é possível trabalhar com:

• A classe OutputStreamWriter processa a cadeia de carateres e codifica-a para uma

sequência de bytes.

• A classe PrintWriter suporta os métodos de escrita de strings e de variáveis de outros

formatos (print e println) para um feixe do tipo OutputStreamWriter.

• A classe PrintStream suporta os métodos de escrita de strings e de byte [] para um

feixe do tipo OutputStream.

Caso se pretendesse enviar objetos num formato binário (não legível), dever-se-ia usar as

classes DataInputStream e DataOutputStream.

Um exemplo de utilização destas classes seria o apresentado em seguida:

try { // soc representa uma variável do tipo Socket inicializada

// Cria feixe de leitura

InputStream ins = soc.getInputStream( );

BufferedReader in = new BufferedReader(

new InputStreamReader(ins, "8859_1" )); // Tipo de caracter

// Cria feixe de escrita

OutputStream out = soc.getOutputStream( );

PrintStream pout = new PrintStream(out);

// Em alternativa poder-se-ia usar PrintWriter:

// PrintWriter pout = new PrintWriter(

// new OutputStreamWriter(out, "8859_1"), true);

// Lê linha e ecoa-a para a saída

String in_string= in.readln();

pout.writeln("Recebi: "+ in_string);

}

catch (IOException e ) { ... }

3.2.4. Exemplo de aplicação - TinyFileServ

No exemplo seguinte é apresentado um servidor de ficheiros parcialmente compatível com o

protocolo HTTP (utilizável por browsers), que recebe o nome do ficheiro no formato (* nome-

completo *) e devolve o conteúdo do ficheiro, fechando a ligação após o envio do último caracter

do ficheiro. O servidor recebe como argumentos da linha de comando o número de porto a partir

da linha de comando e a diretoria raiz correspondente à diretoria "/" (e.g. “java TinyFileServ

20000 /home/rit2/www”). Para cada nova ligação, o servidor lança uma tarefa que envia o ficheiro

pedido e termina após o envio do ficheiro. O servidor é compatível com um browser: lê o segundo

campo do pedido (e.g. GET / HTTP/1.1). Não é enviado o código de resposta, mas os browsers

mais vulgares (Firefox, etc.) sabem interpretar o ficheiro recebido a partir dos primeiros carateres

© Luis Bernardo 13

e do nome do ficheiro. Este exemplo usa algumas classes descritas nas próximas secções deste

capítulo.

//file: TinyFileServd.java

import java.net.*;

import java.io.*;

import java.util.*;

public class TinyFileServd {

public static void main( String argv[] ) throws IOException {

ServerSocket ss = new ServerSocket(

Integer.parseInt(argv.length>0 ? argv[0] : "20000"));

while ( true )

new TinyFileConnection(ss.accept(),

(argv.length>1 ? argv[1] : "")).start( );

}

} // end of class TinyFileServd

class TinyFileConnection extends Thread {

private Socket client;

private String root;

TinyFileConnection ( Socket client, String root ) throws SocketException {

this.client = client;

this.root= root;

setPriority( NORM_PRIORITY + 1 ); // Higher the thread priority

}

public void run( ) {

try {

BufferedReader in = new BufferedReader(

new InputStreamReader(client.getInputStream( ), "8859_1" ));

OutputStream out = client.getOutputStream( );

PrintStream pout = new PrintStream(out, false, "8859_1");

String request = in.readLine( ); // Reads the first line

System.out.println( "Request: "+request );

StringTokenizer st= new StringTokenizer (request);

if (st.countTokens() != 3) return; // Invalid request

String code= st.nextToken(); // USES HTTP syntax

String file= st.nextToken(); // for requesting files

String ver= st.nextToken();

String filename= root+file+(file.equals("/")?"index.htm":"");

System.out.println("Filename= "+filename);

FileInputStream fis = new FileInputStream ( filename );

byte [] data = new byte [fis.available()]; // Fails for large files

fis.read( data ); // Read from file

out.write( data ); // Write to socket

out.flush( ); // Flush socket buffer

fis.close();

client.close( );

}

catch ( FileNotFoundException e ) {

System.out.println( "File not found" );

}

catch ( IOException e ) {

System.out.println( "I/O error " + e );

}

}

} // end of class TinyFileConnection

3.2.5. Exemplo de cliente - HTTPClient

No exemplo seguinte é apresentado uma aplicação cliente parcialmente compatível com o

protocolo HTTP (utilizável com um servidor Web), que recebe um URL (e.g.

http://tele1.dee.fct.unl.pt/rit2), estabelece ligação com o servidor, e escreve o conteúdo da resposta

do servidor num ficheiro. O cliente recebe como argumentos da linha de comando o URL e o

nome completo do ficheiro (e.g. “java HTTPClient http://tele1.dee.fct.unl.pt/rit2 rit2.htm”). O

cliente termina após receber o último carater do ficheiro. O cliente é compatível com um servidor

Web: salta todas as linhas de cabeçalho (escrevendo o seu conteúdo na consola) até receber uma

© Luis Bernardo 14

linha vazia. A partir dessa altura escreve tudo o que receber no ficheiro previamente aberto. Este

exemplo também usa algumas classes descritas nas próximas secções deste capítulo.

// exemplo baseado parcialmente em http://examples.oreilly.com/jenut/HttpClient.java

import java.io.*;

import java.net.*;

public class HTTPClient {

public static void main(String[ ] args) {

try {

if (args.length != 2)

throw new IllegalArgumentException("Wrong number of args");

// Get an output stream to write the URL contents to

BufferedWriter to_file = new BufferedWriter (new OutputStreamWriter

(new FileOutputStream (args[1]), "8859_1"));

// Parse URL

URL url = new URL(args[0]);

String protocol = url.getProtocol();

String host = url.getHost( );

int port = url.getPort( );

String path = url.getPath();

if (path == null || path.length( ) == 0) path = "/";

Socket socket;

if (protocol.equals("http")) {

if (port == -1) port = 80; // Default http port

socket = new Socket(host, port); // Falha para tele1.dee.fct.unl.pt!!!

}

else {

throw new IllegalArgumentException("URL must use http: protocol");

}

// Get input and output streams for the socket

BufferedReader from_server = new BufferedReader(

new InputStreamReader(socket.getInputStream( ), "8859_1" ));

PrintWriter to_server = new PrintWriter(socket.getOutputStream( ));

// Send the HTTP GET command to the web server, specifying the file

to_server.print("GET " + path + " HTTP/1.0\r\n" +

"Host: " + host + "\r\n" +

"Connection: close\r\n\r\n");

to_server.flush( ); // Send it right now!

// Read and print the HTTP headers the server returns

String str;

while((str= from_server.readLine()) != null) {

System.out.println(str);

if (str.length() == 0) // Header separator line

break;

}

char[ ] buffer = new char[8 * 1024];

int bytes_read;

// Now read the rest of the bytes and write to the file

while((bytes_read = from_server.read(buffer)) != -1)

to_file.write(buffer, 0, bytes_read);

// When the server closes the connection, we close our stuff, too

socket.close( );

to_file.close( );

}

catch (Exception e) { // Report any errors that arise

System.err.println(e);

System.err.println("Usage: java HttpClient <URL> <filename>");

}

}

}

© Luis Bernardo 15

3.3 Strings

Uma vez que o protocolo HTTP é baseado em strings, para realizar o servidor web vai ser

necessário interpretar comandos e gerar respostas em formato ASCII. Nesta secção são

apresentadas algumas das classes que podem ser usadas para desempenhar estas tarefas.

3.3.1. Separação em componentes

Um dos aspetos essenciais é a separação de uma string em componentes fundamentais. As

classes String e StringBuffer oferecem o método substring para selecionar uma parte da string:

String a= "Java is great";

String b= a.substring(5); // b is the string "is great"

String c= a.substring(0, 4); // c is the string "Java"

O método trim() remove os espaços e tabulações antes e depois da string.

A comparação de strings pode ser feita com os métodos equals ou equalsIgnoreCase.

A linguagem Java oferece a classe StringTokenizer para decompor uma string em substrings

separadas por espaços ou outros separadores. O método nextToken() permite percorrer todos os

componentes, enquanto os métodos hasMoreTokens() e countTokens() permitem saber quando se

termina. Por exemplo, o código seguinte permite descodificar a primeira linha de um cabeçalho

HTTP nos vários componentes. O construtor pode ter um segundo parâmetro opcional com uma

string com a lista de separadores (por omissão tem apenas o espaço por separador).

StringTokenizer st = new StringTokenizer( request );

// Pode-se acrescentar um parâmetro extra com o separador e.g. ":"

if ( (st.countTokens( ) != 3) { … erro … }

String rqCode= st.nextToken( );

String rqName= st.nextToken( );

String rqVersion = st.nextToken( );

if (rqCode.equals("GET") || rqCode.equals("POST")) {

if ( rqName.startsWith("/") )

rqName = rqName.substring( 1 ); // Removes first character

if ( rqName.endsWith("/") || rqName.equals("") )

rqName = rqName + "index.htm"; // Adds “index.htm” if is a directory

}

3.3.2. Datas compatíveis com o protocolo HTTP

O protocolo HTTP define um formato específico para a escrita de datas:

Thu, 23 Oct 2002 12:00:00 GMT

É possível escrever e ler variáveis com este formato utilizando uma variável da classe

DateFormat.

DateFormat httpformat=

new SimpleDateFormat ("EE, d MMM yyyy HH:mm:ss zz", Locale.UK);

httpformat.setTimeZone(TimeZone.getTimeZone("GMT"));

// Escrita de datas

out.println("A data atual é " + httpformat.format(dNow));

// Leitura de datas

try {

Date dNow= httpformat.parse(str);

} catch (ParseException e) {

System.out.println("Data inválida: " + e + "\n");

}

© Luis Bernardo 16

É possível comparar datas utilizando o método compareTo de objetos da classe Date. A

adição e subtração de intervalos de tempo a datas também são possíveis convertendo a data em

long utilizando o método getTime. Como o valor conta o número de milissegundos desde 1970,

basta somar ou subtrair o valor correspondente ao intervalo. Por exemplo, para avançar um dia

seria:

Date d= new Date(dNow.getTime() + (long)24*60*60*1000);

Outra alternativa é usar a função add da classe Calendar para realizar a operação:

Calendar now= Calendar.getInstance();

now.add(Calendar.DAY_OF_YEAR, +1);

Date d= now.getTime();

3.3.3. URLs

A leitura e validação de URLs podem ser feitas usando a classe java.net.URL. Um URL tem

a estrutura seguinte:

<protocolo>://<autoridade><path>?<query>#<fragment>

Esta classe tem vários construtores que recebem uma string com o URL completo e outro que

recebe o URL por parâmetros. Depois inclui funções que permitem obter os vários campos do

URL: // Construtores – devolvem excepção se url inválido:

public URL(String spec) throws MalformedURLException;

public URL(String protocol, String host, int port, String file)

throws MalformedURLException

// Exemplo de métodos desta classe:

URL url= new URL("http://[email protected]:8080/servlet/xxx?xpto=ola&xa=xa#2");

url.getProtocol() == "http"

url.getAuthority() == " [email protected]:8080" // "" se omitido

url.getUserInfo() == "lflb" // null se omitido

url.getPort() == 8080 // -1 se omitido

url.getDefaultPort() == 80

url.getFile() == "/servlet/xxx?xpto=ola&xa=xa" // "" se omitido

url.getHost() == "tele1.dee.fct.unl.pt" // "" se omitido

url.getPath() == "/servlet/xxx" // "" se omitido

url.getQuery() == "xpto=ola&xa=xa" // null se omitido

url.getRef() == "2" // null se omitido

A biblioteca Java inclui a classe java.net.URLConnection que permite simplificar a criação

de ligações para um URL. Neste trabalho pretende-se realizar essa ligação diretamente sobre

sockets TCP, para ter um controlo mais fino na comunicação.

3.4 Ficheiros

A classe java.io.File representa um ficheiro ou uma diretoria, e define um conjunto de

métodos para os manipular. O construtor recebe o nome completo de um ficheiro, permitindo

depois a classe saber se o ficheiro existe, se é ficheiro ou diretoria, o comprimento do ficheiro,

apagar o ficheiro, ou marcar o ficheiro para ser apagado quando o programa termina. Inclui ainda

métodos para criar ficheiros temporários com nomes únicos.

© Luis Bernardo 17

File f= new File ("/home/pc40/xpto.txt"); // Associa-se a ficheiro

long len= f.length(); // Comprimento do ficheiro

Date date= new Date(f.lastModified()); // Última modificação

if (f.exists()) … // Se existe

if (f.isFile()) … // Se é ficheiro

if (f.canRead()) … // Se é legível

f.delete(); // Apaga ficheiro

File temp= File.createTempFile("proxy", ".tmp"); // Cria ficheiro temporário com nome único

temp.deleteOnExit(); // Apaga ficheiro quando a aplicação termina

File.separator // '/' ou '\\' dependendo do sistema operativo

A leitura a partir de ficheiros de texto é geralmente realizada através da classe

FileInputStream. Recomenda-se que a escrita de ficheiros recebido a partir de servidores Web

seja feita através da classe BufferedWriter, por esta também permitir definir o tipo de caracteres

("ISO-8859-1" por omissão). Caso este tipo seja usado nos canais associados a sockets e a

ficheiros, o Java nunca faz conversão de tipos, permitindo transmitir dados arbitrários (imagens,

aplicações, etc.).

// Para leitura

FileInputStream f= new FileInputStream ( file );

// Para escrita

FileOutputStream fos= new FileOutputStream ( file );

OutputStreamWriter osr= new OutputStreamWriter(fos, "8859_1");

BufferedWriter os= new BufferedWriter(osr);

// Permitem ler e escrever 'char []' com os métodos 'read' e 'write'

// usando o método 'getBytes()' é possível converter um 'char []' em 'byte []'

3.5 Estruturas de dados adicionais

A linguagem Java suporta um conjunto de variado de estruturas de dados que permitem lidar

de uma forma eficaz com conjuntos de pares (nome de propriedade, valor de propriedade), onde

o campo nome_de propriedade é único. Uma das estruturas que permite lidar com este tipo de

dados é a classe Properties. Esta classe é usada para manter as variáveis de sistema. Uma variável

da classe Properties pode ser iniciada vazia ou a partir do conteúdo de um ficheiro de texto, pode-

se acrescentar ou remover elementos, pode-se pesquisar por nome de propriedade ou

exaustivamente, e pode-se exportar o conteúdo para um ficheiro.

Properties prop= new Properties();

try { prop.load(in) } catch (IOException e) {…} // Lê ficheiro

prop.setProperty(“nome”, “valor”); // define valor

String val= prop.getProperty(“nome”); // obtém valor

String val2= prop.getProperty(“nome2”, “omisso”); // obtém valor, se n existe

devolve “omisso”

for (Enumeration p= prop.propertyNames(); p.hasMoreElements();) // percorre lista

System.out.println(p.nextElement());

try { prop.store(new FileOutputStream(“file”), “Configuração:”); } // Grava ficheiro

catch (IOException e) { … }

3.6 Descodificação em base64

Para suportar a descodificação de campos de mensagens em formato base64, vai ser usada

uma biblioteca desenvolvida por Robert W. Harder e disponibilizada gratuitamente em

http://iharder.sourceforge.net/base64/. Para descodificar uma string codificada em base64 (e.g.

txt_enc) basta usar o método Base64.decode:

© Luis Bernardo 18

String txt_dec= new String(Base64.decode(txt_enc));

4. ESPECIFICAÇÕES

Pretende-se neste trabalho realizar um procurador Web que funcione no modo direto e inverso

e que permita explorar várias funcionalidades do protocolo HTTP. Não se pretende uma realização

completa do protocolo, mas uma realização “à medida”, com uma interface gráfica que permite

configurar o servidor de uma forma simples.

4.1 Especificação do funcionamento

O procurador Web deve suportar vários clientes em paralelo, recorrendo a tarefas individuais

para responder a cada cliente. Na figura abaixo está representada a interface gráfica fornecida com

o trabalho. A interface gráfica deverá permitir arrancar e parar o servidor (Active), definir o

número de porto do serviço HTTP (Port), o nome de utilizador e uma palavra de passe para as

páginas (Password), o tempo máximo de manutenção de ligações HTTP abertas (Keep-Alive).

O servidor deve receber pedidos no porto Port desde que o toggleButton Active esteja

selecionado. Caso a caixa Password esteja preenchida, deverá ser usada autenticação por nome

de utilizador e palavra de passe nos acessos HTTP no modo básico. O valor de Keep-Alive apenas

é usado caso se mantenha uma ligação aberta, em HTTP 1.1. Keep-Alive define o número de

segundo máximo de inatividade até que se desligue a ligação (0 significa que não se desliga).

Threads indica o número de ligações de clientes ativas num instante. Finalmente, o botão Clear

limpa a janela de texto.

Quando funciona como procurador inverso (reverse proxy), o campo Redir URL define para

que servidores web reais deve ser efetuada a ligação. Desta forma, é semelhante a um servidor

web clássico, pois envia um ficheiro – a diferença está que vai buscar o ficheiro ao servidor remoto

ou à cache, antes de o transmitir, em vez de usar ficheiros locais fixos.

O funcionamento como Procurador direto (forward proxy) segue o padrão normal com um

procurador.

Para facilitar o desenvolvimento, é fornecido o código de um procurador básico, embora

quase totalmente funcional, que integra e adapta o código dos exemplos 3.2.4 e 3.2.5 com a

interface gráfica, descrito na próxima secção.

4.2 Tarefas a realizar

Para facilitar o desenvolvimento do programa e tornar possível o desenvolvimento do

programa durante as dez horas previstas, é fornecido juntamente com o enunciado um programa

© Luis Bernardo 19

proxy incompleto, com a interface gráfica apresentada anteriormente, que já realiza parte das

funcionalidades pedidas. Cada grupo pode fazer todas as modificações que quiser ao programa

base, ou mesmo, desenhar uma interface gráfica de raiz. No entanto, recomenda-se que invistam

o tempo na correta realização do algoritmo de encaminhamento e no envio dos dados.

O projeto ProxyHttpd_students fornecido é composto por dois pacotes e dez classes. No

pacote HTTPFormat tem as seguintes classes:

• Headers.java (completa) – Implementa uma lista indexada que suporta vários

elementos para cada chave, usada para guardar campos de cabeçalho;

• HTTPReplyCode.java (completa) – Guarda a informação para primeira linha de uma

resposta HTTP (código de resposta, mensagem de resposta, e versão de HTTP);

• HTTPAnswer.java (a completar) – Descritor de resposta HTTP, que guarda a

informa, realiza métodos para leitura e escrita em sockets, e métodos de manipulação

e teste de campos de cabeçalho;

• HTTPQuery.java (a completar) – Descritor de pedido HTTP, que guarda a informa,

realiza métodos para leitura e escrita em sockets, e métodos de manipulação e teste

de campos de cabeçalho;

• Log.java (completa) – Interface para escrita de mensagens no écran.

No pacote Main tem as seguintes classes:

• Base64.java (completa) – Biblioteca para descodificar o formato Base 64;

• sHttpd.java (completa) – Classe que suporta a tarefa que recebe novas ligações TCP

no procurador;

• ProxyHttpd.java (quase completo) – Classe principal com interface gráfica (herda de

JFrame), que faz a gestão de sincronismo dos vários objetos usados (falta criar o

objeto cache de páginas);

• HTTPClient.java (a completar) – Classe auxiliar, que contendo um pedido e uma

resposta, realiza a comunicação entre o procurador e o servidor web;

• HTTPThread.java (a completar) – Classe responsável pela comunicação entre o

procurador e o browser, usando os métodos disponibilizados principalmente pelas

classes HTTPClient, HTTPQuery e HTTPAnswer. Realiza da gestão da ligação do

browser, autenticação e da reutilização de ligações.

No decorrer do trabalho, é recomendado que acrescente mais uma classe extra, para gerir a

cache de páginas web no procurador (indexadas pelo URL).

O programa fornecido inclui vários ficheiros completos, e os que não o estão, já realizam uma

parte significativa das funcionalidades necessárias. A ativação dos sockets e das thread de receção

de pedidos já é feita no programa fornecido. As restantes funcionalidades vão ser distribuídas

pelas classes a completar.

O desenvolvimento do trabalho é realizado em cinco fases: descritas de seguida.

4.2.1 Procurador básico inverso

A realização da comunicação entre o browser e o procurador e o procurador e o servidor estão

quase todas realizadas. Mas faltam algumas funcionalidades:

• Completar a função get_answer_from_server da classe HTTPClient, para enviar o

pedido para o servidor web;

• Completar a função parse_Answer da classe HTTPAnswer, para ler os campos de

cabeçalho;

• Completar a função send_Answer da classe HTTPAnswer, para enviar os campos de

cabeçalho;

© Luis Bernardo 20

• Modifique o código de maneira a modificar os cabeçalhos do pedido, de maneira a

esconder a identidade do browser, só quando for um pedido do tipo inverso.

4.2.2 Procurador básico direto

O funcionamento do procurador direto é semelhante ao do procurador inverso, diferindo

principalmente nos campos de cabeçalho que são enviados nos pedidos ao servidor web e que são

enviados para o browser.

4.2.3 Autenticação de clientes do procurador

A realização da validação dos clientes do procurador tem de ser integrada na função run, da

classe HTTPThread. Recomenda-se que completem e usem a função authentication_valid de

forma a validar se o cabeçalho de autenticação foi enviado com a palavra de passe correta.

boolean authentication_valid ( HTTPQuery q );

4.2.4 Reutilização da ligação browser-procurador

A realização da validação dos clientes do procurador tem de ser integrada na função run, da

classe HTTPThread. Recomenda-se que modifique a função, para criar um ciclo, que permite ler

vários pedidos e enviar as respostas. É necessário ler e escrever os valores dos campos de

cabeçalho recebidos e enviados que gerem a reutilização de ligação.

4.2.5 Caching de pedidos

A criação de uma cache de pedidos compreende a realização de várias tarefas:

• Criação de um objeto central onde vai ser criada a lista com pedidos/respostas

guardados;

• Modificação da função run, de maneira a guardar uma página na cache, quando os

campos de cabeçalho o permitirem;

• Modificação da função run, de maneira a verificar se a página já está em cache, antes

de ir fazer a ligação ao servidor, quando os campos de cabeçalho o permitirem;

• Criação e invocação de uma função que valide se o conteúdo guardado em cache ainda

está válido.

É deixada a liberdade aos alunos nesta tarefa. Mas, foram deixadas algumas funções

incompletas nas classes HTTPClient e HTTPQuery com sugestões de como podem testar se as

respostas são guardáveis, ou se é necessário validar a cópia em cache. Pretende-se que a realização

esteja correta, de acordo com o protocolo HTTP/1.1.

4.3 Testes

O servidor web deverá ser compatível com qualquer browser, com qualquer das opções

ligadas. Assim, recomenda-se que sigam as especificações indicadas na norma HTTP e evitem

adotar configurações específicas para alguns browsers. Recomenda-se que usem o Firefox pois

permite configurar o proxy na aplicação, e um analisador de protocolos (e.g. Wireshark), para

conseguirem monitorizar toda as trocas de mensagens HTTP, validando-as. Podem também usar

o comando telnet para criar ligações para o servidor e gerar pedidos manualmente.

No mínimo, o procurador deverá suportar pedidos (GET) como procurador direto e indireto,

e a autenticação de utilizadores. Para ambicionarem a uma boa nota, dever-se-á também

desenvolver a reutilização de ligações e principalmente, o sistema de cache de respostas.

© Luis Bernardo 21

Atendendo à grande difusão de realizações de servidores e procuradores Web disponíveis na

Internet, alerta-se os alunos que não devem usar código que não conheçam ou consigam

interpretar, pois a discussão vai focar todos os aspetos do código e da realização do servidor.

4.4 Desenvolvimento do trabalho

O trabalho vai ser desenvolvido em cinco semanas. Propõe-se que sejam definidas as

seguintes metas para a realização do trabalho:

1. antes da primeira aula deve ler a documentação e o código fornecido.

Recomenda-se também que instale um servidor web (e.g. google “install Apache

[sistema operativo]”), o Wireshark (https://www.wireshark.org/) e para quem usa

Windows, instalar o telnet (https://www.technipages.com/windows-10-enable-telnet);

2. no fim da primeira aula deve ter realizado as três primeiras subtarefas de 4.2.1 e ter

começado a realizar a última;

3. no fim da segunda aula deve estar a trabalhar na tarefa 4.2.3;

4. no fim da terceira aula deve ter concluído a tarefa 4.2.4;

5. no fim da quarta aula deve estar a trabalhar na tarefa 4.2.5, ainda com um teste

incompleto das várias variantes de campos de cabeçalho – comece por usar os mais

comuns (e.g. “Cache-Control” com “no-cache” ou “max-age=0”. Não se esqueça da

validação de cache, com os campos "If-Modified-Since" e "If-None-Match" do cliente,

e os campos "Last-Modified" e "ETag" do servidor;

6. no fim da última aula deve ter acabado todas as tarefas anteriores. Caso ainda tenha

tempo, enriqueça o 4.2.5, de forma a controlar o tempo em que uma página é guardada

em cache.

Postura dos Alunos

Cada grupo deve ter em consideração o seguinte:

• Não perca tempo com a estética de entrada e saída de dados

• Programe de acordo com os princípios gerais de uma boa codificação (utilização de

indentação, apresentação de comentários, uso de variáveis com nomes conformes às suas

funções...) e

• Proceda de modo a que o trabalho a fazer fique equitativamente distribuído pelos

membros do grupo.

DATAS LIMITE

A parte laboratorial é composta por dois trabalhos de avaliação. A duração prevista para os

dois trabalhos é de 5 semanas. A parte prática tem o seu início no dia 12 de março. A data limite

provisória para entrega do primeiro trabalho é o dia 19 de abril de 2020, às 12:00.

BIBLIOGRAFIA

[RFC1945] HTTP 1.0, http://www.ietf.org/rfc/rfc1945.txt

[RFC2616] HTTP 1.1, http://www.ietf.org/rfc/rfc2616.txt

[RFC2617] HTTP Authentication: Basic and Digest Access Authentication, http://www.ietf.org/rfc/rfc2617.txt

[RFC2817] Upgrading to TLS within HTTP/1.1, http://www.ietf.org/rfc/rfc2817.txt

[RFC2818] HTTP Over TLS, http://www.ietf.org/rfc/rfc2818.txt

[RFC7230] Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing,

https://httpwg.org/specs/rfc7230.html