Prevent caching issues with browsers

The main metadata endpoint is available at the root "/" path, for JSON
compatible media types. This endpoint is often requested by CLI and
IDEs. Initializr is setting HTTP response headers to tell clients to
cache the response body.

With this current situation, several HTTP caching issues can happen.

1. Since many formats are available at the same path, proxies can cache
the response body and redistribute it to many clients, even if they
don't request the same media type. To fix that, we need to add a
`Vary: Accept` response header; with that, proxies will cache responses
but take into account that different Accept request headers might yield
different responses.

2. Browsers have very specific caching implementations, and exposing
that metadata endpoint on "/" and at the same time an HTML page will
create issues related to HTTP caching. Navigation and refreshes might
result with strange problems. To fix that, we need to reinstate the
`/metadata/client` endpoint as a first class citizen (and not just a
redirect). This way, Web UIs can freely use that path to request the
metadata, without risking caching issues.

See gh-914
This commit is contained in:
Brian Clozel 2019-05-31 21:34:05 +02:00 committed by Stephane Nicoll
parent ce150380c2
commit ba73613050
2 changed files with 7 additions and 10 deletions

View File

@ -113,11 +113,6 @@ public class MainController extends AbstractInitializrController {
return this.metadataProvider.get();
}
@RequestMapping("/metadata/client")
public String client() {
return "redirect:/";
}
@RequestMapping(path = "/", produces = "text/plain")
public ResponseEntity<String> serviceCapabilitiesText(
@RequestHeader(value = HttpHeaders.USER_AGENT,
@ -152,19 +147,20 @@ public class MainController extends AbstractInitializrController {
return builder.eTag(createUniqueId(content)).body(content);
}
@RequestMapping(path = "/", produces = "application/hal+json")
@RequestMapping(path = { "/", "/metadata/client" }, produces = "application/hal+json")
public ResponseEntity<String> serviceCapabilitiesHal() {
return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1,
HAL_JSON_CONTENT_TYPE);
}
@RequestMapping(path = "/",
@RequestMapping(path = { "/", "/metadata/client" },
produces = { "application/vnd.initializr.v2.1+json", "application/json" })
public ResponseEntity<String> serviceCapabilitiesV21() {
return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1);
}
@RequestMapping(path = "/", produces = "application/vnd.initializr.v2+json")
@RequestMapping(path = { "/", "/metadata/client" },
produces = "application/vnd.initializr.v2+json")
public ResponseEntity<String> serviceCapabilitiesV2() {
return serviceCapabilitiesFor(InitializrMetadataVersion.V2);
}
@ -180,7 +176,8 @@ public class MainController extends AbstractInitializrController {
String content = getJsonMapper(version).write(this.metadataProvider.get(),
appUrl);
return ResponseEntity.ok().contentType(contentType).eTag(createUniqueId(content))
.cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content);
.varyBy("Accept").cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS))
.body(content);
}
private static InitializrMetadataJsonMapper getJsonMapper(

View File

@ -88,7 +88,7 @@ class MainControllerServiceMetadataIntegrationTests
}
@Test
void metadataClientRedirect() {
void metadataClientEndpoint() {
ResponseEntity<String> response = execute("/metadata/client", String.class, null,
"application/json");
validateCurrentMetadata(response);