Serving static files
Table of contents
- Auto-generating directory listings
- Specifying additional response headers
- Adjusting static file cache
- Serving pre-compressed files
- Serving an individual file
- Configuring HttpFile
- Caching HttpFile
- Aggregating HttpFile
- Building AggregatedHttpFile from HttpData
Use FileService
to serve static files under a certain directory. FileService
supports
GET
and HEAD
HTTP methods and will auto-fill Date
, Last-Modified
, ETag
and auto-detected
Content-Type
headers for you. It is also capable of sending a 304 Not Modified
response based on
If-None-Match
and If-Modified-Since
header values.
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.file.FileService;
ServerBuilder sb = Server.builder();
sb.serviceUnder("/images/",
FileService.of(Paths.get("/var/lib/www/images"));
// You can also serve the resources in the class path.
sb.serviceUnder("/resources",
FileService.of(ClassLoader.getSystemClassLoader(),
"/com/example/resources"));
Auto-generating directory listings
You can configure FileService
to generate the directory listing of the directories without
an index.html
file using the autoIndex(boolean)
method in FileServiceBuilder
.
import com.linecorp.armeria.server.file.FileServiceBuilder;
FileServiceBuilder fsb =
FileService.builder(Paths.get("/var/lib/www/images"));
// Enable auto-index.
fsb.autoIndex(true);
FileService fs = fsb.build();
Specifying additional response headers
You can specify additional response headers such as cache-control
and other custom headers.
import com.linecorp.armeria.common.ServerCacheControl;
import com.linecorp.armeria.common.ServerCacheControlBuilder;
FileServiceBuilder fsb =
FileService.builder(Paths.get("/var/lib/www/images"));
// Specify cache control directives.
ServerCacheControl cc = ServerCacheControl.builder()
.maxAgeSeconds(86400)
.cachePublic()
.build();
fsb.cacheControl(cc /* "max-age=86400, public" */);
// Specify a custom header.
fsb.setHeader("foo", "bar");
FileService fs = fsb.build();
Adjusting static file cache
By default, FileService
caches up to 1024 files whose length is less than or equal to
65,536 bytes. You can customize this behavior using FileServiceBuilder
.
FileServiceBuilder fsb =
FileService.builder(Paths.get("/var/lib/www/images"));
// Cache up to 4096 files.
fsb.entryCacheSpec("maximumSize=4096");
// Cache files whose length is less than or equal to 1 MiB.
fsb.maxCacheEntrySizeBytes(1048576);
FileService fs = fsb.build();
The cache can be disabled by specifying 0
for maxCacheEntries()
.
You can also specify a custom cache specification using entryCacheSpec()
,
as defined in Caffeine documentation.
Or, you can override the default cache specification of maximumSize=1024
using
the JVM property -Dcom.linecorp.armeria.fileServiceCache=<spec>
.
Serving pre-compressed files
FileService
can be configured to serve a pre-compressed file based on the value of the
Accept-Encoding
header. For example, if a client sent the following HTTP request:
GET /index.html HTTP/1.1
Host: example.com
Accept-Encoding: gzip, identity
FileService
could look for /index.html.gz
first and send the following response with the
Content-Encoding: gzip
header if it exists:
HTTP/1.1 200 OK
Host: example.com
Content-Encoding: gzip
Content-Type: text/html
...
<compressed content>
If /index.html.gz
does not exist but /index.html
does, it would fall back on serving the uncompressed
content:
HTTP/1.1 200 OK
Host: example.com
Content-Type: text/html
...
<uncompressed content>
This behavior is enabled by calling FileServiceBuilder.serveCompressedFiles()
with true
.
.gz
(gzip) and .br
(Brotli) files are supported currently.
FileServiceBuilder fsb =
FileService.builder(ClassLoader.getSystemClassLoader(),
"/com/example/resources");
// Enable serving pre-compressed files.
fsb.serveCompressedFiles(true);
FileService fs = fsb.build();
If you set FileServiceBuilder.autoDecompress()
to true
, the FileService
automatically
decompresses the pre-compressed file when a client does not send an Accept-Encoding
header.
FileServiceBuilder fsb = ...
fsb.serveCompressedFiles(true);
fsb.autoDecompress(true);
FileService fs = fsb.build();
Serving an individual file
You can also serve an individual file using HttpFile
. Like FileService
does, HttpFile
supports GET
and HEAD
HTTP methods and will auto-fill Date
, Last-Modified
, ETag
and
auto-detected Content-Type
headers for you. It is also capable of sending a 304 Not Modified
response
based on If-None-Match
and If-Modified-Since
header values.
import com.linecorp.armeria.server.file.HttpFile;
HttpFile favicon = HttpFile.of(Paths.get("/var/lib/www/favicon.ico"));
ServerBuilder sb = Server.builder();
// Serve the favicon.ico file by converting an HttpFile into a service.
sb.service("/favicon.ico", favicon.asService());
For instance, it is possible to serve the same file (e.g. index.html
) for all requests under a certain
path, which is useful when serving a frontend application with client-side routing.
HttpFile index = HttpFile.of(Paths.get("/var/lib/www/index.html"));
ServerBuilder sb = Server.builder();
// Register the file service for assets.
sb.serviceUnder("/node_modules",
FileService.of(Paths.get("/var/lib/www/node_modules")));
sb.serviceUnder("/static",
FileService.of(Paths.get("/var/lib/www/static")));
// Register the fallback file service.
sb.serviceUnder("/", index.asService());
You can also achieve the same behavior using Annotated services:
// Register the fallback file service.
sb.annotatedService(new Object() {
final HttpFile index = HttpFile.of(Paths.get("/var/lib/www/index.html"));
@Get
@Head
@Path("glob:/**")
public HttpResponse getIndex(ServiceRequestContext ctx, HttpRequest req) {
return index.asService().serve(ctx, req);
}
});
Configuring HttpFile
An HttpFile
can be configured to send different headers than the auto-filled ones using
HttpFileBuilder
. For example, you can:
- Disable auto-generation of
Date
,Last-Modified
,Content-Type
andETag
header. - Customize how
ETag
is calculated from metadata. - Add or set additional custom HTTP headers.
import com.linecorp.armeria.server.file.HttpFile;
import com.linecorp.armeria.server.file.HttpFileBuilder;
HttpFileBuilder fb = HttpFile.builder(Paths.get("/var/lib/www/index.html"));
// Disable the 'Date' header.
fb.date(false);
// Disable the 'Last-Modified' header.
fb.lastModified(false);
// Disable the 'ETag' header.
fb.entityTag(false);
// Disable the 'Content-Type' header.
fb.autoDetectContentType(false);
// Set the 'Content-Type' header manually.
fb.contentType("text/html; charset=EUC-KR");
// Set the 'Cache-Control' header.
fb.cacheControl(ServerCacheControl.REVALIDATED /* "no-cache" */);
// Set a custom header.
fb.setHeader("x-powered-by", "Armeria");
HttpFile f = fb.build();
Caching HttpFile
Unlike FileService
, HttpFile
does not cache the file content.
Use HttpFile.ofCached()
to enable content caching for an existing HttpFile
:
HttpFile uncachedFile = HttpFile.of(Paths.get("/var/lib/www/index.html"));
HttpFile cachedFile = HttpFile.ofCached(uncachedFile, 65536);
Note that you need to specify the maximum allowed length of the cached content. In the above example, the file will not be cached if the length of the file exceeds 65,536 bytes.
Aggregating HttpFile
An HttpFile
usually does not store its content in memory but reads its content on demand, allowing you
to stream a potentially very large file. If you want to ensure the content of the file is kept in memory so
that file I/O does not occur on each retrieval, you can use the HttpFile.aggregate()
method:
// You need to prepare an Executor which will be used for reading the file,
// because file I/O is often a blocking operation.
Executor ioExecutor = ...;
HttpFile file = HttpFile.of(Paths.get("/var/lib/www/img/logo.png");
CompletableFuture<AggregatedHttpFile> future = file.aggregate(ioExecutor);
AggregatedHttpFile aggregated = future.join();
// The content of the file can now be retrieved from memory.
HttpData content = aggregated.content();
Note that an aggregated HttpFile
is not linked in any way from the HttpFile
it was aggregated
from, which means the content and attributes of the aggregated HttpFile
does not change when the original
HttpFile
changes. Use HttpFile.ofCached()
instead if such behavior is necessary.
Building AggregatedHttpFile
from HttpData
The content you need to serve is not always from an external resource but sometimes from memory, such as
byte[]
or String
. Use HttpFile.of()
or HttpFile.builder()
to build an
AggregatedHttpFile
from an in-memory resource:
// Build from a byte array.
AggregatedHttpFile f1 = AggregatedHttpFile.of(HttpData.of(new byte[] { 1, 2, 3, 4 }));
// Build from a String.
AggregatedHttpFile f2 = AggregatedHttpFile.of(HttpData.ofUtf8("Hello, world!"));
// Build using a builder.
AggregatedHttpFile f3 =
AggregatedHttpFile.builder(HttpData.ofAscii("Armeria"))
.lastModified(false)
.build();