Skip to content

Commit

Permalink
Add support for HTTP PATCH method
Browse files Browse the repository at this point in the history
The HTTP PATCH method is now supported whereever HTTP methods are used.
Annotated controllers can be mapped to RequestMethod.PATCH.

On the client side the RestTemplate execute(..) and exchange(..)
methods can be used with HttpMethod.PATCH. In terms of HTTP client
libraries, Apache HttpComponents HttpClient version 4.2 or later is
required (see HTTPCLIENT-1191). The JDK HttpURLConnection does not
support the HTTP PATCH method.

Issue: SPR-7985
  • Loading branch information
rstoyanchev committed Jun 22, 2012
1 parent f05e2bc commit a074745
Show file tree
Hide file tree
Showing 18 changed files with 211 additions and 98 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ project('spring-web') {
compile("commons-fileupload:commons-fileupload:1.2", optional)
runtime("commons-io:commons-io:1.3", optional)
compile("commons-httpclient:commons-httpclient:3.1", optional)
compile("org.apache.httpcomponents:httpclient:4.1.1", optional)
compile("org.apache.httpcomponents:httpclient:4.2", optional)
compile("org.codehaus.jackson:jackson-mapper-asl:1.4.2", optional)
compile("com.fasterxml.jackson.core:jackson-databind:2.0.1", optional)
compile("taglibs:standard:1.1.2", optional)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,6 +26,6 @@
*/
public enum HttpMethod {

GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE
GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -141,6 +141,9 @@ protected HttpMethodBase createCommonsHttpMethod(HttpMethod httpMethod, String u
return new PutMethod(uri);
case TRACE:
return new TraceMethod(uri);
case PATCH:
throw new IllegalArgumentException(
"HTTP method PATCH not available before Apache HttpComponents HttpClient 4.2");
default:
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,12 +17,9 @@
package org.springframework.http.client;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URI;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
Expand All @@ -40,6 +37,10 @@
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that uses
Expand Down Expand Up @@ -155,11 +156,30 @@ protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
return new HttpPut(uri);
case TRACE:
return new HttpTrace(uri);
case PATCH:
return createHttpPatch(uri);
default:
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
}
}

private HttpUriRequest createHttpPatch(URI uri) {
String className = "org.apache.http.client.methods.HttpPatch";
ClassLoader classloader = this.getClass().getClassLoader();
if (!ClassUtils.isPresent(className, classloader)) {
throw new IllegalArgumentException(
"HTTP method PATCH not available before Apache HttpComponents HttpClient 4.2");
}
try {
Class<?> clazz = classloader.loadClass(className);
Constructor<?> constructor = clazz.getConstructor(URI.class);
return (HttpUriRequest) constructor.newInstance(uri);
}
catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate " + className, ex);
}
}

/**
* Template method that allows for manipulating the {@link HttpUriRequest} before it is
* returned as part of a {@link HttpComponentsClientHttpRequest}.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -152,7 +152,7 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod
else {
connection.setInstanceFollowRedirects(false);
}
if ("PUT".equals(httpMethod) || "POST".equals(httpMethod)) {
if ("PUT".equals(httpMethod) || "POST".equals(httpMethod) || "PATCH".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,7 +21,7 @@
import org.springframework.http.MediaType;

/**
* Exception thrown when a client POSTs or PUTs content
* Exception thrown when a client POSTs, PUTs, or PATCHes content of a type
* not supported by request handler.
*
* @author Arjen Poutsma
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@

/**
* The HTTP request methods to map to, narrowing the primary mapping:
* GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE.
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit
* this HTTP method restriction (i.e. the type-level restriction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,7 +22,7 @@
* {@link RequestMapping} annotation.
*
* <p>Note that, by default, {@link org.springframework.web.servlet.DispatcherServlet}
* supports GET, HEAD, POST, PUT and DELETE only. DispatcherServlet will
* supports GET, HEAD, POST, PUT, PATCH and DELETE only. DispatcherServlet will
* process TRACE and OPTIONS with the default HttpServlet behavior unless
* explicitly told to dispatch those request types as well: Check out
* the "dispatchOptionsRequest" and "dispatchTraceRequest" properties,
Expand All @@ -36,6 +36,6 @@
*/
public enum RequestMethod {

GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -44,24 +44,24 @@
import org.springframework.util.MultiValueMap;

/**
* {@link javax.servlet.Filter} that makes form encoded data available through
* the {@code ServletRequest.getParameter*()} family of methods during HTTP PUT
* requests.
*
* <p>The Servlet spec requires form data to be available for HTTP POST but
* not for HTTP PUT requests. This filter intercepts HTTP PUT requests where
* content type is {@code 'application/x-www-form-urlencoded'}, reads form
* encoded content from the body of the request, and wraps the ServletRequest
* {@link javax.servlet.Filter} that makes form encoded data available through
* the {@code ServletRequest.getParameter*()} family of methods during HTTP PUT
* or PATCH requests.
*
* <p>The Servlet spec requires form data to be available for HTTP POST but
* not for HTTP PUT or PATCH requests. This filter intercepts HTTP PUT and PATCH
* requests where content type is {@code 'application/x-www-form-urlencoded'},
* reads form encoded content from the body of the request, and wraps the ServletRequest
* in order to make the form data available as request parameters just like
* it is for HTTP POST requests.
*
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HttpPutFormContentFilter extends OncePerRequestFilter {

private final FormHttpMessageConverter formConverter = new XmlAwareFormHttpMessageConverter();

/**
* The default character set to use for reading form data.
*/
Expand All @@ -73,7 +73,7 @@ public void setCharset(Charset charset) {
protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

if ("PUT".equals(request.getMethod()) && isFormContentType(request)) {
if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && isFormContentType(request)) {
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
@Override
public InputStream getBody() throws IOException {
Expand Down Expand Up @@ -102,7 +102,7 @@ private boolean isFormContentType(HttpServletRequest request) {
}

private static class HttpPutFormContentRequestWrapper extends HttpServletRequestWrapper {

private MultiValueMap<String, String> formParameters;

public HttpPutFormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> parameters) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -68,6 +68,7 @@ public static void startJettyServer() throws Exception {
jettyContext.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
jettyContext.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch");
jettyServer.start();
}

Expand Down Expand Up @@ -160,7 +161,7 @@ public void httpMethods() throws Exception {
assertHttpMethod("delete", HttpMethod.DELETE);
}

private void assertHttpMethod(String path, HttpMethod method) throws Exception {
protected void assertHttpMethod(String path, HttpMethod method) throws Exception {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/methods/" + path), method);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,10 +16,26 @@

package org.springframework.http.client;

import java.net.ProtocolException;

import org.junit.Test;
import org.springframework.http.HttpMethod;

public class BufferedSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {

@Override
protected ClientHttpRequestFactory createRequestFactory() {
return new SimpleClientHttpRequestFactory();
}

@Test
public void httpMethods() throws Exception {
try {
assertHttpMethod("patch", HttpMethod.PATCH);
}
catch (ProtocolException ex) {
// Currently HttpURLConnection does not support HTTP PATCH
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,14 +16,21 @@

package org.springframework.http.client;

import org.springframework.http.client.AbstractHttpRequestFactoryTestCase;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import java.net.URI;

import org.junit.Test;
import org.springframework.http.HttpMethod;

public class CommonsHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {

@Override
protected ClientHttpRequestFactory createRequestFactory() {
return new CommonsClientHttpRequestFactory();
}

@Test(expected=IllegalArgumentException.class)
public void httpPatch() throws Exception {
factory.createRequest(new URI(baseUrl + "/methods/PATCH"), HttpMethod.PATCH);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,10 +16,19 @@

package org.springframework.http.client;

import org.junit.Test;
import org.springframework.http.HttpMethod;

public class HttpComponentsClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {

@Override
protected ClientHttpRequestFactory createRequestFactory() {
return new HttpComponentsClientHttpRequestFactory();
}

@Test
public void httpMethods() throws Exception {
assertHttpMethod("patch", HttpMethod.PATCH);
}

}
Loading

0 comments on commit a074745

Please sign in to comment.