Skip to content

Commit f2eeb55

Browse files
committed
Change Absolute File URLs To Forwarded URL
1 parent fd427f2 commit f2eeb55

File tree

6 files changed

+65
-38
lines changed

6 files changed

+65
-38
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@ For the latest build see: https://github.com/medizininformatik-initiative/torch/
7979
| TORCH_CONCEPT_TREE_FILE | ontology/mapping_tree.json | The file for the concept tree mapping. |
8080
| TORCH_DSE_MAPPING_TREE_FILE | ontology/dse_mapping_tree.json | The file for DSE concept tree mapping. |
8181
| TORCH_USE_CQL | true | Flag indicating if CQL should be used. |
82-
| TORCH_BASE_URL || The server name before the proxy from which torch is accessed |
83-
| TORCH_OUTPUT_FILE_SERVER_URL || The URL to access Result location TORCH_RESULTS_DIR served by a proxy/fileserver |
8482
| LOG_LEVEL<br/>_DE_MEDIZININFORMATIKINITIATIVE_TORCH | info | Log level for torch core functionality. |
8583
| LOG_LEVEL<br/>_CA_UHN_FHIR | error | Log level for HAPI FHIR library. |
8684
| SPRING_PROFILES_ACTIVE | active | The active Spring profile. |
@@ -217,6 +215,9 @@ This can be used to track the progress of your data extraction.
217215

218216
If a server is set up for the files e.g. NGINX, the files can be fetched by a Request on the URL set in
219217
TORCH_OUTPUT_FILE_SERVER_URL in [enviroment variables](#environment-variables).
218+
**Torch assumes that the files are served from the baseurl that is next to the fhir api**
219+
E.g.: if localhost:8080/test/fhir is the url from which torch gets called (before forwarding), then
220+
the file url would start with **http://localhost:8080/test/**
220221

221222
```sh
222223
curl -s "http://localhost:8080/da4a1c56-f5d9-468c-b57a-b8186ea4fea8/f33634bd-d51b-463c-a956-93409d96935f.ndjson"

src/main/java/de/medizininformatikinitiative/torch/config/AppConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions;
5959
import org.springframework.web.reactive.function.client.ExchangeStrategies;
6060
import org.springframework.web.reactive.function.client.WebClient;
61+
import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
6162
import reactor.netty.http.client.HttpClient;
6263
import reactor.netty.resources.ConnectionProvider;
6364

@@ -92,6 +93,11 @@ public AppConfig(TorchProperties torchProperties) {
9293
this.torchProperties = torchProperties;
9394
}
9495

96+
@Bean
97+
public ForwardedHeaderTransformer forwardedHeaderTransformer() {
98+
return new ForwardedHeaderTransformer();
99+
}
100+
95101

96102
@Bean
97103
public String searchParametersFile(@Value("${torch.search_parameters_file}") String searchParametersFile) {
@@ -370,7 +376,7 @@ public StructureDefinitionHandler cdsStructureDefinitionHandler(ResourceReader r
370376

371377
@Bean
372378
public ResultFileManager resultFileManager(FhirContext fhirContext) {
373-
return new ResultFileManager(torchProperties.results().dir(), torchProperties.results().persistence(), fhirContext, torchProperties.base().url(), torchProperties.output().file().server().url());
379+
return new ResultFileManager(torchProperties.results().dir(), torchProperties.results().persistence(), fhirContext);
374380
}
375381

376382
@Bean

src/main/java/de/medizininformatikinitiative/torch/config/TorchProperties.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
@ConfigurationProperties(prefix = "torch")
66
public record TorchProperties(
7-
Base base,
8-
Output output,
97
Profile profile,
108
Mapping mapping,
119
Fhir fhir,
@@ -19,24 +17,9 @@ public record TorchProperties(
1917
String dseMappingTreeFile,
2018
boolean useCql
2119
) {
22-
23-
public record Base(String url) {
24-
25-
}
26-
2720
public record Max(int connections) {
2821
}
2922

30-
public record Output(File file) {
31-
public record File(Server server) {
32-
public record Server(String url) {
33-
34-
}
35-
}
36-
37-
}
38-
39-
4023
public record Profile(String dir) {
4124
}
4225

src/main/java/de/medizininformatikinitiative/torch/rest/FhirController.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import org.springframework.web.reactive.function.server.RouterFunction;
2121
import org.springframework.web.reactive.function.server.ServerRequest;
2222
import org.springframework.web.reactive.function.server.ServerResponse;
23+
import org.springframework.web.util.UriComponentsBuilder;
2324
import reactor.core.publisher.Mono;
2425
import reactor.core.scheduler.Schedulers;
2526

2627
import java.io.IOException;
28+
import java.net.URI;
2729
import java.time.Instant;
2830
import java.time.format.DateTimeFormatter;
2931
import java.util.ArrayList;
@@ -47,6 +49,7 @@ public class FhirController {
4749
private static final Logger logger = LoggerFactory.getLogger(FhirController.class);
4850

4951
private static final MediaType MEDIA_TYPE_FHIR_JSON = MediaType.valueOf("application/fhir+json");
52+
public static final String FHIR_STATUS = "/fhir/__status/";
5053

5154
private final ObjectMapper objectMapper;
5255
private final FhirContext fhirContext;
@@ -82,7 +85,7 @@ private record DecodedContent(byte[] crtdl, List<String> patientIds) {
8285

8386
@Autowired
8487
public FhirController(ObjectMapper objectMapper, FhirContext fhirContext, ResultFileManager resultFileManager,
85-
CrtdlProcessingService crtdlProcessingService, CrtdlValidatorService validatorService) {
88+
CrtdlProcessingService crtdlProcessingService, CrtdlValidatorService validatorService) {
8689
this.objectMapper = objectMapper;
8790
this.fhirContext = fhirContext;
8891
this.resultFileManager = resultFileManager;
@@ -119,7 +122,7 @@ private static DecodedContent decodeCrtdlContent(Parameters parameters) {
119122
public RouterFunction<ServerResponse> queryRouter() {
120123
logger.info("Init FhirController Router");
121124
return route(POST("/fhir/$extract-data").and(accept(MEDIA_TYPE_FHIR_JSON)), this::handleExtractData)
122-
.andRoute(GET("/fhir/__status/{jobId}"), this::checkStatus).andRoute(GET("/fhir/__status/"), this::getGlobalStatus);
125+
.andRoute(GET("/fhir/__status/{jobId}"), this::checkStatus).andRoute(GET(FHIR_STATUS), this::getGlobalStatus);
123126
}
124127

125128
private record DecodedCRTDLContent(Crtdl crtdl, List<String> patientIds) {
@@ -183,7 +186,7 @@ public Mono<ServerResponse> handleExtractData(ServerRequest request) {
183186
.subscribeOn(Schedulers.boundedElastic()).subscribe(); // final fire-and-forget
184187

185188
return accepted()
186-
.header("Content-Location", request.uriBuilder().replacePath("/fhir/__status/" + jobId).build().toString())
189+
.header("Content-Location", request.uriBuilder().replacePath(FHIR_STATUS + jobId).build().toString())
187190
.build();
188191
})
189192
.onErrorResume(IllegalArgumentException.class, e -> {
@@ -228,7 +231,30 @@ private Crtdl parseCrtdlContent(byte[] content) throws IOException {
228231
return objectMapper.readValue(content, Crtdl.class);
229232
}
230233

234+
public String stripToBasePath(URI originalUri, String basePath) {
235+
236+
String fullPath = originalUri.getPath();
237+
238+
int index = fullPath.indexOf(basePath);
239+
if (index == -1) {
240+
return originalUri.toString(); // fallback: no change
241+
}
242+
243+
String newPath = fullPath.substring(0, index);
244+
245+
return UriComponentsBuilder
246+
.fromUri(originalUri)
247+
.replacePath(newPath)
248+
.replaceQuery(null)
249+
.fragment(null)
250+
.build()
251+
.toUriString();
252+
}
253+
254+
231255
public Mono<ServerResponse> checkStatus(ServerRequest request) {
256+
String truncatedUrl = stripToBasePath(request.uri(), FHIR_STATUS);
257+
logger.info("Base url: {}", truncatedUrl);
232258
var jobId = request.pathVariable("jobId");
233259
HttpStatus status = resultFileManager.getStatus(jobId);
234260

@@ -242,7 +268,7 @@ public Mono<ServerResponse> checkStatus(ServerRequest request) {
242268
case HttpStatus.OK -> {
243269
// Capture the full request URL and transaction time
244270
String transactionTime = DateTimeFormatter.ISO_INSTANT.format(Instant.now());
245-
return Mono.fromCallable(() -> resultFileManager.loadBundleFromFileSystem(jobId, transactionTime))
271+
return Mono.fromCallable(() -> resultFileManager.loadBundleFromFileSystem(truncatedUrl, jobId, transactionTime))
246272
.flatMap(bundleMap -> {
247273
if (bundleMap == null) {
248274
return ServerResponse.notFound().build();

src/main/java/de/medizininformatikinitiative/torch/util/ResultFileManager.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@
44
import ca.uhn.fhir.context.FhirContext;
55
import de.medizininformatikinitiative.torch.management.OperationOutcomeCreator;
66
import de.medizininformatikinitiative.torch.model.consent.PatientBatchWithConsent;
7-
import org.hl7.fhir.r4.model.Bundle;
87
import org.hl7.fhir.r4.model.OperationOutcome;
98
import org.slf4j.Logger;
109
import org.slf4j.LoggerFactory;
1110
import org.springframework.http.HttpStatus;
12-
import reactor.core.publisher.Flux;
1311
import reactor.core.publisher.Mono;
1412
import reactor.core.scheduler.Schedulers;
1513

1614
import java.io.BufferedWriter;
17-
import java.io.FileWriter;
1815
import java.io.IOException;
1916
import java.io.InputStream;
2017
import java.nio.charset.StandardCharsets;
@@ -23,7 +20,13 @@
2320
import java.nio.file.Paths;
2421
import java.nio.file.StandardOpenOption;
2522
import java.time.Duration;
26-
import java.util.*;
23+
import java.util.ArrayList;
24+
import java.util.HashMap;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Optional;
28+
import java.util.Set;
29+
import java.util.UUID;
2730
import java.util.concurrent.ConcurrentHashMap;
2831
import java.util.stream.Stream;
2932

@@ -38,18 +41,14 @@ public class ResultFileManager {
3841

3942
private final Path resultsDirPath;
4043
private final FhirContext fhirContext;
41-
private final String hostname;
42-
private final String fileServerName;
4344
public final ConcurrentHashMap<String, HttpStatus> jobStatusMap = new ConcurrentHashMap<>();
4445

45-
public ResultFileManager(String resultsDir, String duration, FhirContext fhirContext, String hostname, String fileServerName) {
46+
public ResultFileManager(String resultsDir, String duration, FhirContext fhirContext) {
4647
this.resultsDirPath = Paths.get(resultsDir).toAbsolutePath();
4748
this.fhirContext = fhirContext;
4849

4950

5051
Duration duration1 = Duration.parse(duration);
51-
this.hostname = hostname;
52-
this.fileServerName = fileServerName;
5352

5453

5554
logger.debug("Duration of persistence {}", duration1);
@@ -213,7 +212,15 @@ public String loadErrorFromFileSystem(String jobId) {
213212
}
214213
}
215214

216-
public Map<String, Object> loadBundleFromFileSystem(String jobId, String transactionTime) {
215+
/**
216+
* Generates the server response for a successful operation.
217+
*
218+
* @param url of the calling request.
219+
* @param jobId id to be handled
220+
* @param transactionTime time of the transaction
221+
* @return Map Containing the server response with file locations
222+
*/
223+
public Map<String, Object> loadBundleFromFileSystem(String url, String jobId, String transactionTime) {
217224
Map<String, Object> response = new HashMap<>();
218225
try {
219226
Path jobDir = getJobDirectory(jobId);
@@ -224,10 +231,9 @@ public Map<String, Object> loadBundleFromFileSystem(String jobId, String transac
224231

225232
Files.list(jobDir).forEach(file -> {
226233
String fileName = file.getFileName().toString();
227-
String url = fileServerName + "/" + jobId + "/" + fileName;
228234

229235
Map<String, String> fileEntry = new HashMap<>();
230-
fileEntry.put("url", url);
236+
fileEntry.put("url", url + "/" + jobId + "/" + fileName);
231237

232238
if (fileName.endsWith(".ndjson")) {
233239
fileEntry.put("type", "NDJSON Bundle");
@@ -244,7 +250,7 @@ public Map<String, Object> loadBundleFromFileSystem(String jobId, String transac
244250
logger.debug("OutputFiles size {}", outputFiles.size());
245251

246252
response.put("transactionTime", transactionTime);
247-
response.put("request", hostname + "/fhir/$extract-data");
253+
response.put("request", url + "/fhir/$extract-data");
248254
response.put("requiresAccessToken", false);
249255
response.put("output", outputFiles);
250256
response.put("deleted", deletedFiles);

src/test/java/de/medizininformatikinitiative/torch/config/TestConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
5353
import org.springframework.web.reactive.function.client.ExchangeStrategies;
5454
import org.springframework.web.reactive.function.client.WebClient;
55+
import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
5556
import reactor.netty.http.client.HttpClient;
5657
import reactor.netty.resources.ConnectionProvider;
5758

@@ -89,6 +90,10 @@ public TestConfig(TorchProperties torchProperties) {
8990
this.torchProperties = torchProperties;
9091
}
9192

93+
@Bean
94+
public ForwardedHeaderTransformer forwardedHeaderTransformer() {
95+
return new ForwardedHeaderTransformer();
96+
}
9297

9398
@Bean
9499
public CascadingDelete cascadingDelete() {
@@ -304,7 +309,7 @@ public StructureDefinitionHandler cdsStructureDefinitionHandler(ResourceReader r
304309

305310
@Bean
306311
public ResultFileManager resultFileManager(FhirContext fhirContext) {
307-
return new ResultFileManager(torchProperties.results().dir(), torchProperties.results().persistence(), fhirContext, torchProperties.base().url(), torchProperties.output().file().server().url());
312+
return new ResultFileManager(torchProperties.results().dir(), torchProperties.results().persistence(), fhirContext);
308313
}
309314

310315
@Bean

0 commit comments

Comments
 (0)