Browse Source
This commit ensures that POST/PUT requests sent by the Netty client have a Content-Length header set. Integration tests have been refactored to use mockwebserver instead of Jetty and have been parameterized to run on all available supported clients. Issue: SPR-14860pull/1262/head
9 changed files with 407 additions and 603 deletions
@ -1,202 +0,0 @@
@@ -1,202 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2015 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.http.client; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.Enumeration; |
||||
import java.util.Map; |
||||
|
||||
import javax.servlet.GenericServlet; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.ServletRequest; |
||||
import javax.servlet.ServletResponse; |
||||
import javax.servlet.http.HttpServlet; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.eclipse.jetty.server.Connector; |
||||
import org.eclipse.jetty.server.NetworkConnector; |
||||
import org.eclipse.jetty.server.Server; |
||||
import org.eclipse.jetty.servlet.ServletContextHandler; |
||||
import org.eclipse.jetty.servlet.ServletHolder; |
||||
|
||||
import org.junit.AfterClass; |
||||
import org.junit.BeforeClass; |
||||
|
||||
import org.springframework.util.StreamUtils; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* @author Arjen Poutsma |
||||
* @author Sam Brannen |
||||
*/ |
||||
public abstract class AbstractJettyServerTestCase { |
||||
|
||||
private static Server jettyServer; |
||||
|
||||
protected static String baseUrl; |
||||
|
||||
@BeforeClass |
||||
public static void startJettyServer() throws Exception { |
||||
|
||||
// Let server pick its own random, available port.
|
||||
jettyServer = new Server(0); |
||||
|
||||
ServletContextHandler handler = new ServletContextHandler(); |
||||
handler.setContextPath("/"); |
||||
|
||||
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo"); |
||||
handler.addServlet(new ServletHolder(new ParameterServlet()), "/params"); |
||||
handler.addServlet(new ServletHolder(new StatusServlet(200)), "/status/ok"); |
||||
handler.addServlet(new ServletHolder(new StatusServlet(404)), "/status/notfound"); |
||||
handler.addServlet(new ServletHolder(new MethodServlet("DELETE")), "/methods/delete"); |
||||
handler.addServlet(new ServletHolder(new MethodServlet("GET")), "/methods/get"); |
||||
handler.addServlet(new ServletHolder(new MethodServlet("HEAD")), "/methods/head"); |
||||
handler.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options"); |
||||
handler.addServlet(new ServletHolder(new PostServlet()), "/methods/post"); |
||||
handler.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put"); |
||||
handler.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch"); |
||||
|
||||
jettyServer.setHandler(handler); |
||||
jettyServer.start(); |
||||
|
||||
Connector[] connectors = jettyServer.getConnectors(); |
||||
NetworkConnector connector = (NetworkConnector) connectors[0]; |
||||
baseUrl = "http://localhost:" + connector.getLocalPort(); |
||||
} |
||||
|
||||
@AfterClass |
||||
public static void stopJettyServer() throws Exception { |
||||
if (jettyServer != null) { |
||||
jettyServer.stop(); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Servlet that sets a given status code. |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
private static class StatusServlet extends GenericServlet { |
||||
|
||||
private final int sc; |
||||
|
||||
private StatusServlet(int sc) { |
||||
this.sc = sc; |
||||
} |
||||
|
||||
@Override |
||||
public void service(ServletRequest request, ServletResponse response) throws |
||||
ServletException, IOException { |
||||
((HttpServletResponse) response).setStatus(sc); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class MethodServlet extends GenericServlet { |
||||
|
||||
private final String method; |
||||
|
||||
private MethodServlet(String method) { |
||||
this.method = method; |
||||
} |
||||
|
||||
@Override |
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { |
||||
HttpServletRequest httpReq = (HttpServletRequest) req; |
||||
assertEquals("Invalid HTTP method", method, httpReq.getMethod()); |
||||
res.setContentLength(0); |
||||
((HttpServletResponse) res).setStatus(200); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class PostServlet extends MethodServlet { |
||||
|
||||
private PostServlet() { |
||||
super("POST"); |
||||
} |
||||
|
||||
@Override |
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { |
||||
super.service(req, res); |
||||
long contentLength = req.getContentLength(); |
||||
if (contentLength != -1) { |
||||
InputStream in = req.getInputStream(); |
||||
long byteCount = 0; |
||||
byte[] buffer = new byte[4096]; |
||||
int bytesRead; |
||||
while ((bytesRead = in.read(buffer)) != -1) { |
||||
byteCount += bytesRead; |
||||
} |
||||
assertEquals("Invalid content-length", contentLength, byteCount); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class EchoServlet extends HttpServlet { |
||||
|
||||
@Override |
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
||||
echo(req, resp); |
||||
} |
||||
|
||||
private void echo(HttpServletRequest request, HttpServletResponse response) throws IOException { |
||||
response.setStatus(HttpServletResponse.SC_OK); |
||||
response.setContentType(request.getContentType()); |
||||
response.setContentLength(request.getContentLength()); |
||||
for (Enumeration<String> e1 = request.getHeaderNames(); e1.hasMoreElements();) { |
||||
String headerName = e1.nextElement(); |
||||
for (Enumeration<String> e2 = request.getHeaders(headerName); e2.hasMoreElements();) { |
||||
String headerValue = e2.nextElement(); |
||||
response.addHeader(headerName, headerValue); |
||||
} |
||||
} |
||||
StreamUtils.copy(request.getInputStream(), response.getOutputStream()); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class ParameterServlet extends HttpServlet { |
||||
|
||||
@Override |
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
||||
Map<String, String[]> parameters = req.getParameterMap(); |
||||
assertEquals(2, parameters.size()); |
||||
|
||||
String[] values = parameters.get("param1"); |
||||
assertEquals(1, values.length); |
||||
assertEquals("value", values[0]); |
||||
|
||||
values = parameters.get("param2"); |
||||
assertEquals(2, values.length); |
||||
assertEquals("value1", values[0]); |
||||
assertEquals("value2", values[1]); |
||||
|
||||
resp.setStatus(200); |
||||
resp.setContentLength(0); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
package org.springframework.http.client; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import okhttp3.mockwebserver.Dispatcher; |
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import org.hamcrest.Matchers; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat; |
||||
|
||||
/** |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class AbstractMockWebServerTestCase { |
||||
|
||||
private MockWebServer server; |
||||
|
||||
protected int port; |
||||
|
||||
protected String baseUrl; |
||||
|
||||
protected static final MediaType textContentType = |
||||
new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8")); |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
this.server = new MockWebServer(); |
||||
this.server.setDispatcher(new TestDispatcher()); |
||||
this.server.start(); |
||||
this.port = this.server.getPort(); |
||||
this.baseUrl = "http://localhost:" + this.port; |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
protected class TestDispatcher extends Dispatcher { |
||||
@Override |
||||
public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
||||
try { |
||||
if (request.getPath().equals("/echo")) { |
||||
MockResponse response = new MockResponse() |
||||
.setHeaders(request.getHeaders()) |
||||
.setHeader("Content-Length", request.getBody().size()) |
||||
.setResponseCode(200) |
||||
.setBody(request.getBody()); |
||||
request.getBody().flush(); |
||||
return response; |
||||
} |
||||
else if(request.getPath().equals("/status/ok")) { |
||||
return new MockResponse(); |
||||
} |
||||
else if(request.getPath().equals("/status/notfound")) { |
||||
return new MockResponse().setResponseCode(404); |
||||
} |
||||
else if(request.getPath().startsWith("/params")) { |
||||
assertThat(request.getPath(), Matchers.containsString("param1=value")); |
||||
assertThat(request.getPath(), Matchers.containsString("param2=value1¶m2=value2")); |
||||
return new MockResponse(); |
||||
} |
||||
else if(request.getPath().equals("/methods/post")) { |
||||
assertThat(request.getMethod(), Matchers.is("POST")); |
||||
String transferEncoding = request.getHeader("Transfer-Encoding"); |
||||
if(StringUtils.hasLength(transferEncoding)) { |
||||
assertThat(transferEncoding, Matchers.is("chunked")); |
||||
} |
||||
else { |
||||
long contentLength = Long.parseLong(request.getHeader("Content-Length")); |
||||
assertThat("Invalid content-length", |
||||
request.getBody().size(), Matchers.is(contentLength)); |
||||
} |
||||
return new MockResponse().setResponseCode(200); |
||||
} |
||||
else if(request.getPath().startsWith("/methods/")) { |
||||
String expectedMethod = request.getPath().replace("/methods/","").toUpperCase(); |
||||
assertThat(request.getMethod(), Matchers.is(expectedMethod)); |
||||
return new MockResponse(); |
||||
} |
||||
return new MockResponse().setResponseCode(404); |
||||
} |
||||
catch (Throwable exc) { |
||||
return new MockResponse().setResponseCode(500).setBody(exc.toString()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -1,371 +0,0 @@
@@ -1,371 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2016 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.client; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import javax.servlet.GenericServlet; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.ServletRequest; |
||||
import javax.servlet.ServletResponse; |
||||
import javax.servlet.http.HttpServlet; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.apache.commons.fileupload.FileItem; |
||||
import org.apache.commons.fileupload.FileItemFactory; |
||||
import org.apache.commons.fileupload.FileUploadException; |
||||
import org.apache.commons.fileupload.disk.DiskFileItemFactory; |
||||
import org.apache.commons.fileupload.servlet.ServletFileUpload; |
||||
import org.eclipse.jetty.server.Connector; |
||||
import org.eclipse.jetty.server.NetworkConnector; |
||||
import org.eclipse.jetty.server.Server; |
||||
import org.eclipse.jetty.servlet.ServletContextHandler; |
||||
import org.eclipse.jetty.servlet.ServletHolder; |
||||
import org.junit.AfterClass; |
||||
import org.junit.BeforeClass; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.util.FileCopyUtils; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* @author Arjen Poutsma |
||||
* @author Sam Brannen |
||||
*/ |
||||
public class AbstractJettyServerTestCase { |
||||
|
||||
protected static final String helloWorld = "H\u00e9llo W\u00f6rld"; |
||||
|
||||
protected static final MediaType textContentType = |
||||
new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8")); |
||||
|
||||
protected static final MediaType jsonContentType = |
||||
new MediaType("application", "json", Collections.singletonMap("charset", "UTF-8")); |
||||
|
||||
|
||||
private static Server jettyServer; |
||||
|
||||
protected static int port; |
||||
|
||||
protected static String baseUrl; |
||||
|
||||
|
||||
@BeforeClass |
||||
public static void startJettyServer() throws Exception { |
||||
// Let server pick its own random, available port.
|
||||
jettyServer = new Server(0); |
||||
|
||||
ServletContextHandler handler = new ServletContextHandler(); |
||||
byte[] bytes = helloWorld.getBytes(StandardCharsets.UTF_8); |
||||
handler.addServlet(new ServletHolder(new GetServlet(bytes, textContentType)), "/get"); |
||||
handler.addServlet(new ServletHolder(new GetServlet(new byte[0], textContentType)), "/get/nothing"); |
||||
handler.addServlet(new ServletHolder(new GetServlet(bytes, null)), "/get/nocontenttype"); |
||||
handler.addServlet( |
||||
new ServletHolder(new PostServlet(helloWorld, "/post/1", bytes, textContentType)), |
||||
"/post"); |
||||
handler.addServlet( |
||||
new ServletHolder(new JsonPostServlet("/jsonpost/1", jsonContentType)), |
||||
"/jsonpost"); |
||||
handler.addServlet(new ServletHolder(new StatusCodeServlet(204)), "/status/nocontent"); |
||||
handler.addServlet(new ServletHolder(new StatusCodeServlet(304)), "/status/notmodified"); |
||||
handler.addServlet(new ServletHolder(new ErrorServlet(404)), "/status/notfound"); |
||||
handler.addServlet(new ServletHolder(new ErrorServlet(500)), "/status/server"); |
||||
handler.addServlet(new ServletHolder(new UriServlet()), "/uri/*"); |
||||
handler.addServlet(new ServletHolder(new MultipartServlet()), "/multipart"); |
||||
handler.addServlet(new ServletHolder(new FormServlet()), "/form"); |
||||
handler.addServlet(new ServletHolder(new DeleteServlet()), "/delete"); |
||||
handler.addServlet(new ServletHolder(new PatchServlet(helloWorld, bytes, textContentType)), |
||||
"/patch"); |
||||
handler.addServlet( |
||||
new ServletHolder(new PutServlet(helloWorld, bytes, textContentType)), |
||||
"/put"); |
||||
|
||||
jettyServer.setHandler(handler); |
||||
jettyServer.start(); |
||||
|
||||
Connector[] connectors = jettyServer.getConnectors(); |
||||
NetworkConnector connector = (NetworkConnector) connectors[0]; |
||||
port = connector.getLocalPort(); |
||||
baseUrl = "http://localhost:" + port; |
||||
} |
||||
|
||||
@AfterClass |
||||
public static void stopJettyServer() throws Exception { |
||||
if (jettyServer != null) { |
||||
jettyServer.stop(); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** Servlet that sets the given status code. */ |
||||
@SuppressWarnings("serial") |
||||
private static class StatusCodeServlet extends GenericServlet { |
||||
|
||||
private final int sc; |
||||
|
||||
public StatusCodeServlet(int sc) { |
||||
this.sc = sc; |
||||
} |
||||
|
||||
@Override |
||||
public void service(ServletRequest request, ServletResponse response) throws IOException { |
||||
((HttpServletResponse) response).setStatus(sc); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** Servlet that returns an error message for a given status code. */ |
||||
@SuppressWarnings("serial") |
||||
private static class ErrorServlet extends GenericServlet { |
||||
|
||||
private final int sc; |
||||
|
||||
public ErrorServlet(int sc) { |
||||
this.sc = sc; |
||||
} |
||||
|
||||
@Override |
||||
public void service(ServletRequest request, ServletResponse response) throws IOException { |
||||
((HttpServletResponse) response).sendError(sc); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class GetServlet extends HttpServlet { |
||||
|
||||
private final byte[] buf; |
||||
|
||||
private final MediaType contentType; |
||||
|
||||
public GetServlet(byte[] buf, MediaType contentType) { |
||||
this.buf = buf; |
||||
this.contentType = contentType; |
||||
} |
||||
|
||||
@Override |
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { |
||||
if (contentType != null) { |
||||
response.setContentType(contentType.toString()); |
||||
} |
||||
response.setContentLength(buf.length); |
||||
FileCopyUtils.copy(buf, response.getOutputStream()); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class PostServlet extends HttpServlet { |
||||
|
||||
private final String content; |
||||
|
||||
private final String location; |
||||
|
||||
private final byte[] buf; |
||||
|
||||
private final MediaType contentType; |
||||
|
||||
public PostServlet(String content, String location, byte[] buf, MediaType contentType) { |
||||
this.content = content; |
||||
this.location = location; |
||||
this.buf = buf; |
||||
this.contentType = contentType; |
||||
} |
||||
|
||||
@Override |
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { |
||||
assertTrue("Invalid request content-length", request.getContentLength() > 0); |
||||
assertNotNull("No content-type", request.getContentType()); |
||||
String body = FileCopyUtils.copyToString(request.getReader()); |
||||
assertEquals("Invalid request body", content, body); |
||||
response.setStatus(HttpServletResponse.SC_CREATED); |
||||
response.setHeader("Location", baseUrl + location); |
||||
response.setContentLength(buf.length); |
||||
response.setContentType(contentType.toString()); |
||||
FileCopyUtils.copy(buf, response.getOutputStream()); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class JsonPostServlet extends HttpServlet { |
||||
|
||||
private final String location; |
||||
|
||||
private final MediaType contentType; |
||||
|
||||
public JsonPostServlet(String location, MediaType contentType) { |
||||
this.location = location; |
||||
this.contentType = contentType; |
||||
} |
||||
|
||||
@Override |
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { |
||||
assertTrue("Invalid request content-length", request.getContentLength() > 0); |
||||
assertNotNull("No content-type", request.getContentType()); |
||||
String body = FileCopyUtils.copyToString(request.getReader()); |
||||
response.setStatus(HttpServletResponse.SC_CREATED); |
||||
response.setHeader("Location", baseUrl +location); |
||||
response.setContentType(contentType.toString()); |
||||
byte[] bytes = body.getBytes("utf-8"); |
||||
response.setContentLength(bytes.length);; |
||||
FileCopyUtils.copy(bytes, response.getOutputStream()); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class PutServlet extends HttpServlet { |
||||
|
||||
private final String s; |
||||
|
||||
public PutServlet(String s, byte[] buf, MediaType contentType) { |
||||
this.s = s; |
||||
} |
||||
|
||||
@Override |
||||
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException { |
||||
assertTrue("Invalid request content-length", request.getContentLength() > 0); |
||||
assertNotNull("No content-type", request.getContentType()); |
||||
String body = FileCopyUtils.copyToString(request.getReader()); |
||||
assertEquals("Invalid request body", s, body); |
||||
response.setStatus(HttpServletResponse.SC_ACCEPTED); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class UriServlet extends HttpServlet { |
||||
|
||||
@Override |
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { |
||||
resp.setContentType("text/plain"); |
||||
resp.setCharacterEncoding("utf-8"); |
||||
resp.getWriter().write(req.getRequestURI()); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class MultipartServlet extends HttpServlet { |
||||
|
||||
@Override |
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
||||
assertTrue(ServletFileUpload.isMultipartContent(req)); |
||||
FileItemFactory factory = new DiskFileItemFactory(); |
||||
ServletFileUpload upload = new ServletFileUpload(factory); |
||||
try { |
||||
List<FileItem> items = upload.parseRequest(req); |
||||
assertEquals(4, items.size()); |
||||
FileItem item = items.get(0); |
||||
assertTrue(item.isFormField()); |
||||
assertEquals("name 1", item.getFieldName()); |
||||
assertEquals("value 1", item.getString()); |
||||
|
||||
item = items.get(1); |
||||
assertTrue(item.isFormField()); |
||||
assertEquals("name 2", item.getFieldName()); |
||||
assertEquals("value 2+1", item.getString()); |
||||
|
||||
item = items.get(2); |
||||
assertTrue(item.isFormField()); |
||||
assertEquals("name 2", item.getFieldName()); |
||||
assertEquals("value 2+2", item.getString()); |
||||
|
||||
item = items.get(3); |
||||
assertFalse(item.isFormField()); |
||||
assertEquals("logo", item.getFieldName()); |
||||
assertEquals("logo.jpg", item.getName()); |
||||
assertEquals("image/jpeg", item.getContentType()); |
||||
} |
||||
catch (FileUploadException ex) { |
||||
throw new ServletException(ex); |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class FormServlet extends HttpServlet { |
||||
|
||||
@Override |
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { |
||||
assertEquals(MediaType.APPLICATION_FORM_URLENCODED_VALUE, req.getContentType()); |
||||
|
||||
Map<String, String[]> parameters = req.getParameterMap(); |
||||
assertEquals(2, parameters.size()); |
||||
|
||||
String[] values = parameters.get("name 1"); |
||||
assertEquals(1, values.length); |
||||
assertEquals("value 1", values[0]); |
||||
|
||||
values = parameters.get("name 2"); |
||||
assertEquals(2, values.length); |
||||
assertEquals("value 2+1", values[0]); |
||||
assertEquals("value 2+2", values[1]); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class DeleteServlet extends HttpServlet { |
||||
|
||||
@Override |
||||
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { |
||||
resp.setStatus(200); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class PatchServlet extends GenericServlet { |
||||
|
||||
private final String content; |
||||
|
||||
private final byte[] buf; |
||||
|
||||
private final MediaType contentType; |
||||
|
||||
public PatchServlet(String content, byte[] buf, MediaType contentType) { |
||||
this.content = content; |
||||
this.buf = buf; |
||||
this.contentType = contentType; |
||||
} |
||||
|
||||
@Override |
||||
public void service(ServletRequest req, ServletResponse res) |
||||
throws ServletException, IOException { |
||||
HttpServletRequest request = (HttpServletRequest) req; |
||||
HttpServletResponse response = (HttpServletResponse) res; |
||||
assertEquals("PATCH", request.getMethod()); |
||||
assertTrue("Invalid request content-length", request.getContentLength() > 0); |
||||
assertNotNull("No content-type", request.getContentType()); |
||||
String body = FileCopyUtils.copyToString(request.getReader()); |
||||
assertEquals("Invalid request body", content, body); |
||||
response.setStatus(HttpServletResponse.SC_CREATED); |
||||
response.setContentLength(buf.length); |
||||
response.setContentType(contentType.toString()); |
||||
FileCopyUtils.copy(buf, response.getOutputStream()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,254 @@
@@ -0,0 +1,254 @@
|
||||
package org.springframework.web.client; |
||||
|
||||
import java.io.EOFException; |
||||
import java.nio.charset.Charset; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Collections; |
||||
|
||||
import okhttp3.mockwebserver.Dispatcher; |
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import okio.Buffer; |
||||
import org.hamcrest.Matchers; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertNotNull; |
||||
import static org.junit.Assert.assertThat; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
/** |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class AbstractMockWebServerTestCase { |
||||
|
||||
protected static final String helloWorld = "H\u00e9llo W\u00f6rld"; |
||||
|
||||
private MockWebServer server; |
||||
|
||||
protected int port; |
||||
|
||||
protected String baseUrl; |
||||
|
||||
protected static final MediaType textContentType = |
||||
new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8")); |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
this.server = new MockWebServer(); |
||||
this.server.setDispatcher(new TestDispatcher()); |
||||
this.server.start(); |
||||
this.port = this.server.getPort(); |
||||
this.baseUrl = "http://localhost:" + this.port; |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
protected class TestDispatcher extends Dispatcher { |
||||
@Override |
||||
public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
||||
try { |
||||
byte[] helloWorldBytes = helloWorld.getBytes(StandardCharsets.UTF_8); |
||||
|
||||
if (request.getPath().equals("/get")) { |
||||
return getRequest(request, helloWorldBytes, textContentType.toString()); |
||||
} |
||||
else if (request.getPath().equals("/get/nothing")) { |
||||
return getRequest(request, new byte[0], textContentType.toString()); |
||||
} |
||||
else if (request.getPath().equals("/get/nocontenttype")) { |
||||
return getRequest(request, helloWorldBytes, null); |
||||
} |
||||
else if (request.getPath().equals("/post")) { |
||||
return postRequest(request, helloWorld, "/post/1", textContentType.toString(), helloWorldBytes); |
||||
} |
||||
else if (request.getPath().equals("/jsonpost")) { |
||||
return jsonPostRequest(request, "/jsonpost/1", "application/json; charset=utf-8"); |
||||
} |
||||
else if (request.getPath().equals("/status/nocontent")) { |
||||
return new MockResponse().setResponseCode(204); |
||||
} |
||||
else if (request.getPath().equals("/status/notmodified")) { |
||||
return new MockResponse().setResponseCode(304); |
||||
} |
||||
else if (request.getPath().equals("/status/notfound")) { |
||||
return new MockResponse().setResponseCode(404); |
||||
} |
||||
else if (request.getPath().equals("/status/server")) { |
||||
return new MockResponse().setResponseCode(500); |
||||
} |
||||
else if (request.getPath().contains("/uri/")) { |
||||
return new MockResponse().setBody(request.getPath()).setHeader("Content-Type", "text/plain"); |
||||
} |
||||
else if (request.getPath().equals("/multipart")) { |
||||
return multipartRequest(request); |
||||
} |
||||
else if (request.getPath().equals("/form")) { |
||||
return formRequest(request); |
||||
} |
||||
else if (request.getPath().equals("/delete")) { |
||||
return new MockResponse().setResponseCode(200); |
||||
} |
||||
else if (request.getPath().equals("/patch")) { |
||||
return patchRequest(request, helloWorld, textContentType.toString(), helloWorldBytes); |
||||
} |
||||
else if (request.getPath().equals("/put")) { |
||||
return putRequest(request, helloWorld); |
||||
} |
||||
return new MockResponse().setResponseCode(404); |
||||
} |
||||
catch (Throwable exc) { |
||||
return new MockResponse().setResponseCode(500).setBody(exc.toString()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
private MockResponse getRequest(RecordedRequest request, byte[] body, String contentType) { |
||||
if(request.getMethod().equals("OPTIONS")) { |
||||
return new MockResponse().setResponseCode(200).setHeader("Allow", "GET, OPTIONS, HEAD, TRACE"); |
||||
} |
||||
Buffer buf = new Buffer(); |
||||
buf.write(body); |
||||
MockResponse response = new MockResponse() |
||||
.setHeader("Content-Length", body.length) |
||||
.setBody(buf) |
||||
.setResponseCode(200); |
||||
if (contentType != null) { |
||||
response = response.setHeader("Content-Type", contentType); |
||||
} |
||||
return response; |
||||
} |
||||
|
||||
private MockResponse postRequest(RecordedRequest request, String expectedRequestContent, |
||||
String location, String contentType, byte[] responseBody) { |
||||
|
||||
assertTrue("Invalid request content-length", |
||||
Integer.parseInt(request.getHeader("Content-Length")) > 0); |
||||
String requestContentType = request.getHeader("Content-Type"); |
||||
assertNotNull("No content-type", requestContentType); |
||||
Charset charset = StandardCharsets.ISO_8859_1; |
||||
if(requestContentType.indexOf("charset=") > -1) { |
||||
String charsetName = requestContentType.split("charset=")[1]; |
||||
charset = Charset.forName(charsetName); |
||||
} |
||||
assertEquals("Invalid request body", expectedRequestContent, request.getBody().readString(charset)); |
||||
Buffer buf = new Buffer(); |
||||
buf.write(responseBody); |
||||
return new MockResponse() |
||||
.setHeader("Location", baseUrl + location) |
||||
.setHeader("Content-Type", contentType) |
||||
.setHeader("Content-Length", responseBody.length) |
||||
.setBody(buf) |
||||
.setResponseCode(201); |
||||
} |
||||
|
||||
private MockResponse jsonPostRequest(RecordedRequest request, String location, String contentType) { |
||||
|
||||
assertTrue("Invalid request content-length", |
||||
Integer.parseInt(request.getHeader("Content-Length")) > 0); |
||||
assertNotNull("No content-type", request.getHeader("Content-Type")); |
||||
return new MockResponse() |
||||
.setHeader("Location", baseUrl + location) |
||||
.setHeader("Content-Type", contentType) |
||||
.setHeader("Content-Length", request.getBody().size()) |
||||
.setBody(request.getBody()) |
||||
.setResponseCode(201); |
||||
} |
||||
|
||||
private MockResponse multipartRequest(RecordedRequest request) { |
||||
String contentType = request.getHeader("Content-Type"); |
||||
assertTrue(contentType.startsWith("multipart/form-data")); |
||||
String boundary = contentType.split("boundary=")[1]; |
||||
Buffer body = request.getBody(); |
||||
try { |
||||
assertPart(body, "form-data", boundary, "name 1", "text/plain", "value 1"); |
||||
assertPart(body, "form-data", boundary, "name 2", "text/plain", "value 2+1"); |
||||
assertPart(body, "form-data", boundary, "name 2", "text/plain", "value 2+2"); |
||||
assertFilePart(body, "form-data", boundary, "logo", "logo.jpg", "image/jpeg"); |
||||
} |
||||
catch (EOFException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
return new MockResponse().setResponseCode(200); |
||||
} |
||||
|
||||
private void assertPart(Buffer buffer, String disposition, String boundary, String name, |
||||
String contentType, String value) throws EOFException { |
||||
|
||||
assertTrue(buffer.readUtf8Line().contains("--" + boundary)); |
||||
String line = buffer.readUtf8Line(); |
||||
assertTrue(line.contains("Content-Disposition: "+ disposition)); |
||||
assertTrue(line.contains("name=\""+ name + "\"")); |
||||
assertTrue(buffer.readUtf8Line().startsWith("Content-Type: "+contentType)); |
||||
assertTrue(buffer.readUtf8Line().equals("Content-Length: " + value.length())); |
||||
assertTrue(buffer.readUtf8Line().equals("")); |
||||
assertTrue(buffer.readUtf8Line().equals(value)); |
||||
} |
||||
|
||||
private void assertFilePart(Buffer buffer, String disposition, String boundary, String name, |
||||
String filename, String contentType) throws EOFException { |
||||
|
||||
assertTrue(buffer.readUtf8Line().contains("--" + boundary)); |
||||
String line = buffer.readUtf8Line(); |
||||
assertTrue(line.contains("Content-Disposition: "+ disposition)); |
||||
assertTrue(line.contains("name=\""+ name + "\"")); |
||||
assertTrue(line.contains("filename=\""+ filename + "\"")); |
||||
assertTrue(buffer.readUtf8Line().startsWith("Content-Type: "+contentType)); |
||||
assertTrue(buffer.readUtf8Line().startsWith("Content-Length: ")); |
||||
assertTrue(buffer.readUtf8Line().equals("")); |
||||
assertNotNull(buffer.readUtf8Line()); |
||||
} |
||||
|
||||
private MockResponse formRequest(RecordedRequest request) { |
||||
assertEquals("application/x-www-form-urlencoded", request.getHeader("Content-Type")); |
||||
String body = request.getBody().readUtf8(); |
||||
assertThat(body, Matchers.containsString("name+1=value+1")); |
||||
assertThat(body, Matchers.containsString("name+2=value+2%2B1")); |
||||
assertThat(body, Matchers.containsString("name+2=value+2%2B2")); |
||||
return new MockResponse().setResponseCode(200); |
||||
} |
||||
|
||||
private MockResponse patchRequest(RecordedRequest request, String expectedRequestContent, |
||||
String contentType, byte[] responseBody) { |
||||
assertEquals("PATCH", request.getMethod()); |
||||
assertTrue("Invalid request content-length", |
||||
Integer.parseInt(request.getHeader("Content-Length")) > 0); |
||||
String requestContentType = request.getHeader("Content-Type"); |
||||
assertNotNull("No content-type", requestContentType); |
||||
Charset charset = StandardCharsets.ISO_8859_1; |
||||
if(requestContentType.indexOf("charset=") > -1) { |
||||
String charsetName = requestContentType.split("charset=")[1]; |
||||
charset = Charset.forName(charsetName); |
||||
} |
||||
assertEquals("Invalid request body", expectedRequestContent, request.getBody().readString(charset)); |
||||
Buffer buf = new Buffer(); |
||||
buf.write(responseBody); |
||||
return new MockResponse().setResponseCode(201) |
||||
.setHeader("Content-Length", responseBody.length) |
||||
.setHeader("Content-Type", contentType) |
||||
.setBody(buf); |
||||
} |
||||
|
||||
private MockResponse putRequest(RecordedRequest request, String expectedRequestContent) { |
||||
assertTrue("Invalid request content-length", |
||||
Integer.parseInt(request.getHeader("Content-Length")) > 0); |
||||
String requestContentType = request.getHeader("Content-Type"); |
||||
assertNotNull("No content-type", requestContentType); |
||||
Charset charset = StandardCharsets.ISO_8859_1; |
||||
if(requestContentType.indexOf("charset=") > -1) { |
||||
String charsetName = requestContentType.split("charset=")[1]; |
||||
charset = Charset.forName(charsetName); |
||||
} |
||||
assertEquals("Invalid request body", expectedRequestContent, request.getBody().readString(charset)); |
||||
return new MockResponse().setResponseCode(202); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue