Como os servlets funcionam? Instanciação, sessões, variáveis ​​compartilhadas e multithreading

1144

Suponha, eu tenho um servidor web que contém numerosos servlets. Para informações transmitidas entre esses servlets, estou configurando variáveis ​​de sessão e instância.

Agora, se 2 ou mais usuários enviam solicitação para este servidor, o que acontece com as variáveis ​​da sessão?
Todos eles serão comuns a todos os usuários ou serão diferentes para cada usuário?
Se eles são diferentes, como o servidor conseguiu diferenciar usuários diferentes?

Mais uma pergunta semelhante, se houver nusuários acessando um determinado servlet, esse servlet será instanciado apenas na primeira vez em que o primeiro usuário o acessou ou será instanciado para todos os usuários separadamente?
Em outras palavras, o que acontece com as variáveis ​​de instância?

Ku Jon
fonte

Respostas:

1821

ServletContext

Quando o contêiner de servlet (como o Apache Tomcat ) for iniciado, ele implementará e carregará todos os seus aplicativos da web. Quando um aplicativo da web é carregado, o contêiner do servlet cria o ServletContextúnico e o mantém na memória do servidor. Do aplicativo web web.xmle todos incluídos web-fragment.xmlarquivos é analisado, e cada um <servlet>, <filter>e <listener>encontrado (ou cada classe anotada com @WebServlet, @WebFiltere @WebListenerrespectivamente) é instanciado uma vez e mantidos na memória do servidor também. Para cada filtro instanciado, seu init()método é chamado com um novo FilterConfig.

Quando a Servlettem um valor <servlet><load-on-startup>ou @WebServlet(loadOnStartup)maior que 0, seu init()método também é chamado durante a inicialização de um novo ServletConfig. Esses servlets são inicializados na mesma ordem especificada por esse valor ( 11º, 22º, etc). Se o mesmo valor é especificada por mais de um servlet, então cada um desses servlets é carregado na mesma ordem em que aparecem na web.xml, web-fragment.xmlou @WebServletclassloading. Caso o valor "carregar na inicialização" esteja ausente, o init()método será chamado sempre que a solicitação HTTP atingir esse servlet pela primeira vez.

Quando o contêiner do servlet terminar com todas as etapas de inicialização descritas acima, ele ServletContextListener#contextInitialized()será chamado.

Quando o recipiente servlet é desligado, ele descarrega todas as aplicações web, invoca o destroy()método de todos os seus servlets e filtros inicializados, e todos ServletContext, Servlet, Filtere Listenerinstâncias são lixeira. Finalmente, o ServletContextListener#contextDestroyed()será invocado.

HttpServletRequest e HttpServletResponse

O contêiner de servlet está conectado a um servidor da Web que atende solicitações HTTP em um determinado número de porta (a porta 8080 geralmente é usada durante o desenvolvimento e a porta 80 em produção). Quando um cliente (por exemplo, usuário com um navegador web ou por meio de programação usandoURLConnection ) envia uma solicitação HTTP, o recipiente servlet cria novo HttpServletRequeste HttpServletResponseobjetos e passa-los através de qualquer definido Filterna cadeia e, por fim, a Servletinstância.

No caso de filtros , o doFilter()método é chamado. Quando o código do contêiner do servlet chama chain.doFilter(request, response), a solicitação e a resposta continuam no próximo filtro ou atingem o servlet se não houver filtros restantes.

No caso de servlets , o service()método é chamado. Por padrão, esse método determina qual dos doXxx()métodos chamar com base em request.getMethod(). Se o método determinado estiver ausente no servlet, um erro HTTP 405 será retornado na resposta.

O objeto de solicitação fornece acesso a todas as informações sobre a solicitação HTTP, como URL, cabeçalhos, cadeia de caracteres e corpo da consulta. O objeto de resposta fornece a capacidade de controlar e enviar a resposta HTTP da maneira que você deseja, por exemplo, permitindo definir os cabeçalhos e o corpo (geralmente com conteúdo HTML gerado a partir de um arquivo JSP). Quando a resposta HTTP é confirmada e concluída, os objetos de solicitação e resposta são reciclados e disponibilizados para reutilização.

HttpSession

Quando um cliente visita o aplicativo da Web pela primeira vez e / ou HttpSessioné obtido pela primeira vez request.getSession(), o contêiner do servlet cria um novo HttpSessionobjeto, gera um ID longo e exclusivo (que você pode obter session.getId()) e o armazena no servidor. memória. O contêiner de servlet também define um Cookieno Set-Cookiecabeçalho da resposta HTTP com JSESSIONIDcomo nome e o ID da sessão exclusivo como valor.

De acordo com a especificação do cookie HTTP (um contrato que qualquer navegador e servidor da web decente deve aderir), o cliente (o navegador da web) deve enviar esse cookie de volta nas solicitações subsequentes no Cookiecabeçalho enquanto o cookie for válido ( ou seja, o ID exclusivo deve se referir a uma sessão não expirada e o domínio e o caminho estão corretos). Usando o monitor de tráfego HTTP interno do seu navegador, você pode verificar se o cookie é válido (pressione F12 no Chrome / Firefox 23+ / IE9 + e verifique a guia Rede / Rede ). O contêiner do servlet verificará o Cookiecabeçalho de cada solicitação HTTP recebida quanto à presença do cookie com o nome JSESSIONIDe usará seu valor (o ID da sessão) para obter o associado HttpSessionda memória do servidor.

As HttpSessionpermanece viva até que tenha sido ocioso (ou seja, não usado em uma solicitação) para mais do que o valor limite especificado no <session-timeout>, uma configuração no web.xml. O valor do tempo limite é padronizado em 30 minutos. Portanto, quando o cliente não visita o aplicativo da Web por mais tempo que o especificado, o contêiner do servlet descarta a sessão. Cada solicitação subsequente, mesmo com o cookie especificado, não terá mais acesso à mesma sessão; o contêiner de servlet criará uma nova sessão.

No lado do cliente, o cookie da sessão permanece ativo enquanto a instância do navegador estiver em execução. Portanto, se o cliente fechar a instância do navegador (todas as guias / janelas), a sessão será lixeira no lado do cliente. Em uma nova instância do navegador, o cookie associado à sessão não existiria, portanto não seria mais enviado. Isso faz com que um inteiramente novo HttpSessionseja criado, com um cookie de sessão totalmente novo sendo usado.

Em poucas palavras

  • A ServletContextvida útil enquanto durar o aplicativo da web. É compartilhado entre todos os pedidos em todas as sessões.
  • A HttpSessionvida útil enquanto o cliente estiver interagindo com o aplicativo Web com a mesma instância do navegador e a sessão não tiver atingido o tempo limite no servidor. É compartilhado entre todas as solicitações na mesma sessão.
  • O HttpServletRequeste HttpServletResponsevive desde o momento em que o servlet recebe uma solicitação HTTP do cliente, até a resposta completa (a página da web) chegar. É não compartilhada em outro lugar.
  • Todos Servlet, Filtere Listenercasos viver tanto tempo quanto o aplicativo web vive. Eles são compartilhados entre todas as solicitações em todas as sessões.
  • Qualquer attributeque está definido no ServletContext, HttpServletRequeste HttpSessionvai viver tanto tempo quanto o objeto na vida de interrogação. O objeto em si representa o "escopo" nas estruturas de gerenciamento de beans, como JSF, CDI, Spring, etc. Essas estruturas armazenam seus beans com escopo definido como um attributedos seus escopos correspondentes mais próximos.

Segurança da linha

Dito isto, sua principal preocupação é possivelmente a segurança do thread . Agora você deve saber que servlets e filtros são compartilhados entre todas as solicitações. Essa é a coisa boa do Java, é multithread e threads diferentes (leia-se: solicitações HTTP) podem fazer uso da mesma instância. Caso contrário, seria muito caro recriar, init()e destroy()eles para cada solicitação.

Você também deve perceber que nunca deve atribuir nenhum pedido ou dados com escopo de sessão como uma variável de instância de um servlet ou filtro. Será compartilhado entre todos os outros pedidos em outras sessões. Isso não é seguro para threads! O exemplo abaixo ilustra isso:

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

Veja também:

BalusC
fonte
25
Então, quando eu descobrir de alguma forma o JSessionId que é enviado para um cliente, posso roubar a sessão dele?
Toskan
54
@Toskan: está correto. É conhecido como corte de fixação de sessão . Observe que isso não é específico para JSP / Servlet. Todas as outras linguagens do servidor que mantêm a sessão por um cookie também são sensíveis, como PHP com PHPSESSIDcookie, ASP.NET com ASP.NET_SessionIDcookie, etc. É também por isso que a reescrita de URLs ;jsessionid=xxxcomo algumas estruturas JSP / Servlet MVC fazem automaticamente é desaprovada. Apenas certifique-se de que o ID da sessão nunca seja exposto na URL ou por outros meios nas páginas da Web, para que o usuário final inconsciente não seja atacado.
BalusC
11
@Toskan: Além disso, verifique se o seu aplicativo da web não é sensível a ataques XSS. Ou seja, não exiba novamente nenhuma entrada controlada pelo usuário na forma sem escape. O XSS abriu portas para maneiras de coletar IDs de sessão de todos os usuários finais. Veja também Qual é o conceito geral por trás do XSS?
BalusC
2
@ BalusC, desculpe pela minha estupidez. Isso significa que todos os usuários acessam a mesma instância do thisIsNOTThreadSafe, certo?
de sombra
4
@TwoThumbSticks 404 é retornado quando todo o servlet estiver ausente. 405 é retornado quando o servlet está presente, mas o método doXxx () desejado não está implementado.
BalusC
428

Sessões

insira a descrição da imagem aqui insira a descrição da imagem aqui

Em resumo: o servidor da web emite um identificador exclusivo para cada visitante em sua primeira visita. O visitante deve trazer de volta esse ID para que ele seja reconhecido na próxima vez. Esse identificador também permite que o servidor separe adequadamente os objetos pertencentes a uma sessão e a de outra.

Instanciação de Servlet

Se o carregamento na inicialização for falso :

insira a descrição da imagem aqui insira a descrição da imagem aqui

Se o carregamento na inicialização for verdadeiro :

insira a descrição da imagem aqui insira a descrição da imagem aqui

Quando ele estiver no modo de serviço e no ritmo, o mesmo servlet funcionará nas solicitações de todos os outros clientes.

insira a descrição da imagem aqui

Por que não é uma boa ideia ter uma instância por cliente? Pense nisso: você contratará um entregador de pizza para cada pedido que vier? Faça isso e você estará fora do negócio em pouco tempo.

Ele vem com um pequeno risco. Lembre-se: esse indivíduo único mantém todas as informações do pedido no bolso: portanto, se você não for cuidadoso com a segurança de threads nos servlets , ele poderá acabar dando o pedido errado a um determinado cliente.

Jops
fonte
26
Sua foto é muito boa para minha compreensão. Eu tenho uma pergunta: o que esse pizzaria fará quando houver muitos pedidos de pizza, apenas espere um entregador de pizza ou contrate mais entregador de pizza? Obrigado .
zh18
6
Ele retornará uma mensagem comto many requests at this moment. try again later
Please_Dont_Bully_Me_SO_Lords
3
Os servlets, diferentemente dos entregadores de pizza, podem fazer mais de uma entrega ao mesmo tempo. Eles só precisam ter um cuidado especial no local em que anotam o endereço do cliente, o sabor da pizza ...
bruno
42

Sessão em servlets Java é igual à sessão em outras linguagens, como PHP. É exclusivo para o usuário. O servidor pode acompanhá-lo de diferentes maneiras, como cookies, reescrita de URL, etc. Este artigo sobre documentos em Java explica no contexto de servlets Java e indica que exatamente como a sessão é mantida é um detalhe de implementação deixado para os projetistas do servidor. A especificação apenas estipula que deve ser mantida como exclusiva para um usuário em várias conexões com o servidor. Confira este artigo da Oracle para obter mais informações sobre as duas perguntas.

Editar Há um excelente tutorial aqui sobre como trabalhar com sessões dentro de servlets. E aqui está um capítulo da Sun sobre Java Servlets, o que são e como usá-los. Entre esses dois artigos, você poderá responder a todas as suas perguntas.

Chris Thompson
fonte
Isso me traz outra pergunta: como existe apenas um contexto de servlet para todo o aplicativo e obtemos acesso às variáveis ​​de sessão por meio desse contexto de servlet. Como as variáveis ​​de sessão podem ser exclusivas para cada usuário? Obrigado ..
Ku Jon
1
como você está acessando a sessão a partir do servletContext? Você não está se referindo a servletContext.setAttribute (), está?
Matt b
4
@KuJon Cada aplicativo da web tem um ServletContextobjeto. Esse objeto possui zero, um ou mais objetos de sessão - uma coleção de objetos de sessão. Cada sessão é identificada por algum tipo de sequência identificadora, como visto nos desenhos animados em outra resposta. Esse identificador é rastreado no cliente por cookie ou reescrita de URL. Cada objeto de sessão possui suas próprias variáveis.
Basil Bourque
33

Quando o contêiner de servlet (como o Apache Tomcat) for iniciado, ele lerá o arquivo web.xml (apenas um por aplicativo) se algo der errado ou aparecer um erro no console do lado do contêiner; caso contrário, ele implementará e carregará toda a web aplicativos usando web.xml (denominado como descritor de implementação).

Durante a fase de instanciação do servlet, a instância do servlet está pronta, mas não pode atender à solicitação do cliente porque está ausente com duas informações:
1: informações de contexto
2: informações de configuração inicial

O mecanismo do servlet cria o objeto de interface servletConfig que encapsula as informações ausentes acima nele, o mecanismo do servlet chama init () do servlet fornecendo referências ao objeto servletConfig como argumento. Depois que init () é completamente executado, o servlet está pronto para atender à solicitação do cliente.

P) Durante a vida útil do servlet, quantas vezes a instanciação e a inicialização ocorrem?

A) apenas uma vez (para cada solicitação de cliente, um novo encadeamento é criado) apenas uma instância do servlet atende a qualquer número da solicitação do cliente, ou seja, depois de atender a um servidor de solicitação do cliente não morre. Ele aguarda outras solicitações do cliente, ou seja, qual limitação de CGI (para cada solicitação de cliente é criada) é superada com o servlet (o mecanismo interno do servlet cria o encadeamento).

Q) Como o conceito de sessão funciona?

A) sempre que getSession () é chamado no objeto HttpServletRequest

Etapa 1 : o objeto de solicitação é avaliado para o ID da sessão recebida.

Etapa 2 : se o ID não disponível, um novo objeto HttpSession é criado e seu ID de sessão correspondente é gerado (por exemplo, de HashTable). O ID da sessão é armazenado no objeto de resposta httpservlet e a referência do objeto HttpSession é retornada ao servlet (doGet / doPost) .

Etapa 3 : se o ID disponível, o novo objeto de sessão não for criado, o ID da sessão será escolhido na pesquisa de objeto de solicitação feita na coleção de sessões, usando o ID da sessão como chave.

Depois que a pesquisa for bem-sucedida, o ID da sessão é armazenado no HttpServletResponse e as referências do objeto da sessão existentes são retornadas ao doGet () ou doPost () do UserDefineservlet.

Nota:

1) quando o controle sai do código do servlet para o cliente, não esqueça que o objeto da sessão está sendo mantido pelo contêiner do servlet, ou seja, o mecanismo do servlet

2) o multithreading é deixado para os desenvolvedores de servlets implementarem, por exemplo, manipular as múltiplas solicitações do cliente, nada para se preocupar com o código multithread

Formulário inshort:

Um servlet é criado quando o aplicativo é iniciado (implementado no contêiner do servlet) ou quando é acessado pela primeira vez (dependendo da configuração de carregar na inicialização) quando o servlet é instanciado, o método init () do servlet é chamado então o servlet (sua única e única instância) manipula todas as solicitações (seu método service () sendo chamado por vários encadeamentos). É por isso que não é aconselhável ter nenhuma sincronização, e você deve evitar variáveis ​​de instância do servlet quando o aplicativo não estiver implementado (o contêiner do servlet para), o método destroy () é chamado.

Ajay Takur
fonte
20

Sessões - o que Chris Thompson disse.

Instanciação - um servlet é instanciado quando o contêiner recebe a primeira solicitação mapeada para o servlet (a menos que o servlet esteja configurado para carregar na inicialização com o <load-on-startup>elemento in web.xml). A mesma instância é usada para atender solicitações subsequentes.

Lauri Lehtinen
fonte
3
Corrigir. Pensamento adicional: Cada solicitação obtém um novo encadeamento (ou reciclado) para executar nessa instância única do Servlet. Cada Servlet possui uma instância e, possivelmente, muitos encadeamentos (se houver muitos pedidos simultâneos).
Basil Bourque
13

A especificação de servlet JSR-315 define claramente o comportamento do contêiner da Web nos métodos de serviço (e doGet, doPost, doPut etc.) (2.3.3.1 Problemas de multithreading, página 9):

Um contêiner de servlet pode enviar solicitações simultâneas através do método de serviço do servlet. Para manipular as solicitações, o Servlet Developer deve fazer provisões adequadas para processamento simultâneo com vários encadeamentos no método de serviço.

Embora não seja recomendado, uma alternativa para o desenvolvedor é implementar a interface SingleThreadModel, que exige que o contêiner garanta que haja apenas um encadeamento de solicitação por vez no método de serviço. Um contêiner de servlet pode satisfazer esse requisito serializando solicitações em um servlet ou mantendo um conjunto de instâncias de servlet. Se o servlet fizer parte de um aplicativo da Web que foi marcado como distribuível, o contêiner poderá manter um conjunto de instâncias de servlet em cada JVM pela qual o aplicativo é distribuído.

Para servlets que não implementam a interface SingleThreadModel, se o método de serviço (ou métodos como doGet ou doPost que são despachados para o método de serviço da classe abstrata HttpServlet) tiver sido definido com a palavra-chave sincronizada, o contêiner de servlet não poderá usar a abordagem do conjunto de instâncias , mas deve serializar solicitações por meio dele. É altamente recomendável que os desenvolvedores não sincronizem o método de serviço (ou métodos enviados a ele) nessas circunstâncias devido a efeitos prejudiciais no desempenho

tharindu_DG
fonte
2
Para sua informação, a especificação atual do servlet (2015-01) é 3.1, definida pelo JSR 340 .
Basil Bourque
1
Resposta muito elegante! @tharindu_DG
Tom Taylor
0

Como ficou claro nas explicações acima, implementando o SingleThreadModel , um servlet pode ter segurança de thread garantida pelo contêiner do servlet. A implementação do contêiner pode fazer isso de duas maneiras:

1) Serializando solicitações (enfileiramento) para uma única instância - é semelhante a um servlet que NÃO implementa o SingleThreadModel, MAS sincroniza os métodos service / doXXX; OU

2) Criando um conjunto de instâncias - que é uma opção melhor e uma troca entre o esforço de inicialização / inicialização / hora do servlet e os parâmetros restritivos (tempo de memória / CPU) do ambiente que hospeda o servlet.

Mahesh Balasubramanian
fonte
-1

Não. Servlets não são seguros para threads

Isso permite acessar mais de um thread por vez

se você quiser torná-lo Servlet como Thread safe., U pode ir para

Implement SingleThreadInterface(i) que é uma interface em branco, não há

métodos

ou podemos ir para métodos de sincronização

podemos fazer todo o método de serviço como sincronizado usando sincronizado

palavra-chave na frente do método

Exemplo::

public Synchronized class service(ServletRequest request,ServletResponse response)throws ServletException,IOException

ou podemos colocar o bloco do código no bloco Sincronizado

Exemplo::

Synchronized(Object)

{

----Instructions-----

}

Eu sinto que o bloco sincronizado é melhor do que fazer todo o método

Sincronizado

Ved Prakash
fonte