Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

_Isis_

JSP + Servlet : doPost não é executado ao postar p/ mesmo JSP

Recommended Posts

Galera, comecei a estudar Java web faz algumas semanas. Como tive problemas com os livros, acabei ficando com o "Essential JEE", que é bem seco, sem passo-a-passo.

Na parte de web conheço mais o funcionamento do Django (MVC), então acredito que o problema deve ser relacionado a alguma configuração ou ao esquema request-response em Java.

Container: WildFly 8

IDE: Eclipse

Java 7

web.xml (a parte relevante)

  <servlet>
    <servlet-name>QuadFactorialServlet</servlet-name>
    <jsp-file>/WEB-INF/jsps/QuadFactorial.jsp</jsp-file>
  </servlet>
  <servlet-mapping>
    <servlet-name>QuadFactorialServlet</servlet-name>
    <url-pattern>/QuadFactorial</url-pattern>
  </servlet-mapping>

QuadFactorial.jsp (em WebContent/WEB-INF/jsps)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form id="fac" method="POST" action="${pageContext.request.contextPath}/QuadFactorial">
		<label for="number">Informe n:</label>
		<input id="number" name="number" type="text"/>
		<input type="submit" value="Enviar" name="submit"/>
	</form>
</body>
</html>

QuadFactorialServlet.java

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name="QuadFactorialServlet")
public class QuadFactorialServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	/* http://introcs.cs.princeton.edu/java/91float/Gamma.java.html */
	private static double logGamma(double x) {
	      double tmp = (x - 0.5) * Math.log(x + 4.5) - (x + 4.5);
	      double ser = 1.0 + 76.18009173    / (x + 0)   - 86.50532033    / (x + 1)
	                       + 24.01409822    / (x + 2)   -  1.231739516   / (x + 3)
	                       +  0.00120858003 / (x + 4)   -  0.00000536382 / (x + 5);
	      return tmp + Math.log(ser * Math.sqrt(2 * Math.PI));
	}
	private static double gamma(double x) { return Math.exp(logGamma(x)); }
    
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Map<String, String[]> m = request.getParameterMap();
		PrintWriter pr = response.getWriter();
		String message = "";
		
		if (m.size() > 0) {
			int n = Integer.parseInt(m.get("number")[0]);
			if (n < 0) {
				message = "O número deve ser maior ou igual a zero.";
			} else {
				/*double f = gamma(n+1+n)/gamma(n+1);
				message = Double.toString(f);*/
			}
		}
	}

}

A ideia era aceitar um inteiro via formulário e calcular o fatorial quádruplo desse número (por enquanto não estou querendo fazer redirects,forwards nem imprimir texto no JSP. Poderia até ser no console do servidor). Mas o problema é que, mesmo definindo um breakpoint, quando dou 'Debug As' no jsp dentro da IDE, a página é exibida quando requisição = GET (http://localhost:8080/jee_exp/QuadFactorial) e não acontece nada quando requisição = POST.

 

Fui dar uma olhada no valor interpolado no fonte da página e está /jee_exp/QuadFactorial. Então achei que, por algum motivo estranho, a requisição poderia ser um GET, então adicionei um doGet, botei um breakpoint lá e abri o DevTools no Chrome. Não. É um POST mesmo e continua não chegando ao breakpoint no método doPost (1a linha).

 

Também achei que fosse a falta do @Override no método. Com ou sem, continua dando problema.

 

Alguém sabe me dizer o que está acontecendo?

OFF: Pelo que já li, os servlets+web.xml com o mapeamento do jsp são quase a mesma coisa que as class-based views do Django (p/ quem não conhece: https://docs.djangoproject.com/en/1.8/topics/class-based-views/). Esse está sendo meu modelo de comparação.


UPDATE 15/06/2015

Resolvi criar um segundo servlet chamado TestPostServlet, associado ao url /testPost. Alterei a action do meu formulário p/ ${pageContext.request.contextPath}/testPost. Agora o doPost do novo servlet é chamado.
Então fica a pergunta: como faço p/ que o mesmo Servlet processe ambas as requisições (GET e POST) a partir do mesmo JSP? É necessário configurar algo a mais?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não tive a oportunidade de tentar reproduzir o que fizeste. Porém encontrei duas coisas que causaram estranheza. Primeiramente, não se deve colocar os arquivos JSP dentro da pasta WEB-INF. Em WEB-INF deve-se adicionar apenas arquivos de configuração e meta-dados. Num projeto seguindo o padrão Maven, sua estrutura de pastas deveria ser algo semelhante a:

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── github
        │           └── aureliano
        │               └── servlets
        │                   └── QuadFactorialServlet.java
        ├── resources
        └── webapp
            ├── QuadFactorial.jsp
            └── WEB-INF
                └── web.xml

A outra coisa digna de nota é a sua sobrescrita de método. Sobrescreveste o método doPost deixando-o com acesso a nível de pacote/herança (protected). Recomendo deixar com visibilidade pública. Assim o método ficará visível fora do pacote.

 

Testa as duas coisas e vê no que dá.

Compartilhar este post


Link para o post
Compartilhar em outros sites

A sobrescrita foi gerada pela IDE. Protected, public, tanto faz. Não funciona quando o POST vem do JSP associado à URL.

O interessante é que fazendo o POST p/ outro servlet (em outro URL) com os métodos protected, tal como gerados pela IDE, a coisa funciona. Então o problema não deve ser visibilidade de método.

 

Esqueça Maven. Não estou usando isso (provavelmente não vou usar, já que meu objetivo não é construir aplicações em Java*). Antes de mudar o jsp p/ dentro do WEB-INF já não funcionava também.

* Estou estudando isso porque volta e meia me pedem teste de desempenho e é horrível fazer isso sem entender o básico do ciclo de vida de um JSP ou JSF.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Para tratar tanto o GET quanto o POST, pode criar um método único que recebe os objetos de requisição e resposta. Nos métodos doPost e doGet, você faz uma chamada ao método que criou, exemplo:

protected void doGetPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    // código
} 

Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        doGetPost(request, response);
} 

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        doGetPost(request, response);
}

O primeiro problema foi resolvido?

Compartilhar este post


Link para o post
Compartilhar em outros sites

O doPost não é executado => não adianta chamar outro método dentro dele.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Hoje eu tentei reproduzir o cenário que descreveste. Criando um projeto com as mesmas configurações obtive o mesmo resultado que tu. Apliquei as recomendações que eu propus e ainda assim não funcionou. Então comecei a alterar as configurações do projeto. Primeiramente, alterei a estrutura de pasta seguindo o padrão Maven. É verdade, que a princípio, pode parecer que Maven é inútil pra ti, levando em consideração que pretendes apenas trabalhar com testes. Todavia, creio ser importante conhecer os padrões de estrutura de projetos Web. Assim fica mais fácil saber onde fica cada coisa. Como disse no meu post anterior, a estrutura ficou como abaixo:

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── github
        │           └── aureliano
        │               └── servlets
        │                   └── QuadFactorialServlet.java
        ├── resources
        └── webapp
            ├── QuadFactorial.jsp
            └── WEB-INF
                └── web.xml

A primeira coisa que alterei foi o web.xml. O que observei é que na configuração do servlet tu não colocaste a tag servlet-class: que é necessária para que o web container possa instanciar o servlet. Por isso substituí a tag jsp-file por servlet-class. Meu web.xml ficou assim:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>QuadFactorialServlet</servlet-name>
    <servlet-class>com.github.aureliano.servlets.QuadFactorialServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>QuadFactorialServlet</servlet-name>
    <url-pattern>/QuadFactorial</url-pattern>
  </servlet-mapping>
</web-app>

Tive que aplicar uma outra reforma no arquivo JSP. No atributo "action" do formulário tive que substituir ${pageContext.request.contextPath} por <%= request.getContextPath() %>. A primeira, salvo engano, trata-se de sintaxe JSTL. Como eu não adicionei JSTL como dependência do meu projeto, utilizei scriptlet. Com as três alterações que descrevi: alterar estrutura de pastas, modificar configuração do servlet no web.xml e alterar a tag action do JSP; consegui fazer com que o servlet funcionasse adequadamente. Tanto o método GET quanto POST são respondidos por ele. Para provar isso dupliquei o formulário na página. Um formulário com o método GET e outro com POST.

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
  </head>

  <body>
    <form id="fac" method="POST" action="<%= request.getContextPath() %>/QuadFactorial">
      <label for="number">Informe n:</label>
      <input id="number" name="number" type="text"/>
      <input type="submit" value="Enviar" name="submit"/>
    </form>
	
    <form id="fac" method="GET" action="<%= request.getContextPath() %>/QuadFactorial">
      <label for="number">Informe n:</label>
      <input id="number" name="number" type="text"/>
      <input type="submit" value="Enviar" name="submit"/>
    </form>
  </body>
</html>

E o meu servlet foi alterado pra imprimir no log do Tomcat uma mensagem com o método tratado e outra com o valor calculado.

package com.github.aureliano.servlets;

import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class QuadFactorialServlet extends HttpServlet {

  private static final long serialVersionUID = 1L;

  private static double gamma(double x) {
    return Math.exp(logGamma(x));
  }

  private static double logGamma(double x) {
    double tmp = (x - 0.5) * Math.log(x + 4.5) - (x + 4.5);
    double ser = 1.0 + 76.18009173 / (x + 0) - 86.50532033 / (x + 1) + 24.01409822 / (x + 2) - 1.231739516 / (x + 3) + 0.00120858003
        / (x + 4) - 0.00000536382 / (x + 5);
    return tmp + Math.log(ser * Math.sqrt(2 * Math.PI));
  }

  private void applyRequest(HttpServletRequest request, HttpServletResponse response) {
    Map<String, String[]> m = request.getParameterMap();
    String message = "";

    if (m.size() > 0) {
      int n = Integer.parseInt(m.get("number")[0]);
      if (n < 0) {
        message = "O número deve ser maior ou igual a zero.";
      } else {
        double f = gamma(n + 1 + n) / gamma(n + 1);
        message = Double.toString(f);
      }
      System.out.println(">>> VALOR CALCULADO: " + message);
    }
  }

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println(" << HTTP GET >>");
    this.applyRequest(request, response);
  }

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println(" << HTTP POST >>");
    this.applyRequest(request, response);
  }
}

Ao informar o valor 5 no formulário de método GET obtive esta saída:

 << HTTP GET >>
>>> VALOR CALCULADO: 30239.999999013267

E ao informar o valor 5 no formulário de método POST obtive esta saída:

 << HTTP POST >>
>>> VALOR CALCULADO: 30239.999999013267

PS: Testei tanto os métodos doGet e doPost com acesso protected como public. Ambos funcionaram. Entretanto prefiro utilizá-los com acesso público.

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Vergil
Tentei aqui sua solução. Ela muda a URL ao postar o dado,certo? É uma das coisas que não quero. Li um básico sobre servlets e jsp (e pelo visto esse básico nem delimita direito o domínio das soluções) e como não consigo encontrar material explicativo sobre a "arquitetura" da coisa toda (funcionamento de servlets e jsp em conjunto, interferência de um em outro,etc), resolvi fazer experiências. Começando do cru mesmo antes de tentar JSF.
Mas vou dar o crédito da solução p/ vc porque não teria como vc saber exatamente o que eu queria apenas pela descrição do problema.


A única forma que consegui visualizar p/ resolver é remover a tag jsp-file do web.xml e fazer o dispatch do jsp direto do servlet (que, p/ diferenciar do servlet no qual o jsp se transforma, na minha cabeça virou "backend servlet") no doGet.

/WebContent/jsp/number_input.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
 <form name="myform" method="POST" action="${pageContext.request.contextPath}/Factorial">
 	<input type="text" name="number"/>
 	<input type="submit"/>
 </form>
</body>
</html>

FactorialServlet.java

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name="FactorialServlet", urlPatterns={"/Factorial"})
public class FactorialServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.getRequestDispatcher("/jsp/number_input.jsp").forward(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("ajshdkahsd");
		doGet(request, response);
	}

}

O que entupiria o doGet caso eu passasse os parâmetros via GET. Fora a "persistência" dos dados entre uma requisição e outra, no caso de mensagens e exibição realçada de dados incorretos.

Com esse forward eu consigo manter a URL inalterada (não vou expor "caminhos internos" p/ usuário), ter uma url sem nome de templates e não espalhar requisições p/ outro servlet (como se a página tivesse que ser auto-contida).

Claro, eu poderia fazer o cálculo usando Javascript no lado do cliente mesmo, mas estou num lugar onde dev web não conhece Javascript. Então tenho que pensar nessas coisas bizonhas. Mas basicamente estou escrevendo código que deveria ser responsabilidade do core de um framework mais "unificado".

Outra ideia que me surgiu, mas que não sei se vou implementar nem de curiosidade, é estender o HttpServlet p/ que o doGet herdado já faça o forward do template. Não sei se existe a possibilidade de incluir um atributo na annotation @WebServlet nem se dá pra criar uma configuração no web.xml associando servlets a um diretório de template p/ busca. Não tenho conhecimento suficiente da arquitetura p/ fazer isso ainda.

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

Tentei aqui sua solução. Ela muda a URL ao postar o dado,certo? É uma das coisas que não quero.

Bem, ele muda URL porque o formulário é submetido a uma ação que aponta pra um recurso diferente. Ou seja, a página JSP é um recurso e o servlet é outro. Então creio que este seja o comportamento esperado. Seria o caso de no Servlet haver um redirect. Isso evitaria por exemplo que o usuário duplicasse submissão de formulário (PRG).

 

 

 

A única forma que consegui visualizar p/ resolver é remover a tag jsp-file do web.xml e fazer o dispatch do jsp direto do servlet (que, p/ diferenciar do servlet no qual o jsp se transforma, na minha cabeça virou "backend servlet") no doGet.

Confesso que não entendi direito. Mas não seria o caso de substituir o forward pelo redirect? O forward faz com que o servidor aponte para outro recurso. E como a operação ocorre no servidor, a URL não muda justamente pelo fato do cliente não saber pra qual recurso o servidor o mandou. Com o redirect é diferente, uma vez que o servidor envia ao cliente uma mensagem para que o usuário abra uma nova requisição apontando pro novo recurso. Com o redirect a URL fica correta e sem perigo de haver submissão duplicada.

 

 

O que entupiria o doGet caso eu passasse os parâmetros via GET. Fora a "persistência" dos dados entre uma requisição e outra, no caso de mensagens e exibição realçada de dados incorretos.

 

Com esse forward eu consigo manter a URL inalterada (não vou expor "caminhos internos" p/ usuário), ter uma url sem nome de templates e não espalhar requisições p/ outro servlet (como se a página tivesse que ser auto-contida).

 

Claro, eu poderia fazer o cálculo usando Javascript no lado do cliente mesmo, mas estou num lugar onde dev web não conhece Javascript. Então tenho que pensar nessas coisas bizonhas. Mas basicamente estou escrevendo código que deveria ser responsabilidade do core de um framework mais "unificado".

 

Outra ideia que me surgiu, mas que não sei se vou implementar nem de curiosidade, é estender o HttpServlet p/ que o doGet herdado já faça o forward do template. Não sei se existe a possibilidade de incluir um atributo na annotation @WebServlet nem se dá pra criar uma configuração no web.xml associando servlets a um diretório de template p/ busca. Não tenho conhecimento suficiente da arquitetura p/ fazer isso ainda.

Utilizando Servlet puro, infelizmente, você teria que implementar os forwards e redirects. Todavia, existe uma série de frameworks que já implementam todo esse trabalho de roteamento, carregamento de templates, ciclo de vida de objetos entre requisições etc. Se tiver interesse em ter estes recursos a disposição, seria o caso de partir pra um JSF ou congênere.

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Vergil
O redirect que fiz foi indicando o JSP, não o servlet. Confesso que até agora não sei a diferença.

 

Utilizando Servlet puro, infelizmente, você teria que implementar os forwards e redirects. Todavia, existe uma série de frameworks que já implementam todo esse trabalho de roteamento, carregamento de templates, ciclo de vida de objetos entre requisições etc. Se tiver interesse em ter estes recursos a disposição, seria o caso de partir pra um JSF ou congênere.


Sim, eu sei disso. Em parte eu me dou mal com livros de programação porque eu quero saber dos detalhes antes de achar que sei fazer alguma coisa com o framework. Como nenhum dos 3 ou 4 livros de servlet e JSP que peguei p/ ler mencionou o que acontece (e porque acontece) quando você mapeia o jsp e o servlet na mesma url com a tag jsp-file... Às vezes me parece que os autores acreditam que quem lê esses livros não vai pensar "e se?".

 

Confesso que não entendi direito.


No início imaginei que dentro do container poderia haver alguma configuração que relacionasse a url com o template (JSP) e o controlador (servlet). Por isso amarrei as duas coisas no web.xml na mesma URL. Como não sei nada de JSP vou tentar mostrar como eu conheço p/ você entender a origem da confusão.

Na parte que corresponderia ao servlet (controle,lógica) eu teria algo assim:

from django.views.generic import TemplateView

class AboutView(TemplateView):
    template_name = "about.html"

Essa super-classe TemplateView já tem o método get implementado. Ela, por sua vez, herda de TemplateResponseMixin, que contém o nome do template. (Você pode ver o código dessas coisas aqui: https://github.com/django/django/blob/master/django/views/generic/base.py)

O que foi feito nessa AboutView foi apenas definir o template que eu quero usar (uma página estática). Pensei que a tag jsp-file associada ao nome do servlet tivesse o mesmo efeito que a declaração do AboutView.

As rotas estão num arquivo urls.py:

from some_app.views import AboutView

urlpatterns = [
    url(r'^about/', AboutView.as_view()),
]

Esse método "as_view" vem da classe View, da qual a classe TemplateView é filha (e por extensão, a AbouView tb). Quando se chama esse método, ocorrem uma série de coisas que vão desembocar no método dispatch, que vai chamar o método correto das subclasses de acordo com o tipo de requisição (GET, POST, PUT, etc). Como era um xml, não dava pra botar chamada de método, então imaginei que se eu associasse o servlet à mesma URL do JSP o container saberia como se virar. Mas acabou que nenhum método do servlet era executado, o que me pareceu, à primeira vista, algo bem parecido com o sombreamento de variáveis, mas aplicado a contexto.

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

 

O redirect que fiz foi indicando o JSP, não o servlet. Confesso que até agora não sei a diferença.

A grosso modo, um servlet é uma classe java para tratar requisições no escopo do servidor. Antigamente usava-se apenas servlet pra tratar requisições no servidor. Recebia-se a requisição e utilizava-se o próprio servlet pra enviar a resposta (response). Desenhava-se o HTML de resposta com o printWriter (response.getWriter()). Com o aumento da complexidade viu-se a necessidade de se separar as coisas. Então criou-se o JSP, que nada mais é que uma página com marcações HTML e scriptlets, que quando implantada num container é compilada e transformada em código Java. Ou seja, vira um servlet. Depois descobriram que o JSP já não atendia mais. Porque este misturava código HTML com Java. E aí criaram frameworks que implementassem o padrão MVC. Ou seja, hoje em dia não é mais comum o uso de JSP. O JSF 2 já nem usa JSP: usa-se facelets.

 

Melhor do que livros é especificação da API: http://docs.oracle.com/javaee/6/tutorial/doc/bnafd.html

http://download.oracle.com/otndocs/jcp/servlet-3.0-fr-oth-JSpec/

 

 

 

No início imaginei que dentro do container poderia haver alguma configuração que relacionasse a url com o template (JSP) e o controlador (servlet). Por isso amarrei as duas coisas no web.xml na mesma URL.

No Java tudo, ou pelo menos a maioria, é estático. O web.xml não é lugar pra fazer roteamento. Se fosse o caso do JSF, o lugar certo seria o arquivo faces-config.xml. Nele registra-se as classes de controle, bem como as rotas. Eu até te entendo um pouco, já que eu passei pela situação inversa. Saí de uma plataforma rígida e de código compilado como o Java e fui me aventurar em Ruby com frameworks como Padrino e Rails. A diferença de paradigma de implementação é bem diferente, porém os conceitos são similares. Acho que se tu começasses a estudar o JSF sentir-se-ia um pouco mais confortável.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.