enable client side caching for plot requests

Doesn't work perfectly yet, because the height/width sometimes changes
by one or two pixels.
This commit is contained in:
2018-04-29 19:16:13 +02:00
parent 024c14435c
commit 2903b5a828
5 changed files with 73 additions and 11 deletions

View File

@@ -1,6 +1,7 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
@@ -36,7 +37,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -49,9 +52,13 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@Controller @Controller
@EnableAutoConfiguration @EnableAutoConfiguration
public class PdbController implements HardcodedValues { public class PdbController implements HardcodedValues, PropertyKeys {
private static final Logger LOGGER = LoggerFactory.getLogger(PdbController.class); private static final Logger LOGGER = LoggerFactory.getLogger(PdbController.class);
@@ -60,9 +67,12 @@ public class PdbController implements HardcodedValues {
private final ReentrantLock plotterLock = new ReentrantLock(); private final ReentrantLock plotterLock = new ReentrantLock();
@Value("${mode.production:true}") @Value("${" + PRODUCTION_MODE + ":true}")
private boolean modeProduction; private boolean modeProduction;
@Value("${" + CACHE_IMAGES_DURATION_SECONDS + ":" + CACHE_IMAGES_DURATION_SECONDS_DEFAULT + "}")
private int cacheDurationInSeconds;
public PdbController(final PerformanceDb db, final Plotter plotter) { public PdbController(final PerformanceDb db, final Plotter plotter) {
this.db = db; this.db = db;
this.plotter = plotter; this.plotter = plotter;
@@ -79,13 +89,29 @@ public class PdbController implements HardcodedValues {
return new ModelAndView(view, model); return new ModelAndView(view, model);
} }
@RequestMapping(path = "/plots", //
method = RequestMethod.GET, //
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
)
@ResponseBody
ResponseEntity<PlotResponse> createPlotGet(@RequestParam(name = "request") final String request)
throws InternalPlottingException, InterruptedException, JsonParseException, JsonMappingException,
IOException {
final ObjectMapper objectMapper = new ObjectMapper();
final PlotRequest plotRequest = objectMapper.readValue(request, PlotRequest.class);
return createPlot(plotRequest);
}
@RequestMapping(path = "/plots", // @RequestMapping(path = "/plots", //
method = RequestMethod.POST, // method = RequestMethod.POST, //
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, // consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE // produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
) )
@ResponseBody @ResponseBody
PlotResponse createPlot(@RequestBody final PlotRequest request) ResponseEntity<PlotResponse> createPlot(@RequestBody final PlotRequest request)
throws InternalPlottingException, InterruptedException { throws InternalPlottingException, InterruptedException {
final PlotSettings plotSettings = PlotSettingsTransformer.toSettings(request); final PlotSettings plotSettings = PlotSettingsTransformer.toSettings(request);
@@ -107,7 +133,11 @@ public class PdbController implements HardcodedValues {
: imageUrl; : imageUrl;
final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries()); final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries());
return new PlotResponse(stats, imageUrl, thumbnailUrl); final PlotResponse plotResponse = new PlotResponse(stats, imageUrl, thumbnailUrl);
final CacheControl cacheControl = CacheControl.maxAge(cacheDurationInSeconds, TimeUnit.SECONDS);
return ResponseEntity.ok().cacheControl(cacheControl).body(plotResponse);
} catch (final NoDataPointsException e) { } catch (final NoDataPointsException e) {
throw new NotFoundException(e); throw new NotFoundException(e);
} finally { } finally {

View File

@@ -11,4 +11,20 @@ public interface PropertyKeys {
* Path for temporary files * Path for temporary files
*/ */
String TMP_DIR = "path.tmp"; String TMP_DIR = "path.tmp";
/**
* The number of seconds generated images shall be cached.
*/
String CACHE_IMAGES_DURATION_SECONDS = "cache.images.duration.seconds";
/**
* Default value for {@link PropertyKeys#CACHE_IMAGES_DURATION_SECONDS}
*/
String CACHE_IMAGES_DURATION_SECONDS_DEFAULT = "3600";
/**
* Indicates whether or not this instance is running in production. This
* property is used to switch Vue.js into production or development mode.
*/
String PRODUCTION_MODE = "mode.production";
} }

View File

@@ -1,19 +1,25 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter implements HardcodedValues, PropertyKeys { public class WebConfiguration implements WebMvcConfigurer, HardcodedValues, PropertyKeys {
private final String outputDir; private final String outputDir;
private final int cacheDurationInSeconds;
public WebConfiguration(@Value("${" + PATH_GENERATED_IMAGES + "}") final String outputDir) { public WebConfiguration(@Value("${" + PATH_GENERATED_IMAGES + "}") final String outputDir,
@Value("${" + CACHE_IMAGES_DURATION_SECONDS + ":" + CACHE_IMAGES_DURATION_SECONDS_DEFAULT
+ "}") final int cacheDurationInSeconds) {
this.outputDir = outputDir; this.outputDir = outputDir;
this.cacheDurationInSeconds = cacheDurationInSeconds;
} }
@Override @Override
@@ -22,6 +28,7 @@ public class WebConfiguration extends WebMvcConfigurerAdapter implements Hardcod
final String pathPattern = "/" + WEB_IMAGE_OUTPUT_PATH + "/**"; final String pathPattern = "/" + WEB_IMAGE_OUTPUT_PATH + "/**";
final String resourceLocation = "file:" + Paths.get(outputDir).toAbsolutePath() + "/"; final String resourceLocation = "file:" + Paths.get(outputDir).toAbsolutePath() + "/";
registry.addResourceHandler(pathPattern).addResourceLocations(resourceLocation); final CacheControl cacheControl = CacheControl.maxAge(cacheDurationInSeconds, TimeUnit.SECONDS);
registry.addResourceHandler(pathPattern).addResourceLocations(resourceLocation).setCacheControl(cacheControl);
} }
} }

View File

@@ -4,4 +4,6 @@ db.base=${base.dir}/db
path.tmp=${base.dir}/tmp path.tmp=${base.dir}/tmp
path.output=${base.dir}/out path.output=${base.dir}/out
cache.images.duration.seconds=3600
logging.config=classpath:log4j2.xml logging.config=classpath:log4j2.xml

View File

@@ -908,7 +908,10 @@ function sendPlotRequest(query){
}; };
data.searchBar.imagelink = ''; data.searchBar.imagelink = '';
postJson("plots", request, success, error); const requestParam = {
'request': JSON.stringify(request)
};
getJson("plots", requestParam, success, error)
} }
function updateImageLink(query) { function updateImageLink(query) {
@@ -1002,7 +1005,10 @@ function createDashboardItem(originalQuery, field, imageHeight, imageWidth)
data.dashboard.progress.value++; data.dashboard.progress.value++;
createDashboardItem(originalQuery, field, imageHeight, imageWidth); createDashboardItem(originalQuery, field, imageHeight, imageWidth);
}; };
postJson("plots", request, success, error); var requestParam = {
request: JSON.stringify(request)
};
getJson("plots", requestParam, success, error)
} }
} }
@@ -1045,7 +1051,8 @@ function getJson(url, requestData, successCallback, errorCallback) {
type: "GET", type: "GET",
url: url, url: url,
data: requestData, data: requestData,
contentType: 'application/json' contentType: 'application/json',
cache:true
}) })
.done(successCallback) .done(successCallback)
.fail(errorCallback); .fail(errorCallback);