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

4.x: Update to static content #9502

Merged
merged 8 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-internal/http-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Features
| CORS | 850 |
| Security | 800 |
| Routing (all handlers) | 100 |
| Static Content | 95 |
| OpenAPI | 90 |
| Observe | 80 |

Expand Down
33 changes: 25 additions & 8 deletions docs/src/main/asciidoc/mp/server.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -333,24 +333,41 @@ io.helidon.examples.AdminService:
.META-INF/microprofile-config.properties - File system static content
----
# Location of content on file system
server.static.path.location=/var/www/html
# default is index.html
server.static.path.welcome=resource.html
# static content path - default is "/"
# server.static.path.context=/static-file
server.features.static-content.path.0.location=/var/www/html
# default is index.html (only in Helidon MicroProfile)
server.features.static-content.path.0.welcome=resource.html
# static content context on webserver - default is "/"
# server.features.static-content.path.0.context=/static-file
----

[source,properties]
.META-INF/microprofile-config.properties - Classpath static content
----
# src/main/resources/WEB in your source tree
server.static.classpath.location=/WEB
server.features.static-content.classpath.0.location=/WEB
# default is index.html
server.static.classpath.welcome=resource.html
server.features.static-content.classpath.0.welcome=resource.html
# static content path - default is "/"
# server.static.classpath.context=/static-cp
# server.features.static-content.classpath.0.context=/static-cp
----

It is usually easier to configure list based options using `application.yaml` instead, such as:
[source,yaml]
.application.yaml - Static content
----
server:
features:
static-content:
welcome: "welcome.html"
classpath:
- context: "/static"
location: "/WEB"
path:
- context: "/static-file"
location: "./static-content"
----

See xref:{rootdir}/config/io_helidon_webserver_staticcontent_StaticContentFeature.adoc[Static Content Feature Configuration Reference] for details. The only difference is that we set welcome file to `index.html` by default.

=== Example configuration of routing

Expand Down
48 changes: 34 additions & 14 deletions docs/src/main/asciidoc/se/webserver.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -626,12 +626,12 @@ To enable HTTP/2 support add the following dependency to your project's `pom.xml

== Static Content Support

+Use the `io.helidon.webserver.staticcontent.StaticContentService` class to serve files and classpath resources.
`StaticContentService` can be created for any readable directory or classpath
context root and registered on a path in `HttpRouting`.
Static content is served through a `StaticContentFeature`. As with other server features, it can be configured through config,
or registered with server config builder.

You can combine dynamic handlers with `StaticContentService` objects: if no file matches the request path, then the request is forwarded to
the next handler.
Static content supports serving of files from classpath, or from any readable directory on file system.
Each content handler has a location specified (required), and can provide a context that will be registered with the WebServer
(defaults to `/`).

=== Maven Coordinates

Expand All @@ -650,19 +650,39 @@ To enable Static Content Support add the following dependency to your project's
To register static content based on a file system (`/pictures`), and classpath (`/`):

[source,java]
.server feature using `WebServerConfig.Builder`
----
include::{sourcedir}/se/WebServerSnippets.java[tag=snippet_22, indent=0]
----
<1> Create a new `StaticContentService` object to serve data from the file system,
and associate it with the `"/pictures"` context path.
<2> Create a `StaticContentService` object to serve resources from the contextual
`ClassLoader`. The specific classloader can be also
defined. A builder lets you provide more configuration values.
<3> `index.html` is the file that is returned if a directory is requested.
<1> Create a new `StaticContentFeature` to register with the web server (will be served on all sockets by default)
<2> Add path location served from `/some/WEB/pics` absolute path
<3> Associate the path location with server context `/pictures`
<4> Add classpath location to serve resources from the contextual
`ClassLoader` from location `/static-content`
<5> `index.html` is the file that is returned if a directory is requested
<6> serve the classpath content on root context `/`

Static content can also be registered using configuration of server feature.

If you use `Config` with your webserver setup, you can register the same static content using configuration:

[source,yaml]
.application.yaml
----
server:
features:
static-content:
path:
- context: "/pictures"
location: "/some/WEB/pics"
classpath:
- context: "/"
welcome: "index.html"
location: "/static-content"
----

See xref:{rootdir}/config/io_helidon_webserver_staticcontent_StaticContentFeature.adoc[Static Content Feature Configuration Reference] for details of configuration options.

A `StaticContentService` object can be created using `create(...)` factory methods or a
`builder`. The `builder` lets you provide more configuration values, including _welcome file-name_
and mappings of filename extensions to media types.

== Media types support
WebServer and WebClient share the HTTP media support of Helidon, and any supported media type can be used in both.
Expand Down
16 changes: 10 additions & 6 deletions docs/src/main/java/io/helidon/docs/se/WebServerSnippets.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@
import io.helidon.http.media.jsonp.JsonpSupport;
import io.helidon.webserver.ProxyProtocolData;
import io.helidon.webserver.WebServer;
import io.helidon.webserver.WebServerConfig;
import io.helidon.webserver.accesslog.AccessLogFeature;
import io.helidon.webserver.http.HttpRoute;
import io.helidon.webserver.http.HttpRouting;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http1.Http1Route;
import io.helidon.webserver.http2.Http2Route;
import io.helidon.webserver.staticcontent.StaticContentService;
import io.helidon.webserver.staticcontent.StaticContentFeature;

// tag::snippet_14[]
import jakarta.json.Json;
Expand Down Expand Up @@ -240,12 +241,15 @@ void snippet_21(HttpRules rules) {
// end::snippet_21[]
}

void snippet_22(HttpRouting.Builder routing) {
void snippet_22(WebServerConfig.Builder builder) {
// tag::snippet_22[]
routing.register("/pictures", StaticContentService.create(Paths.get("/some/WEB/pics"))) // <1>
.register("/", StaticContentService.builder("/static-content") // <2>
.welcomeFileName("index.html") // <3>
.build());
builder.addFeature(StaticContentFeature.builder() // <1>
.addPath(p -> p.location(Paths.get("/some/WEB/pics")) // <2>
.context("/pictures")) // <3>
.addClasspath(cl -> cl.location("/static-content") // <4>
.welcome("index.html") // <5>
.context("/")) // <6>
.build());
// end::snippet_22[]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
* Copyright (c) 2018, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -56,7 +56,7 @@
import io.helidon.webserver.observe.ObserveFeatureConfig;
import io.helidon.webserver.observe.spi.Observer;
import io.helidon.webserver.spi.ServerFeature;
import io.helidon.webserver.staticcontent.StaticContentService;
import io.helidon.webserver.staticcontent.StaticContentConfig;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
Expand Down Expand Up @@ -383,8 +383,10 @@ private void registerKpiMetricsDeferrableRequestContextSetterHandler(JaxRsCdiExt
if (!routingsWithKPIMetrics.contains(routing)) {
routingsWithKPIMetrics.add(routing);
routing.any(KeyPerformanceIndicatorSupport.DeferrableRequestContext.CONTEXT_SETTING_HANDLER);
LOGGER.log(Level.TRACE, () -> String.format("Adding deferrable request KPI metrics context for routing with name '%s'"
+ "", namedRouting.orElse("<unnamed>")));
if (LOGGER.isLoggable(Level.TRACE)) {
LOGGER.log(Level.TRACE, String.format("Adding deferrable request KPI metrics context for routing with name '%s'",
namedRouting.orElse("<unnamed>")));
}
}
}

Expand Down Expand Up @@ -557,40 +559,64 @@ private void registerDefaultRedirect() {
}

private void registerStaticContent() {
Config config = (Config) ConfigProvider.getConfig();
config = config.get("server.static");
Config rootConfig = (Config) ConfigProvider.getConfig();
Config config = rootConfig.get("server.static");

if (config.exists()) {
LOGGER.log(Level.WARNING, "Configuration of static content through \"server.static\" is now deprecated."
+ " Please use \"server.features.static-content\", with sub-keys \"path\" and/or \"classpath\""
+ " containing a list of handlers. At least \"context\" and \"location\" should be provided for each handler."
+ " Location for classpath is the resource location with static content, for path it is the"
+ " location on file system with the root of static content. For advanced configuration such as"
+ " in-memory caching, temporary storage setup etc. kindly see our config reference for "
+ "\"StaticContentFeature\" in documentation.");
}

config.get("classpath")
.ifExists(this::registerClasspathStaticContent);

config.get("path")
.ifExists(this::registerPathStaticContent);

Config featureConfig = rootConfig.get("server.features.static-content");
if (featureConfig.exists()) {
var builder = StaticContentConfig.builder()
.config(featureConfig);
if (builder.welcome().isEmpty()) {
builder.welcome("index.html");
}
addFeature(builder.build());
}
}

@SuppressWarnings("removal")
private void registerPathStaticContent(Config config) {
Config context = config.get("context");
StaticContentService.FileSystemBuilder pBuilder = StaticContentService.builder(config.get("location")
io.helidon.webserver.staticcontent.StaticContentService.FileSystemBuilder pBuilder =
io.helidon.webserver.staticcontent.StaticContentService.builder(config.get("location")
.as(Path.class)
.get());
pBuilder.welcomeFileName(config.get("welcome")
.asString()
.orElse("index.html"));

StaticContentService staticContent = pBuilder.build();
var staticContent = pBuilder.build();

if (context.exists()) {
routingBuilder.register(context.asString().get(), staticContent);
} else {
Supplier<StaticContentService> ms = () -> staticContent;
Supplier<io.helidon.webserver.staticcontent.StaticContentService> ms = () -> staticContent;
routingBuilder.register(ms);
}
STARTUP_LOGGER.log(Level.TRACE, "Static path");
}

@SuppressWarnings("removal")
private void registerClasspathStaticContent(Config config) {
Config context = config.get("context");

StaticContentService.ClassPathBuilder cpBuilder = StaticContentService.builder(config.get("location").asString().get());
io.helidon.webserver.staticcontent.StaticContentService.ClassPathBuilder cpBuilder =
io.helidon.webserver.staticcontent.StaticContentService.builder(config.get("location").asString().get());
cpBuilder.welcomeFileName(config.get("welcome")
.asString()
.orElse("index.html"));
Expand All @@ -604,7 +630,7 @@ private void registerClasspathStaticContent(Config config) {
.flatMap(List::stream)
.forEach(cpBuilder::addCacheInMemory);

StaticContentService staticContent = cpBuilder.build();
var staticContent = cpBuilder.build();

if (context.exists()) {
routingBuilder.register(context.asString().get(), staticContent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ tracing.global=false

features.print-details=true

server.static.classpath.context=/static
server.static.classpath.location=/web
server.static.classpath.welcome=welcome.txt
server.features.static-content.classpath.0.context=/static
server.features.static-content.classpath.0.location=/web
server.features.static-content.classpath.0.welcome=welcome.txt
16 changes: 14 additions & 2 deletions webserver/static-content/etc/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright (c) 2022, 2023 Oracle and/or its affiliates.
Copyright (c) 2022, 2024 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -23,11 +23,17 @@
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">

<Match>
<!-- input for temp file creation -->
<!-- jar file loaded from classpath for a requested resource, validated to be under the configured classpath root -->
<Class name="io.helidon.webserver.staticcontent.ClassPathContentHandler"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>

<Match>
<!-- input for temp file creation -->
<Class name="io.helidon.webserver.staticcontent.TemporaryStorageImpl"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>

<Match>
<!-- URL is obtained from classloader for a resource on classpath and then used to read it -->
<Class name="io.helidon.webserver.staticcontent.ClassPathContentHandler"/>
Expand All @@ -39,4 +45,10 @@
<Class name="io.helidon.webserver.staticcontent.CachedHandlerUrlStream"/>
<Bug pattern="URLCONNECTION_SSRF_FD"/>
</Match>

<Match>
<!-- URL is obtained from classloader for a resource on classpath and then used to read it -->
<Class name="io.helidon.webserver.staticcontent.CachedHandlerJar"/>
<Bug pattern="URLCONNECTION_SSRF_FD"/>
</Match>
</FindBugsFilter>
47 changes: 47 additions & 0 deletions webserver/static-content/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.logging</groupId>
<artifactId>helidon-logging-jul</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -75,8 +80,50 @@
<artifactId>helidon-common-features-processor</artifactId>
<version>${helidon.version}</version>
</path>
<path>
<groupId>io.helidon.codegen</groupId>
<artifactId>helidon-codegen-apt</artifactId>
<version>${helidon.version}</version>
</path>
<path>
<groupId>io.helidon.config.metadata</groupId>
<artifactId>helidon-config-metadata-codegen</artifactId>
<version>${helidon.version}</version>
</path>
<path>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-codegen</artifactId>
<version>${helidon.version}</version>
</path>
<path>
<groupId>io.helidon.codegen</groupId>
<artifactId>helidon-codegen-helidon-copyright</artifactId>
<version>${helidon.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
<dependencies>
<dependency>
<groupId>io.helidon.codegen</groupId>
<artifactId>helidon-codegen-apt</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.config.metadata</groupId>
<artifactId>helidon-config-metadata-codegen</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-codegen</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.codegen</groupId>
<artifactId>helidon-codegen-helidon-copyright</artifactId>
<version>${helidon.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Expand Down
Loading