Skip to content

Commit 232a148

Browse files
committed
improved HTTP HEAD performance
* Binary HTTP HEAD now implemented for requests with application/fhir+xml and application/fhir+json accept headers
1 parent 18a7a2b commit 232a148

File tree

3 files changed

+76
-13
lines changed

3 files changed

+76
-13
lines changed

dsf-fhir/dsf-fhir-server/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@
184184
<version>${crypto-utils.version}</version>
185185
</dependency>
186186

187+
<dependency>
188+
<groupId>commons-io</groupId>
189+
<artifactId>commons-io</artifactId>
190+
</dependency>
191+
187192
<dependency>
188193
<groupId>dev.dsf</groupId>
189194
<artifactId>dsf-common-jetty</artifactId>

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/WebserviceConfig.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ public class WebserviceConfig
158158
@Autowired
159159
private HistoryConfig historyConfig;
160160

161+
@Autowired
162+
private AdapterConfig adapterConfig;
163+
161164
@Bean
162165
public BrowserPolicyHeaderResponseFilter browserPolicyHeaderResponseFilter()
163166
{
@@ -202,7 +205,8 @@ private ActivityDefinitionServiceImpl activityDefinitionServiceImpl()
202205
@Bean
203206
public BinaryService binaryService()
204207
{
205-
return new BinaryServiceJaxrs(binaryServiceSecure(), helperConfig.parameterConverter());
208+
return new BinaryServiceJaxrs(binaryServiceSecure(), helperConfig.parameterConverter(),
209+
adapterConfig.fhirAdapter());
206210
}
207211

208212
private BinaryServiceSecure binaryServiceSecure()

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/jaxrs/BinaryServiceJaxrs.java

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77
import java.util.Arrays;
88
import java.util.List;
99
import java.util.Objects;
10+
import java.util.Optional;
1011

12+
import org.apache.commons.io.output.CountingOutputStream;
13+
import org.apache.commons.io.output.NullOutputStream;
14+
import org.hl7.fhir.r4.model.Base64BinaryType;
1115
import org.hl7.fhir.r4.model.Binary;
1216
import org.hl7.fhir.r4.model.Reference;
1317
import org.slf4j.Logger;
1418
import org.slf4j.LoggerFactory;
1519

1620
import ca.uhn.fhir.rest.api.Constants;
1721
import dev.dsf.fhir.adapter.DeferredBase64BinaryType;
22+
import dev.dsf.fhir.adapter.FhirAdapter;
1823
import dev.dsf.fhir.help.ParameterConverter;
1924
import dev.dsf.fhir.help.ResponseGenerator;
2025
import dev.dsf.fhir.model.StreamableBase64BinaryType;
@@ -72,12 +77,14 @@ public void write(OutputStream output) throws IOException, WebApplicationExcepti
7277
}
7378

7479
private final ParameterConverter parameterConverter;
80+
private final FhirAdapter fhirAdapter;
7581

76-
public BinaryServiceJaxrs(BinaryService delegate, ParameterConverter parameterConverter)
82+
public BinaryServiceJaxrs(BinaryService delegate, ParameterConverter parameterConverter, FhirAdapter fhirAdapter)
7783
{
7884
super(delegate);
7985

8086
this.parameterConverter = parameterConverter;
87+
this.fhirAdapter = fhirAdapter;
8188
}
8289

8390
@Override
@@ -86,6 +93,7 @@ public void afterPropertiesSet() throws Exception
8693
super.afterPropertiesSet();
8794

8895
Objects.requireNonNull(parameterConverter, "parameterConverter");
96+
Objects.requireNonNull(fhirAdapter, "fhirAdapter");
8997
}
9098

9199
@POST
@@ -231,7 +239,9 @@ public Response vreadHead(@PathParam("id") String id, @PathParam("version") long
231239

232240
private Response configureReadResponse(UriInfo uri, HttpHeaders headers, boolean head, Response read)
233241
{
234-
if (read.getEntity() instanceof Binary binary && !isValidFhirRequest(uri, headers))
242+
Optional<MediaType> fhirMediaType = getValidFhirMediaType(uri, headers);
243+
244+
if (read.getEntity() instanceof Binary binary && fhirMediaType.isEmpty())
235245
{
236246
if (mediaTypeMatches(headers, binary))
237247
{
@@ -276,25 +286,69 @@ private Response configureReadResponse(UriInfo uri, HttpHeaders headers, boolean
276286
else
277287
return Response.status(Status.NOT_ACCEPTABLE).build();
278288
}
289+
else if (read.getEntity() instanceof Binary binary && fhirMediaType.isPresent() && head)
290+
{
291+
ResponseBuilder b = Response.status(Status.OK);
292+
b.type(fhirMediaType.get());
293+
294+
if (binary.getMeta() != null && binary.getMeta().getLastUpdated() != null
295+
&& binary.getMeta().getVersionId() != null)
296+
{
297+
b.lastModified(binary.getMeta().getLastUpdated());
298+
b.tag(new EntityTag(binary.getMeta().getVersionId(), true));
299+
}
300+
301+
b.cacheControl(ResponseGenerator.PRIVATE_NO_CACHE_NO_TRANSFORM);
302+
303+
b.header(HttpHeaders.CONTENT_LENGTH, calculateFhirResponseSize(binary, fhirMediaType.get()));
304+
305+
return b.build();
306+
}
279307
else
280308
return read;
281309
}
282310

283-
private boolean isValidFhirRequest(UriInfo uri, HttpHeaders headers)
311+
private long calculateFhirResponseSize(Binary binary, MediaType mediaType)
312+
{
313+
long dataSize = (long) binary.getUserData(RangeRequest.USER_DATA_VALUE_DATA_SIZE);
314+
315+
// setting single byte to make sure data element is part of xml/json
316+
binary.setDataElement(new Base64BinaryType(new byte[1]));
317+
318+
try (CountingOutputStream out = new CountingOutputStream(NullOutputStream.INSTANCE))
319+
{
320+
fhirAdapter.writeTo(binary, Binary.class, null, null, mediaType, null, out);
321+
322+
// minus 4 to account for single byte in data element
323+
return out.getByteCount() - 4 + calculateBase64EncodedLength(dataSize);
324+
}
325+
catch (IOException e)
326+
{
327+
throw new RuntimeException(e);
328+
}
329+
}
330+
331+
private long calculateBase64EncodedLength(long dataSize)
332+
{
333+
return dataSize < 0 ? 0 : 4 * ((dataSize + 2) / 3);
334+
}
335+
336+
private Optional<MediaType> getValidFhirMediaType(UriInfo uri, HttpHeaders headers)
284337
{
285338
// _format parameter override present and valid
286339
if (uri.getQueryParameters().containsKey(Constants.PARAM_FORMAT))
287340
{
288-
parameterConverter.getMediaTypeThrowIfNotSupported(uri, headers);
289-
return true;
341+
MediaType mediaType = parameterConverter.getMediaTypeThrowIfNotSupported(uri, headers);
342+
return Optional.of(mediaType);
290343
}
291344
else
292345
{
293346
List<MediaType> types = headers.getAcceptableMediaTypes();
294347
MediaType accept = types == null ? null : types.get(0);
295348

296349
// accept header is FHIR mime-type
297-
return Arrays.stream(FHIR_MEDIA_TYPES).anyMatch(f -> f.equals(accept.toString()));
350+
return Arrays.stream(FHIR_MEDIA_TYPES).filter(f -> f.equals(accept.toString())).findFirst()
351+
.map(s -> accept);
298352
}
299353
}
300354

@@ -308,13 +362,13 @@ private boolean mediaTypeMatches(HttpHeaders headers, Binary binary)
308362
private ResponseBuilder toStreamResponse(Binary binary)
309363
{
310364
ResponseBuilder b = Response.status(Status.OK);
311-
b = b.type(binary.getContentType() != null ? binary.getContentType() : MediaType.APPLICATION_OCTET_STREAM);
365+
b.type(binary.getContentType() != null ? binary.getContentType() : MediaType.APPLICATION_OCTET_STREAM);
312366

313367
if (binary.getMeta() != null && binary.getMeta().getLastUpdated() != null
314368
&& binary.getMeta().getVersionId() != null)
315369
{
316-
b = b.lastModified(binary.getMeta().getLastUpdated());
317-
b = b.tag(new EntityTag(binary.getMeta().getVersionId(), true));
370+
b.lastModified(binary.getMeta().getLastUpdated());
371+
b.tag(new EntityTag(binary.getMeta().getVersionId(), true));
318372
}
319373

320374
if (binary.hasSecurityContext() && binary.getSecurityContext().hasReference())
@@ -323,9 +377,9 @@ private ResponseBuilder toStreamResponse(Binary binary)
323377
b.header(Constants.HEADER_X_SECURITY_CONTEXT, binary.getSecurityContext().getReference());
324378
}
325379

326-
b = b.cacheControl(ResponseGenerator.PRIVATE_NO_CACHE_NO_TRANSFORM);
327-
b = b.header(RangeRequest.ACCEPT_RANGES_HEADER, RangeRequest.ACCEPT_RANGES_HEADER_VALUE);
328-
b = b.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + toFileName(binary));
380+
b.cacheControl(ResponseGenerator.PRIVATE_NO_CACHE_NO_TRANSFORM);
381+
b.header(RangeRequest.ACCEPT_RANGES_HEADER, RangeRequest.ACCEPT_RANGES_HEADER_VALUE);
382+
b.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + toFileName(binary));
329383

330384
return b;
331385
}

0 commit comments

Comments
 (0)