Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a way to configure a CacheSpec for HttpFileService #2142

Merged
merged 20 commits into from Oct 10, 2019
16 changes: 16 additions & 0 deletions core/src/main/java/com/linecorp/armeria/common/Flags.java
Expand Up @@ -282,6 +282,10 @@ public final class Flags {
private static final Optional<String> HEADER_VALUE_CACHE_SPEC =
caffeineSpec("headerValueCache", DEFAULT_HEADER_VALUE_CACHE_SPEC);

private static final String DEFAULT_HTTP_FILE_SERVICE_CACHE_SPEC = "maximumSize=1024";
private static final Optional<String> HTTP_FILE_SERVICE_CACHE_SPEC =
caffeineSpec("httpFileServiceCache", DEFAULT_HTTP_FILE_SERVICE_CACHE_SPEC);
heowc marked this conversation as resolved.
Show resolved Hide resolved

private static final String DEFAULT_CACHED_HEADERS =
":authority,:scheme,:method,accept-encoding,content-type";
private static final List<String> CACHED_HEADERS =
Expand Down Expand Up @@ -772,6 +776,18 @@ public static Optional<String> headerValueCacheSpec() {
return HEADER_VALUE_CACHE_SPEC;
}

/**
* Returns the value of the {@code httpFileServiceCache} parameter. It would be used to create a Caffeine
* {@link Cache} instance using {@link CaffeineSpec} for caching file entries.
*
* <p>The default value of this flag is {@value DEFAULT_HTTP_FILE_SERVICE_CACHE_SPEC}. Specify the
* {@code -Dcom.linecorp.armeria.httpFileServiceCache=<spec>} JVM option to override the default value.
* Also, specify {@code -Dcom.linecorp.armeria.httpFileServiceCache=off} JVM option to disable it.
*/
public static Optional<String> httpFileServiceCacheSpec() {
return HTTP_FILE_SERVICE_CACHE_SPEC;
}

/**
* Returns the value of the {@code cachedHeaders} parameter which contains a comma-separated list of
* headers whose values are cached using {@code headerValueCache}.
Expand Down
Expand Up @@ -110,17 +110,16 @@ public static HttpFileService forVfs(HttpVfs vfs) {

HttpFileService(HttpFileServiceConfig config) {
this.config = requireNonNull(config, "config");
if (config.maxCacheEntries() != 0) {
if (config.entryCacheSpec().isPresent()) {
cache = newCache(config);
} else {
cache = null;
}
}

private static Cache<PathAndEncoding, AggregatedHttpFile> newCache(HttpFileServiceConfig config) {
final Caffeine<Object, Object> b = Caffeine.newBuilder();
b.maximumSize(config.maxCacheEntries())
.recordStats()
final Caffeine<Object, Object> b = Caffeine.from(config.entryCacheSpec().get());
b.recordStats()
.removalListener((RemovalListener<PathAndEncoding, AggregatedHttpFile>) (key, value, cause) -> {
if (value != null) {
final HttpData content = value.content();
Expand Down
Expand Up @@ -21,10 +21,12 @@
import java.nio.file.Path;
import java.time.Clock;
import java.util.Map.Entry;
import java.util.Optional;

import javax.annotation.Nullable;

import com.linecorp.armeria.common.CacheControl;
import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpHeaderNames;
import com.linecorp.armeria.common.HttpHeaders;
import com.linecorp.armeria.common.HttpHeadersBuilder;
Expand All @@ -36,7 +38,7 @@
*/
public final class HttpFileServiceBuilder {

private static final int DEFAULT_MAX_CACHE_ENTRIES = 1024;
private static final Optional<String> DEFAULT_ENTRY_CACHE_SPEC = Flags.httpFileServiceCacheSpec();
private static final int DEFAULT_MAX_CACHE_ENTRY_SIZE_BYTES = 65536;

/**
Expand Down Expand Up @@ -78,10 +80,12 @@ public static HttpFileServiceBuilder forVfs(HttpVfs vfs) {

private final HttpVfs vfs;
private Clock clock = Clock.systemUTC();
private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
private Optional<String> entryCacheSpec = DEFAULT_ENTRY_CACHE_SPEC;
private int maxCacheEntrySizeBytes = DEFAULT_MAX_CACHE_ENTRY_SIZE_BYTES;
private boolean serveCompressedFiles;
private boolean autoIndex;
private boolean enableMaxCacheEntries = true;
private boolean enableEntryCacheSpec = true;
heowc marked this conversation as resolved.
Show resolved Hide resolved
@Nullable
private HttpHeadersBuilder headers;

Expand All @@ -98,11 +102,38 @@ public HttpFileServiceBuilder clock(Clock clock) {
}

/**
* Sets the maximum allowed number of cached file entries. If not set, {@value #DEFAULT_MAX_CACHE_ENTRIES}
* Sets the maximum allowed number of cached file entries. If not set, {@code DEFAULT_ENTRY_CACHE_SPEC}
heowc marked this conversation as resolved.
Show resolved Hide resolved
* is used by default.
*
* @deprecated Use {@link #entryCacheSpec(String)}.
* <pre>{@code
* > HttpFileServiceBuilder.forFileSystem(...)
* > .entryCacheSpec("maximumSize=1024")
* > .build();
* }</pre>
*/
@Deprecated
heowc marked this conversation as resolved.
Show resolved Hide resolved
public HttpFileServiceBuilder maxCacheEntries(int maxCacheEntries) {
this.maxCacheEntries = HttpFileServiceConfig.validateMaxCacheEntries(maxCacheEntries);
if (!enableMaxCacheEntries) {
throw new IllegalStateException("already configured for the http file service: " + entryCacheSpec);
heowc marked this conversation as resolved.
Show resolved Hide resolved
}
entryCacheSpec = HttpFileServiceConfig.validateMaxCacheEntries(maxCacheEntries);
enableEntryCacheSpec = false;
return this;
}

/**
* Sets the cache spec for caching file entries. If not set, {@code DEFAULT_ENTRY_CACHE_SPEC}
heowc marked this conversation as resolved.
Show resolved Hide resolved
* is used by default.
*/
public HttpFileServiceBuilder entryCacheSpec(String entryCacheSpec) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just saw that code patch coverage is lower than the baseline. Could you add a test case for here :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added test case. Please tell me if it is not enough.

requireNonNull(entryCacheSpec, "entryCacheSpec");
if (!enableEntryCacheSpec) {
throw new IllegalStateException(
"already configured for the http file service: " + this.entryCacheSpec);
heowc marked this conversation as resolved.
Show resolved Hide resolved
}
this.entryCacheSpec = HttpFileServiceConfig.validateEntryCacheSpec(Optional.of(entryCacheSpec));
enableMaxCacheEntries = false;
return this;
}

Expand Down Expand Up @@ -221,13 +252,13 @@ public HttpFileServiceBuilder cacheControl(CharSequence cacheControl) {
*/
public HttpFileService build() {
return new HttpFileService(new HttpFileServiceConfig(
vfs, clock, maxCacheEntries, maxCacheEntrySizeBytes,
vfs, clock, entryCacheSpec, maxCacheEntrySizeBytes,
serveCompressedFiles, autoIndex, buildHeaders()));
}

@Override
public String toString() {
return HttpFileServiceConfig.toString(this, vfs, clock, maxCacheEntries, maxCacheEntrySizeBytes,
return HttpFileServiceConfig.toString(this, vfs, clock, entryCacheSpec, maxCacheEntrySizeBytes,
serveCompressedFiles, autoIndex, headers);
}
}
Expand Up @@ -20,9 +20,11 @@

import java.time.Clock;
import java.util.Map.Entry;
import java.util.Optional;

import javax.annotation.Nullable;

import com.github.benmanes.caffeine.cache.CaffeineSpec;
import com.google.common.base.MoreObjects;

import com.linecorp.armeria.common.HttpHeaders;
Expand All @@ -36,25 +38,43 @@ public final class HttpFileServiceConfig {

private final HttpVfs vfs;
private final Clock clock;
private final int maxCacheEntries;
private final Optional<String> entryCacheSpec;
private final int maxCacheEntrySizeBytes;
private final boolean serveCompressedFiles;
private final boolean autoIndex;
private final HttpHeaders headers;

HttpFileServiceConfig(HttpVfs vfs, Clock clock, int maxCacheEntries, int maxCacheEntrySizeBytes,
HttpFileServiceConfig(HttpVfs vfs, Clock clock, Optional<String> entryCacheSpec, int maxCacheEntrySizeBytes,
boolean serveCompressedFiles, boolean autoIndex, HttpHeaders headers) {
this.vfs = requireNonNull(vfs, "vfs");
this.clock = requireNonNull(clock, "clock");
this.maxCacheEntries = validateMaxCacheEntries(maxCacheEntries);
this.entryCacheSpec = validateEntryCacheSpec(entryCacheSpec);
this.maxCacheEntrySizeBytes = validateMaxCacheEntrySizeBytes(maxCacheEntrySizeBytes);
this.serveCompressedFiles = serveCompressedFiles;
this.autoIndex = autoIndex;
this.headers = requireNonNull(headers, "headers");
}

static int validateMaxCacheEntries(int maxCacheEntries) {
return validateNonNegativeParameter(maxCacheEntries, "maxCacheEntries");
static Optional<String> validateEntryCacheSpec(Optional<String> entryCacheSpec) {
if (!entryCacheSpec.isPresent() || "off".equals(entryCacheSpec.get())) {
return Optional.empty();
}
try {
CaffeineSpec.parse(entryCacheSpec.get());
} catch (Exception e) {
throw new IllegalArgumentException("invalid cache spec: " + entryCacheSpec, e);
}
return entryCacheSpec;
}

@Deprecated
heowc marked this conversation as resolved.
Show resolved Hide resolved
static Optional<String> validateMaxCacheEntries(int maxCacheEntries) {
heowc marked this conversation as resolved.
Show resolved Hide resolved
validateNonNegativeParameter(maxCacheEntries, "maxCacheEntries");
if (maxCacheEntries == 0) {
return Optional.empty();
}
final String entryCacheSpec = String.format("maximumSize=%d", maxCacheEntries);
return validateEntryCacheSpec(Optional.of(entryCacheSpec));
}

static int validateMaxCacheEntrySizeBytes(int maxCacheEntrySizeBytes) {
Expand Down Expand Up @@ -83,10 +103,10 @@ public Clock clock() {
}

/**
* Returns the maximum allowed number of cached file entries.
* Returns the cache spec for file entry cache.
heowc marked this conversation as resolved.
Show resolved Hide resolved
*/
public int maxCacheEntries() {
return maxCacheEntries;
public Optional<String> entryCacheSpec() {
return entryCacheSpec;
}

/**
Expand Down Expand Up @@ -121,19 +141,19 @@ public HttpHeaders headers() {

@Override
public String toString() {
return toString(this, vfs(), clock(), maxCacheEntries(), maxCacheEntrySizeBytes(),
return toString(this, vfs(), clock(), entryCacheSpec(), maxCacheEntrySizeBytes(),
serveCompressedFiles(), autoIndex(), headers());
}

static String toString(Object holder, HttpVfs vfs, Clock clock,
int maxCacheEntries, int maxCacheEntrySizeBytes,
Optional<String> entryCacheSpec, int maxCacheEntrySizeBytes,
boolean serveCompressedFiles, boolean autoIndex,
@Nullable Iterable<Entry<AsciiString, String>> headers) {

return MoreObjects.toStringHelper(holder).omitNullValues()
.add("vfs", vfs)
.add("clock", clock)
.add("maxCacheEntries", maxCacheEntries)
.add("entryCacheSpec", entryCacheSpec)
.add("maxCacheEntrySizeBytes", maxCacheEntrySizeBytes)
.add("serveCompressedFiles", serveCompressedFiles)
.add("autoIndex", autoIndex)
Expand Down
Expand Up @@ -84,7 +84,7 @@ protected void configure(ServerBuilder sb) {
sb.serviceUnder(
"/uncached/fs/",
HttpFileServiceBuilder.forFileSystem(tmpDir)
.maxCacheEntries(0)
.entryCacheSpec("off")
heowc marked this conversation as resolved.
Show resolved Hide resolved
.autoIndex(true)
.build());

Expand All @@ -97,7 +97,7 @@ protected void configure(ServerBuilder sb) {
"/uncached/compressed/",
HttpFileServiceBuilder.forClassPath(baseResourceDir + "foo")
.serveCompressedFiles(true)
.maxCacheEntries(0)
.entryCacheSpec("off")
.build());

sb.serviceUnder(
Expand All @@ -106,7 +106,7 @@ protected void configure(ServerBuilder sb) {
sb.serviceUnder(
"/uncached/classes/",
HttpFileServiceBuilder.forClassPath("/")
.maxCacheEntries(0)
.entryCacheSpec("off")
.build());

sb.serviceUnder(
Expand All @@ -116,10 +116,10 @@ protected void configure(ServerBuilder sb) {
sb.serviceUnder(
"/uncached/",
HttpFileServiceBuilder.forClassPath(baseResourceDir + "foo")
.maxCacheEntries(0)
.entryCacheSpec("off")
.build()
.orElse(HttpFileServiceBuilder.forClassPath(baseResourceDir + "bar")
.maxCacheEntries(0)
.entryCacheSpec("off")
.build()));

sb.decorator(LoggingService.newDecorator());
Expand Down
6 changes: 4 additions & 2 deletions site/src/sphinx/server-http-file.rst
Expand Up @@ -84,13 +84,15 @@ By default, :api:`HttpFileService` caches up to 1024 files whose length is less
HttpFileServiceBuilder.forFileSystem("/var/lib/www/images");

// Cache up to 4096 files.
fsb.maxCacheEntries(4096);
fsb.entryCacheSpec("maximumSize=4096");
// Cache files whose length is less than or equal to 1 MiB.
fsb.maxCacheEntrySizeBytes(1048576);

HttpFileService fs = fsb.build();

The cache can also be disabled by specifying ``0`` for ``maxCacheEntries()``.
The cache can also be disabled by specifying ``off`` for ``entryCacheSpec()``.
Or, you can override the default value of ``maximumSize=1024`` using the JVM system property
``-Dcom.linecorp.armeria.httpFileServiceCache=<spec>``.

Serving pre-compressed files
----------------------------
Expand Down