Este é um projeto criado como parte de um curso que fiz na Universidade de Varsóvia. É uma implementação simples de um contêiner de servlet que suporta o Java Servlets compatível com a API Java Servlet. Embora seja bastante barato, ele pode carregar e executar aplicativos simples que usam funcionalidades básicas de servlets, incluindo despacho, servlets JSP e Async.
Não encontrei muitas outras pequenas implementações de contêineres de servlet, portanto, sinta -se à vontade para usar isso como um exemplo de como criar um contêiner como este. Lembre -se de que, devido às restrições de tempo, é um projeto bastante imperfeito e de buggy e, enquanto eu tentava permanecer fiel à API Servlet, tomei algumas liberdades aqui e ali.
A idéia é que esse contêiner funcione de maneira semelhante ao Tomcat. Você pode adicionar classes de servlet usando o servletContainer :: addRoute, mas a maneira preferida é usar a funcionalidade de varredura de componentes. Ou seja, se o aplicativo for abastecido no arquivo warName.war e colocado no diretório deploy no server/src/main/resources ele será carregado no servidor Iniciar e disponível no localhost:8000/warName .
Por padrão, o servidor é executado na porta 8000. Ele pode ser alterado na função Main . Esta função também contém e exemplo de como iniciar e configurar este servidor.
O projeto foi construído usando Gradle. Ele contém dois subprojetos: aplicativo de servidor e demonstração (banco de dados simples de livros). Foi testado no Linux e eu não sei se funciona no Windows/MacOS.
Para executar o uso do servidor:
./gradlew server:run
O servidor inicial exige que criemos uma instância do ServletContainer .
ServletContainer(int threads) Cria uma nova instância do ServletContainer . com um determinado número de threads usados para criar Threadpool. Este pool será usado para lidar com solicitações HTTP.
void ServletContainer::start(int port) Inicia o contêiner do servlet em determinada porta. Lembre -se de que os arquivos .war não são carregados neste momento. Eles são carregados apenas quando ServletContainer:servletScan é chamado.
void ServletContainer::stop()Pare graciosamente o recipiente.
void ServletContainer::servletScan() Carrega todos os arquivos .war do diretório deploy no server/src/main/resources . Ele também carregará arquivos .jsp e transpilam imediatamente -os em arquivos .class. Somente aulas anotadas com @WebServlet serão carregadas. Uma ressalva é que os arquivos .war que eu uso têm uma estrutura de diretório específica que não tenho tanta certeza de que seja a mesma que na maioria dos outros arquivos .war. Você pode verificar a tarefa de graduação war no aplicativo de demonstração para ver como essa estrutura deve ser. Este método deve ser chamado no máximo uma vez antes do método start ().
void ServletContainer::servletScan(String url) O mesmo que ServletContainer::servletScan() , mas pode carregar arquivos .war a partir de um determinado URL.
void ServletContainer::addRoute(Class<? extends HttpServlet> cls, String path)Adiciona servlet ao contêiner. O Servlet lidará apenas com o URL determinado (consulte as perguntas frequentes para obter mais detalhes).
O servidor suporta manuseio simultâneo de vários clientes de uma só vez. Cada cliente será tratado por thread separado do ThreadPool. O tamanho padrão do pool é 4 e pode ser alterado na função Main .
O servidor suportará qualquer classe que herda do httpServlet. Essa classe pode ser adicionada usando ServletContainer::addRoute .
As funcionalidades mais importantes dessas classes são implementadas. Você pode escrever para o cliente usando (somente) PrintWriter , definir cabeçalhos e status de resposta. HttpServletRequest pode extrair o URL da solicitação, o método HTTP (Get, Post, Excluir, Patch), parâmetros de consulta e parâmetros do corpo (no caso de postagem). Os dados são enviados ao cliente depois de liberar o buffer ou fechar o HttpServletResponse . Usando RequestDispatcher , você pode enviar consultas para outros servlets, incluindo JSP Servlets.
Há suporte para servlets assíncronos. Após a execução HttpServletRequest::startAsync , a solicitação entra no modo assíncrono. Existem duas maneiras de usar esse modo. Qualquer código será executado enquanto AsyncContext::complete for executado em algum momento, que encerra a conexão com o cliente. Você também pode usar AsyncConext::start(Runnable) , aqui também precisa executar AsyncContext::complete em algum momento. Este último é compatível com a API Java Servlet. Ele também suporta Timeout ( AsyncContext::setTimeout ) e AsyncListener . Os servlets assíncronos são implementados usando CompletableFuture , para que eles usem o ThreadPool separado da usada para clientes síncronos.
Se o aplicativo tiver sido abastecido para .war e movido para a pasta deploy , ele será carregado automaticamente. Todas as classes anotadas com @WebServlet e herdador do HttpServlet serão adicionadas ao contêiner do servlet e estarão disponíveis no localhost:port/warName/servletUrl . Cada uma dessas classes deve ter exatamente um servletUrl especificado na anotação @WebServlet no atributo Value. Se o aplicativo usar o JSP, ele será compilado automaticamente em .class e carregado. Muitos aplicativos podem ser carregados, no entanto, não sei o que acontece quando existem os mesmos nomes de classes usados em dois aplicativos.
O servidor pode transpilar arquivos .jsp para .class. Há suporte para quase toda a sintaxe no JSP. Isso inclui:
<%@ page import/include=... %><% ... %><%! ... %><%= ... %><%-- %>${...} ${} A sintaxe suporta parcialmente a linguagem de expressão. Operações aritméticas simples podem ser executadas e qualquer expressão da instance.property1.property2 do formulário.Property1.Property2 será convertida em request.getAttribute("instance").getProperty1().getProperty2() . Em <% ... %> também está out.println(...) que grava diretamente no cliente. O JSP pode ser exibido usando RequestDispatcher::forward ou está disponível diretamente no localhost:8000/warName/jspFileName.jsp . Uma amostra JSP Ação está disponível no localhost:8000/library/jsp . Lembre -se de que eu implementei me analisando para que a formatação/sintaxe de código estranha possa quebrá -la.
Para demonstrar o contêiner do servlet, implementei um aplicativo simples que simula um banco de dados de livro. Os livros podem ser adicionados, removidos e atualizados usando formulários HTML. Todo o livro também pode ser visualizado na tabela HTML. O front -end está totalmente desenvolvido usando o JSP. O aplicativo é abastecido na biblioteca.war usando a tarefa war Gradle, movida para deploy e carregar para o servidor quando ele iniciar.
Terminais:
Existem mais de 30 testes para verificar a maior parte da funcionalidade e do aplicativo de demonstração. É preferido executá -los da Intellij devido a problemas explicados em perguntas frequentes . Como alternativa, eles podem ser executados usando:
./gradlew test
Este projeto fazia parte de um curso universitário e eu não tinha permissão para usar nenhuma biblioteca de terceiros, exceto Junit.
Existem problemas com as portas de soquete que já estão em uso. Eu nunca descessei para consertar, mas na maioria das vezes funciona quando foi executado de Intellij.
No momento, a resolução de qual servlet deve lidar com o URL fornecido é bastante primitivo. Basicamente, o url Server procura um servlet cujo servletUrl é um prefixo de url . Se houver muitos com o prefixo mais longo, será selecionado. Isso significa que, se um servlet tiver servletUrl igual a /app/home ele também manipulará /app/home/nonexisting/ , /app/home/12345 etc.
Qualquer um é bem -vindo para contribuir com este projeto. Criei alguns problemas de boa primeira emissão, então fique à vontade para conferi-los :)