/ / El método PUT con AJAX en Spring 3.2 no funciona: json, spring, jquery, jackson, put

El método PUT con AJAX en Spring 3.2 no funciona: json, spring, jquery, jackson, put

Estoy intentando llamar a un método en Spring (3.2.0) a través de AJAX usando el siguiente jQuery 1.6.

function updateRoleEnabled(id)
{
$.ajax({
datatype:"json",
type: "PUT",
url: "/wagafashion/ajax/UpdateUserRole.htm",
data: "id="+id+"&t="+new Date().getTime(),
success: function(response)
{
},
error: function(e)
{
alert("Error: " + e);
}
});
}

Intenta invocar el siguiente método en Spring.

@RequestMapping(value=("ajax/UpdateUserRole"), method=RequestMethod.PUT)
public @ResponseBody void updateUserRole(@RequestParam(value=("id")) String id)
{
System.out.println("id = "+id);
}

FireFox responde con el siguiente error.

Estado HTTP 405: no se admite el método de solicitud "GET"

tipo informe de estado

mensaje El método de solicitud "GET" no es compatible

descripción El método HTTP especificado no está permitido para el recurso (el método de solicitud "GET" no es compatible).

Apache Tomcat / 6.0.26

Funciona con el GET y POST métodos y JSON (con Jackson-2.1.1) también funciona bien en otras partes de la aplicación.


Si necesita ver el dispatcher-servlet.xml archivo, el contenido completo es el siguiente.

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

<context:component-scan base-package="controller" />
<context:component-scan base-package="validatorbeans" />

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" >
<mvc:message-converters register-defaults="false">
<bean id="jacksonMessageConverter" p:supportedMediaTypes="application/json" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="false" />
<property name="ignoreAcceptHeader" value="false" />
<property name="mediaTypes" >
<value>
atom=application/atom+xml
html=text/html
json=application/json
*=*/*
</value>
</property>
</bean>

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">
fileUploadingFailure
</prop>
</props>
</property>
</bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="index.htm">indexController</prop>
</props>
</property>
</bean>

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp" />

<bean name="indexController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController"
p:viewName="index" />
</beans>

Cómo hacer métodos HTTP distintos a GET y POST trabajar en Spring 3.2?


EDITAR:

Basado en el comentario a continuación, el siguiente es mi web.xml archivo.

<?xml version="1.0" encoding="utf-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>



<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>filter.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/admin_side/*</url-pattern>
</filter-mapping>


<filter>
<filter-name>FileUploadFilter</filter-name>
<filter-class>com.ckfinder.connector.FileUploadFilter</filter-class>
<init-param>
<param-name>sessionCookieName</param-name>
<param-value>JSESSIONID</param-value>
</init-param>
<init-param>
<param-name>sessionParameterName</param-name>
<param-value>jsessionid</param-value>
</init-param>
</filter>


<filter-mapping>
<filter-name>FileUploadFilter</filter-name>
<url-pattern>
/ckfinder/core/connector/java/connector.java
</url-pattern>
</filter-mapping>

<filter>
<filter-name>multipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>





<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<description>ServletContextListener</description>
<listener-class>listener.UnregisterDatabaseDrivers</listener-class>
</listener>
<servlet>
<servlet-name>ConnectorServlet</servlet-name>
<servlet-class>com.ckfinder.connector.ConnectorServlet</servlet-class>
<init-param>
<param-name>XMLConfig</param-name>
<param-value>/WEB-INF/config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ConnectorServlet</servlet-name>
<url-pattern>
/ckfinder/core/connector/java/connector.java
</url-pattern>
</servlet-mapping>


<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>




<error-page>
<description>Missing login</description>
<error-code>401</error-code>
<location>/WEB-INF/jsp/admin_side/ErrorPage.jsp</location>
</error-page>

<error-page>
<description>Forbidden directory listing</description>
<error-code>403</error-code>
<location>/WEB-INF/jsp/admin_side/ErrorPage.jsp</location>
</error-page>

<error-page>
<description>Missing page</description>
<error-code>404</error-code>
<location>/WEB-INF/jsp/admin_side/ErrorPage.jsp</location>
</error-page>

<error-page>
<description>Uncaught exception</description>
<error-code>500</error-code>
<location>/WEB-INF/jsp/admin_side/ErrorPage.jsp</location>
</error-page>

<error-page>
<description>Unsupported servlet method</description>
<error-code>503</error-code>
<location>/WEB-INF/jsp/admin_side/ErrorPage.jsp</location>
</error-page>



<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>redirect.jsp</welcome-file>
</welcome-file-list>
</web-app>

Respuestas

2 para la respuesta № 1

A menos que uno esté usando solo parámetros de ruta, el procesamiento de un HTTP PUT regular necesita algo más de trabajo.

Ya que Primavera 3.1, HttpPutFormContentFilter se puede usar para hacer @RequestParam trabajar para application/x-www-form-urlencoded datos:

Filtro que hace que los datos codificados en formato estén disponibles a través del ServletRequest.getParameter*() familia de métodos durante las solicitudes HTTP PUT.

La especificación de servlet requiere que los datos del formulario estén disponibles para HTTP POST pero no para solicitudes HTTP PUT. Este filtro intercepta solicitudes HTTP PUT donde el tipo de contenido es "application/x-www-form-urlencoded", lee el contenido codificado del formulario del cuerpo de la solicitud y envuelve el ServletRequest para que los datos del formulario estén disponibles como parámetros de solicitud, tal como lo está para las solicitudes HTTP POST.

Sin embargo: este filtro consume el flujo de entrada de la solicitud, por lo que no está disponible para convertidores como FormHttpMessageConverter, como se usa para @RequestBody MultiValueMap<String, String> o HttpEntity<MultiValueMap<String, String>>. Como resultado, una vez que tenga configurado el filtro anterior en su aplicación, obtendrá "IOException: stream closed" al invocar métodos que usan otros convertidores que también esperan raw application/x-www-form-urlencoded PONER datos.


Alternativamente, uno puede hacer todo manualmente, usando @RequestBody o HttpEntity<?>:

@RequestMapping(value="ajax/UpdateUserRole", method=RequestMethod.PUT,
produces = MediaType.TEXT_PLAIN_VALUE)
public @ResponseBody String updateUserRole(
@RequestBody final MultiValueMap<String, String> data,
final HttpServletResponse response) {
Map<String, String> params = data.toSingleValueMap();
String id = params.get("id");
String a = params.get("a");
String b = params.get("b");
if(id == null || a == null || b == null) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
return "id = " + id;
}

Ver también un ejemplo usando WebDataBinder, o usar:

public ResponseEntity<String> updateUserRole(
final HttpEntity<MultiValueMap<String, String>> entity) {
Map<String, String> params = entity.getBody().toSingleValueMap();
String id = params.get("id");
...


Tenga en cuenta que para realizar pruebas, utilice MockMvc "s mockMvc.perform(put(url).param(name, value)) en realidad, también funcionaría con el código de la pregunta, aunque fallaría en un contenedor de servlets. Pero MockMvc no se está ejecutando en tal contenedor de servlets, por lo tanto, lo está engañando un poco.

MockMvc "s .param(name, value) también funciona bien con HttpPutFormContentFilter. Pero al usar MockMvc para probar @RequestBody o HttpEntity<?>, también es necesario crear cualquier application/x-www-form-urlencoded PONER contenido manualmente. Me gusta:

mockMvc.perform(put(url).content("id=" + URLEncoder.encode(id, "utf-8")
+ "&a=" + URLEncoder.encode(a, "utf-8") + "&b=" + ...)

Para poder usar simplemente .param(name, value), al igual que para GET y POST, se podría definir:

public static RequestPostProcessor convertParameters() {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(
final MockHttpServletRequest request) {
if ("PUT".equalsIgnoreCase(request.getMethod()) {
Map<String, String[]> params = request.getParameterMap();
if (params != null) {
StringBuilder content = new StringBuilder();
for (Entry<String, String[]> es : params.entrySet()) {
for (String value : es.getValue()) {
try {
content.append(URLEncoder.encode(es.getKey(), "utf-8"))
.append("=")
.append(URLEncoder.encode(value, "utf-8"))
.append("&");
}
catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("utf-8 not supported");
}
}
}
request.setParameters(new HashMap<String, String[]>());
request.setContent(content.toString().getBytes());
request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
}
}
return request;
}
};
}

... y luego usar .with(convertParameters()) cerca de .param(name, value):

mockMvc.perform(put(url)
.with(convertParameters())
.param("id", id).param("a", a).param("b", b) ...)

Dado todo lo anterior, simplemente usando HttpPutFormContentFilter para application/x-www-form-urlencoded los datos realmente hacen la vida más fácil.

Cuando el navegador no envía application/x-www-form-urlencoded datos, pero cosas como JSON, luego intentar mapear a MultiValueMap producirá 415 tipos de medios no admitidos. En su lugar, usa algo como @RequestBody MyDTO data o HttpEntity<MyDTO> entity como se explica en Analizando JSON en Spring MVC usando Jackson JSON.