Multiple values for one HTTP header were reduced to the last value.

This commit is contained in:
Volker Schmidt 2018-09-06 19:33:34 +08:00 committed by Eeva Turkka
parent 92936d6889
commit 20f5df6657
5 changed files with 119 additions and 9 deletions

View File

@ -22,6 +22,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L% * #L%
*/ */
import java.io.*; import java.io.*;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -104,7 +105,7 @@ public class JaxRsResponse extends RestfulResponse<JaxRsRequest> {
private ResponseBuilder buildResponse(int statusCode) { private ResponseBuilder buildResponse(int statusCode) {
ResponseBuilder response = Response.status(statusCode); ResponseBuilder response = Response.status(statusCode);
for (Entry<String, String> header : getHeaders().entrySet()) { for (Entry<String, List<String>> header : getHeaders().entrySet()) {
response.header(header.getKey(), header.getValue()); response.header(header.getKey(), header.getValue());
} }
return response; return response;

View File

@ -21,9 +21,7 @@ package ca.uhn.fhir.rest.server;
*/ */
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.*;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
@ -35,7 +33,7 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
private IIdType myOperationResourceId; private IIdType myOperationResourceId;
private IPrimitiveType<Date> myOperationResourceLastUpdated; private IPrimitiveType<Date> myOperationResourceLastUpdated;
private ConcurrentHashMap<String, String> theHeaders = new ConcurrentHashMap<String, String>(); private Map<String, List<String>> theHeaders = new HashMap<>();
private T theRequestDetails; private T theRequestDetails;
public RestfulResponse(T requestDetails) { public RestfulResponse(T requestDetails) {
@ -44,14 +42,14 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
@Override @Override
public void addHeader(String headerKey, String headerValue) { public void addHeader(String headerKey, String headerValue) {
this.getHeaders().put(headerKey, headerValue); this.getHeaders().computeIfAbsent(headerKey, k -> new ArrayList<>()).add(headerValue);
} }
/** /**
* Get the http headers * Get the http headers
* @return the headers * @return the headers
*/ */
public ConcurrentHashMap<String, String> getHeaders() { public Map<String, List<String>> getHeaders() {
return theHeaders; return theHeaders;
} }

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.io.Writer; import java.io.Writer;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@ -75,8 +76,18 @@ public class ServletRestfulResponse extends RestfulResponse<ServletRequestDetail
private void addHeaders() { private void addHeaders() {
HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse(); HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
getRequestDetails().getServer().addHeadersToResponse(theHttpResponse); getRequestDetails().getServer().addHeadersToResponse(theHttpResponse);
for (Entry<String, String> header : getHeaders().entrySet()) { for (Entry<String, List<String>> header : getHeaders().entrySet()) {
theHttpResponse.setHeader(header.getKey(), header.getValue()); final String key = header.getKey();
boolean first = true;
for (String value : header.getValue()) {
// existing headers should be overridden
if (first) {
theHttpResponse.setHeader(key, value);
first = false;
} else {
theHttpResponse.addHeader(key, value);
}
}
} }
} }

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
/**
* Unit tests of {@link RestfulResponse}.
*/
public class RestfulResponseTest {
@Test
public void addMultipleHeaderValues() {
@SuppressWarnings("unchecked")
final RestfulResponse<?> restfulResponse =
mock(RestfulResponse.class, withSettings()
.useConstructor((RequestDetails) null).defaultAnswer(CALLS_REAL_METHODS));
restfulResponse.addHeader("Authorization", "Basic");
restfulResponse.addHeader("Authorization", "Bearer");
restfulResponse.addHeader("Cache-Control", "no-cache, no-store");
assertEquals(2, restfulResponse.getHeaders().size());
assertThat(restfulResponse.getHeaders().get("Authorization"), Matchers.contains("Basic", "Bearer"));
assertThat(restfulResponse.getHeaders().get("Cache-Control"), Matchers.contains("no-cache, no-store"));
}
}

View File

@ -0,0 +1,65 @@
package ca.uhn.fhir.rest.server.servlet;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* Unit tests of {@link ServletRestfulResponse}.
*/
public class ServletRestfulResponseTest {
@Mock
private RestfulServer server;
@Mock
private ServletOutputStream servletOutputStream;
@Mock
private HttpServletResponse servletResponse;
private ServletRequestDetails requestDetails;
private ServletRestfulResponse response;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Before
public void init() throws IOException {
Mockito.when(servletResponse.getOutputStream()).thenReturn(servletOutputStream);
requestDetails = new ServletRequestDetails();
requestDetails.setServer(server);
requestDetails.setServletResponse(servletResponse);
response = new ServletRestfulResponse(requestDetails);
}
@Test
public void addMultipleHeaderValues() throws IOException {
final ServletRestfulResponse response = new ServletRestfulResponse(requestDetails);
response.addHeader("Authorization", "Basic");
response.addHeader("Authorization", "Bearer");
response.addHeader("Cache-Control", "no-cache, no-store");
response.getResponseWriter(200, "Status", "text/plain", "UTF-8", false);
final InOrder orderVerifier = Mockito.inOrder(servletResponse);
orderVerifier.verify(servletResponse).setHeader(eq("Authorization"), eq("Basic"));
orderVerifier.verify(servletResponse).addHeader(eq("Authorization"), eq("Bearer"));
verify(servletResponse).setHeader(eq("Cache-Control"), eq("no-cache, no-store"));
}
}