diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java index aaf032a93e3..9541202b24f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java @@ -93,12 +93,13 @@ public abstract class BaseHttpClientInvocation { } } - theHttpRequest.addHeader("User-Agent", "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)"); + String versionString = VersionUtil.getVersion(); + theHttpRequest.addHeader("User-Agent", "HAPI-FHIR/" + versionString + " (FHIR Client)"); theHttpRequest.addHeader("Accept-Charset", "utf-8"); theHttpRequest.addHeader("Accept-Encoding", "gzip"); if (theEncoding == null) { - theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_ALL); + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON); } else if (theEncoding == EncodingEnum.JSON) { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON); } else if (theEncoding == EncodingEnum.XML) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 3604bb615f8..5b65a36c31c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.method; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /* * #%L * HAPI FHIR - Core Library @@ -213,9 +215,13 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca * whatever reason. */ ByteArrayEntity entity = new ByteArrayEntity(binary.getContent()); - entity.setContentType(binary.getContentType()); + HttpRequestBase retVal = createRequest(url, entity); addMatchHeaders(retVal, url); + super.addHeadersToRequest(retVal, null); + if (isNotBlank(binary.getContentType())) { + retVal.addHeader(Constants.HEADER_CONTENT_TYPE, binary.getContentType()); + } return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 2a854e62764..cfa1bc0ca69 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -54,7 +54,7 @@ public class Constants { public static final String FORMAT_XML = "xml"; public static final String HEADER_ACCEPT = "Accept"; public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - public static final String HEADER_ACCEPT_VALUE_ALL = CT_FHIR_XML + ";q=1.0, " + CT_FHIR_JSON + ";q=1.0"; + public static final String HEADER_ACCEPT_VALUE_XML_OR_JSON = CT_FHIR_XML + ";q=1.0, " + CT_FHIR_JSON + ";q=1.0"; public static final String HEADER_ALLOW = "Allow"; public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic "; diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java index bdaa8711e0b..fb583c22261 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java @@ -143,7 +143,7 @@ public class GenericClientDstu2Test { client.fetchConformance().ofType(Conformance.class).execute(); assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(idx).getHeaders("Accept").length); - assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL)); + assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON)); idx++; client.fetchConformance().ofType(Conformance.class).encodedJson().execute(); @@ -192,12 +192,12 @@ public class GenericClientDstu2Test { assertEquals("FAMILY", resp.getName().get(0).getFamily().get(0).getValue()); assertEquals("http://" + methodName + ".example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(0).getHeaders("Accept").length); - assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL)); + assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON)); assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_XML)); assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON)); assertEquals("http://" + methodName + ".example.com/fhir/Patient/123", capt.getAllValues().get(1).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(1).getHeaders("Accept").length); - assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL)); + assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON)); assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_XML)); assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON)); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java new file mode 100644 index 00000000000..ea5c53d1ac4 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java @@ -0,0 +1,140 @@ +package ca.uhn.fhir.rest.client; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.Arrays; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.hl7.fhir.dstu3.model.Binary; +import org.hl7.fhir.dstu3.model.Conformance; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.util.VersionUtil; + +public class GenericClientDstu3Test { + private static FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu3Test.class); + + private HttpClient myHttpClient; + + private HttpResponse myHttpResponse; + + private int myResponseCount = 0; + + @Before + public void before() { + myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + myResponseCount = 0; + } + + private byte[] extractBodyAsByteArray(ArgumentCaptor capt) throws IOException { + byte[] body = IOUtils.toByteArray(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent()); + return body; + } + + + @Test + public void testUserAgentForConformance() throws Exception { + IParser p = ourCtx.newXmlParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + client.fetchConformance().ofType(Conformance.class).execute(); + assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + } + + @Test + public void testUserAgentForBinary() throws Exception { + IParser p = ourCtx.newXmlParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Binary bin = new Binary(); + bin.setContentType("application/foo"); + bin.setContent(new byte[] {0, 1, 2, 3, 4}); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/foo", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] {0,1,2,3,4}, extractBodyAsByteArray(capt)); + + } + + private void validateUserAgent(ArgumentCaptor capt) { + assertEquals(1, capt.getAllValues().get(0).getHeaders("User-Agent").length); + assertEquals(expectedUserAgent(), capt.getAllValues().get(0).getHeaders("User-Agent")[0].getValue()); + } + + private String expectedUserAgent() { + return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)"; + } + + + @BeforeClass + public static void beforeClass() { + ourCtx = FhirContext.forDstu3(); + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 83f4d6eb438..2699b44b858 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -77,6 +77,13 @@ know if you need the ability to add this header automatically. Thanks to Lars Kristian Roland for pointing this out. + + In the client, the create/update operations on a Binary resource + (which use the raw binary's content type as opposed to the FHIR + content type) were not including any request headers (Content-Type, + User-Agent, etc.) Thanks to Peter Van Houte of Agfa Healthcare for + reporting! +