Improve resources caching

Update controllers to add an ETag information so that meta-data is cached
on the client.

Also enables the compression for json, css and html resources.

Closes gh-165
This commit is contained in:
Stephane Nicoll 2015-12-11 11:15:07 +01:00
parent 89330da312
commit 5fb8c84e5e
4 changed files with 50 additions and 7 deletions

View File

@ -6,6 +6,12 @@ info:
spring-boot:
version: 1.3.0.RELEASE
server:
compression:
enabled: true
mime-types: application/json,text/css,text/html
min-response-size: 2048
initializr:
env:
boms:

View File

@ -16,6 +16,9 @@
package io.spring.initializr.web
import java.nio.charset.StandardCharsets
import java.util.concurrent.TimeUnit
import groovy.util.logging.Slf4j
import io.spring.initializr.generator.CommandLineHelpGenerator
import io.spring.initializr.mapper.InitializrMetadataJsonMapper
@ -27,11 +30,13 @@ import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.InitializrMetadata
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.CacheControl
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.util.DigestUtils
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
@ -86,16 +91,20 @@ class MainController extends AbstractInitializrController {
def builder = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN)
if (userAgent) {
if (userAgent.startsWith(WebConfig.CURL_USER_AGENT_PREFIX)) {
return builder.body(commandLineHelpGenerator.generateCurlCapabilities(metadata, appUrl))
def content = commandLineHelpGenerator.generateCurlCapabilities(metadata, appUrl)
return builder.eTag(createUniqueId(content)).body(content)
}
if (userAgent.startsWith(WebConfig.HTTPIE_USER_AGENT_PREFIX)) {
return builder.body(commandLineHelpGenerator.generateHttpieCapabilities(metadata, appUrl))
def content = commandLineHelpGenerator.generateHttpieCapabilities(metadata, appUrl)
return builder.eTag(createUniqueId(content)).body(content)
}
if (userAgent.startsWith(WebConfig.SPRING_BOOT_CLI_AGENT_PREFIX)) {
return builder.body(commandLineHelpGenerator.generateSpringBootCliCapabilities(metadata, appUrl))
def content = commandLineHelpGenerator.generateSpringBootCliCapabilities(metadata, appUrl)
return builder.eTag(createUniqueId(content)).body(content)
}
}
builder.body(commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl))
def content = commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl)
builder.eTag(createUniqueId(content)).body(content)
}
@RequestMapping(value = "/", produces = ["application/hal+json"])
@ -120,7 +129,8 @@ class MainController extends AbstractInitializrController {
private ResponseEntity<String> serviceCapabilitiesFor(InitializrMetadataVersion version, MediaType contentType) {
String appUrl = generateAppUrl()
def content = getJsonMapper(version).write(metadataProvider.get(), appUrl)
return ResponseEntity.ok().contentType(contentType).body(content)
return ResponseEntity.ok().contentType(contentType).eTag(createUniqueId(content))
.cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content)
}
private static InitializrMetadataJsonMapper getJsonMapper(InitializrMetadataVersion version) {
@ -220,4 +230,10 @@ class MainController extends AbstractInitializrController {
result
}
private String createUniqueId(String content) {
StringBuilder builder = new StringBuilder()
DigestUtils.appendMd5DigestAsHex(content.getBytes(StandardCharsets.UTF_8), builder)
builder.toString()
}
}

View File

@ -16,6 +16,8 @@
package io.spring.initializr.web
import java.nio.charset.StandardCharsets
import groovy.json.JsonBuilder
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadataProvider
@ -23,6 +25,9 @@ import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionRange
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.util.DigestUtils
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@ -40,7 +45,7 @@ class UiController {
protected InitializrMetadataProvider metadataProvider
@RequestMapping(value = "/ui/dependencies", produces = ["application/json"])
String dependencies(@RequestParam(required = false) String version) {
ResponseEntity<String> dependencies(@RequestParam(required = false) String version) {
def dependencyGroups = metadataProvider.get().dependencies.content
def content = []
Version v = version ? Version.parse(version) : null
@ -55,7 +60,9 @@ class UiController {
}
}
}
writeDependencies(content)
def json = writeDependencies(content)
ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).
eTag(createUniqueId(json)).body(json)
}
private static String writeDependencies(List<DependencyItem> items) {
@ -96,4 +103,10 @@ class UiController {
}
}
private String createUniqueId(String content) {
StringBuilder builder = new StringBuilder()
DigestUtils.appendMd5DigestAsHex(content.getBytes(StandardCharsets.UTF_8), builder)
builder.toString()
}
}

View File

@ -24,6 +24,7 @@ import org.junit.Ignore
import org.junit.Test
import org.skyscreamer.jsonassert.JSONCompareMode
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
@ -32,6 +33,7 @@ import org.springframework.web.client.HttpClientErrorException
import static org.hamcrest.CoreMatchers.allOf
import static org.hamcrest.CoreMatchers.containsString
import static org.hamcrest.CoreMatchers.nullValue
import static org.hamcrest.core.IsNot.not
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
@ -169,6 +171,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
@Test
void metadataWithCurrentAcceptHeader() {
ResponseEntity<String> response = invokeHome(null, 'application/vnd.initializr.v2.1+json')
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
validateCurrentMetadata(new JSONObject(response.body))
}
@ -184,6 +187,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
@Test
void metadataWithHalAcceptHeader() {
ResponseEntity<String> response = invokeHome(null, 'application/hal+json')
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
validateContentType(response, MainController.HAL_JSON_CONTENT_TYPE)
validateCurrentMetadata(new JSONObject(response.body))
}
@ -283,6 +287,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
private void validateCurlHelpContent(ResponseEntity<String> response) {
validateContentType(response, MediaType.TEXT_PLAIN)
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
assertThat(response.body, allOf(
containsString("Spring Initializr"),
containsString('Examples:'),
@ -291,6 +296,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
private void validateHttpIeHelpContent(ResponseEntity<String> response) {
validateContentType(response, MediaType.TEXT_PLAIN)
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
assertThat(response.body, allOf(
containsString("Spring Initializr"),
containsString('Examples:'),
@ -300,6 +306,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
private void validateGenericHelpContent(ResponseEntity<String> response) {
validateContentType(response, MediaType.TEXT_PLAIN)
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
assertThat(response.body, allOf(
containsString("Spring Initializr"),
not(containsString('Examples:')),
@ -308,6 +315,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
private void validateSpringBootHelpContent(ResponseEntity<String> response) {
validateContentType(response, MediaType.TEXT_PLAIN)
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
assertThat(response.body, allOf(
containsString("Service capabilities"),
containsString("Supported dependencies"),