Skip to content

Commit 8443d96

Browse files
authored
[4.x] Observers now inherit weight of the ObserveFeature. (helidon-io#8554)
* HttpFeatures now inherit weight of ServerFeatures. As ObserverFeature weight can be configured, we can control whether it is executed before or after user routing (default is after). * Updated all features to honor weight from configuration both for server features and associated http features. * Update of examples that were broken by correct handling of weight in server features
1 parent 5730a16 commit 8443d96

26 files changed

Lines changed: 699 additions & 68 deletions

File tree

examples/security/outbound-override/src/main/java/io/helidon/security/examples/outbound/OutboundOverrideExample.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.helidon.security.Subject;
2525
import io.helidon.webserver.WebServer;
2626
import io.helidon.webserver.WebServerConfig;
27+
import io.helidon.webserver.context.ContextFeature;
2728
import io.helidon.webserver.security.SecurityHttpFeature;
2829

2930
/**
@@ -77,7 +78,12 @@ static void setup(WebServerConfig.Builder server) {
7778
Config clientConfig = Config.create(ConfigSources.classpath("client-service.yaml"));
7879
Config backendConfig = Config.create(ConfigSources.classpath("backend-service.yaml"));
7980

80-
server.config(clientConfig.get("security"))
81+
// as we use the security http feature directly, we cannot use discovered security feature
82+
// this is a unique case where we combine two sets of server set-ups in a single webserver
83+
server.featuresDiscoverServices(false)
84+
// context feature is a pre-requisite of security
85+
.addFeature(ContextFeature.create())
86+
.config(clientConfig.get("security"))
8187
.routing(routing -> routing
8288
.addFeature(SecurityHttpFeature.create(clientConfig.get("security.web-server")))
8389
.register(new OverrideService()))

examples/security/outbound-override/src/main/java/io/helidon/security/examples/outbound/OutboundOverrideJwtExample.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2018, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import io.helidon.security.Subject;
2626
import io.helidon.webserver.WebServer;
2727
import io.helidon.webserver.WebServerConfig;
28+
import io.helidon.webserver.context.ContextFeature;
2829
import io.helidon.webserver.security.SecurityHttpFeature;
2930

3031
/**
@@ -85,7 +86,12 @@ static void setup(WebServerConfig.Builder server) {
8586
Config clientConfig = Config.create(ConfigSources.classpath("client-service-jwt.yaml"));
8687
Config backendConfig = Config.create(ConfigSources.classpath("backend-service-jwt.yaml"));
8788

88-
server.routing(routing -> routing
89+
// as we use the security http feature directly, we cannot use discovered security feature
90+
// this is a unique case where we combine two sets of server set-ups in a single webserver
91+
server.featuresDiscoverServices(false)
92+
// context feature is a pre-requisite of security
93+
.addFeature(ContextFeature.create())
94+
.routing(routing -> routing
8995
.addFeature(SecurityHttpFeature.create(clientConfig.get("security.web-server")))
9096
.register(new JwtOverrideService()))
9197

examples/security/webserver-signatures/src/main/java/io/helidon/examples/security/signatures/SignatureExampleBuilderMain.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.helidon.security.providers.httpsign.OutboundTargetDefinition;
4040
import io.helidon.webserver.WebServer;
4141
import io.helidon.webserver.WebServerConfig;
42+
import io.helidon.webserver.context.ContextFeature;
4243
import io.helidon.webserver.http.HttpRouting;
4344
import io.helidon.webserver.security.SecurityFeature;
4445
import io.helidon.webserver.security.SecurityHttpFeature;
@@ -121,7 +122,12 @@ public static void main(String[] args) {
121122
}
122123

123124
static void setup(WebServerConfig.Builder server) {
124-
server.routing(SignatureExampleBuilderMain::routing1)
125+
// as we explicitly configure SecurityHttpFeature, we must disable automated loading of security,
126+
// as it would add another feature with different configuration
127+
server.featuresDiscoverServices(false)
128+
// context is a required pre-requisite of security
129+
.addFeature(ContextFeature.create())
130+
.routing(SignatureExampleBuilderMain::routing1)
125131
.putSocket("service2", socket -> socket
126132
.routing(SignatureExampleBuilderMain::routing2));
127133
}

examples/security/webserver-signatures/src/main/java/io/helidon/examples/security/signatures/SignatureExampleConfigMain.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2018, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import io.helidon.config.ConfigSources;
2323
import io.helidon.webserver.WebServer;
2424
import io.helidon.webserver.WebServerConfig;
25+
import io.helidon.webserver.context.ContextFeature;
2526
import io.helidon.webserver.http.HttpRouting;
2627
import io.helidon.webserver.security.SecurityHttpFeature;
2728

@@ -72,7 +73,12 @@ public static void main(String[] args) {
7273
}
7374

7475
static void setup(WebServerConfig.Builder server) {
75-
server.routing(SignatureExampleConfigMain::routing1)
76+
// as we explicitly configure SecurityHttpFeature, we must disable automated loading of security,
77+
// as it would add another feature with different configuration
78+
server.featuresDiscoverServices(false)
79+
// context is a required pre-requisite of security
80+
.addFeature(ContextFeature.create())
81+
.routing(SignatureExampleConfigMain::routing1)
7682
.putSocket("service2", socket -> socket
7783
.routing(SignatureExampleConfigMain::routing2));
7884
}

openapi/openapi/src/main/java/io/helidon/openapi/OpenApiFeature.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030

3131
import io.helidon.builder.api.RuntimeType;
3232
import io.helidon.common.LazyValue;
33+
import io.helidon.common.Weighted;
3334
import io.helidon.common.config.Config;
3435
import io.helidon.common.media.type.MediaType;
3536
import io.helidon.common.media.type.MediaTypes;
@@ -41,7 +42,7 @@
4142
* Helidon Support for OpenAPI.
4243
*/
4344
@RuntimeType.PrototypedBy(OpenApiFeatureConfig.class)
44-
public final class OpenApiFeature implements ServerFeature, RuntimeType.Api<OpenApiFeatureConfig> {
45+
public final class OpenApiFeature implements Weighted, ServerFeature, RuntimeType.Api<OpenApiFeatureConfig> {
4546

4647
static final String OPENAPI_ID = "openapi";
4748
static final double WEIGHT = 90;
@@ -171,6 +172,11 @@ public String type() {
171172
return OPENAPI_ID;
172173
}
173174

175+
@Override
176+
public double weight() {
177+
return config.weight();
178+
}
179+
174180
/**
175181
* Initialize the model.
176182
*/

webserver/access-log/src/main/java/io/helidon/webserver/accesslog/AccessLogFeature.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2019, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.function.Consumer;
2323

2424
import io.helidon.builder.api.RuntimeType;
25+
import io.helidon.common.Weighted;
2526
import io.helidon.config.Config;
2627
import io.helidon.webserver.WebServer;
2728
import io.helidon.webserver.http.HttpRouting;
@@ -31,7 +32,7 @@
3132
* Service that adds support for Access logging to Server.
3233
*/
3334
@RuntimeType.PrototypedBy(AccessLogConfig.class)
34-
public final class AccessLogFeature implements ServerFeature, RuntimeType.Api<AccessLogConfig> {
35+
public final class AccessLogFeature implements Weighted, ServerFeature, RuntimeType.Api<AccessLogConfig> {
3536
/**
3637
* Name of the {@link System#getLogger(String)} used to log access log records.
3738
* The message logged contains all information, so the format should be modified
@@ -144,6 +145,11 @@ public String type() {
144145
return ACCESS_LOG_ID;
145146
}
146147

148+
@Override
149+
public double weight() {
150+
return config.weight();
151+
}
152+
147153
AccessLogHttpFeature httpFeature(String socketName) {
148154
return new AccessLogHttpFeature(weight, clock, logFormat, loggerName, socketName);
149155
}

webserver/context/src/main/java/io/helidon/webserver/context/ContextFeature.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
3030
* When added to the processing, further processing will be executed in a request specific context.
3131
*/
3232
@RuntimeType.PrototypedBy(ContextFeatureConfig.class)
33-
public class ContextFeature implements ServerFeature, RuntimeType.Api<ContextFeatureConfig> {
33+
public class ContextFeature implements Weighted, ServerFeature, RuntimeType.Api<ContextFeatureConfig> {
3434
/**
3535
* Default weight of the feature. It is quite high, as context is used by a lot of other features.
3636
*/
@@ -123,4 +123,9 @@ public String type() {
123123
public ContextFeatureConfig prototype() {
124124
return config;
125125
}
126+
127+
@Override
128+
public double weight() {
129+
return config.weight();
130+
}
126131
}

webserver/cors/src/main/java/io/helidon/webserver/cors/CorsFeature.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import java.util.function.Consumer;
2222

2323
import io.helidon.builder.api.RuntimeType;
24+
import io.helidon.common.Weighted;
2425
import io.helidon.common.config.Config;
2526
import io.helidon.webserver.WebServer;
2627
import io.helidon.webserver.spi.ServerFeature;
@@ -29,7 +30,7 @@
2930
* Adds CORS support to Helidon WebServer.
3031
*/
3132
@RuntimeType.PrototypedBy(CorsConfig.class)
32-
public class CorsFeature implements ServerFeature, RuntimeType.Api<CorsConfig> {
33+
public class CorsFeature implements Weighted, ServerFeature, RuntimeType.Api<CorsConfig> {
3334
/**
3435
* Default weight of the feature.
3536
*/
@@ -134,4 +135,9 @@ public String type() {
134135
public CorsConfig prototype() {
135136
return config;
136137
}
138+
139+
@Override
140+
public double weight() {
141+
return config.weight();
142+
}
137143
}

webserver/observe/config/src/main/java/io/helidon/webserver/observe/config/ConfigObserver.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,9 +22,9 @@
2222
import java.util.regex.Pattern;
2323

2424
import io.helidon.builder.api.RuntimeType;
25-
import io.helidon.http.HttpException;
26-
import io.helidon.http.Status;
25+
import io.helidon.webserver.http.HttpFeature;
2726
import io.helidon.webserver.http.HttpRouting;
27+
import io.helidon.webserver.observe.DisabledObserverFeature;
2828
import io.helidon.webserver.observe.spi.Observer;
2929
import io.helidon.webserver.spi.ServerFeature;
3030

@@ -105,13 +105,11 @@ public void register(ServerFeature.ServerFeatureContext featureContext,
105105
if (config.enabled()) {
106106
for (HttpRouting.Builder routing : observeEndpointRouting) {
107107
// register the service itself
108-
routing.register(endpoint, new ConfigService(patterns, findProfile(), config.permitAll()));
108+
routing.addFeature(new ConfigHttpFeature(endpoint, patterns, findProfile(), config.permitAll()));
109109
}
110110
} else {
111111
for (HttpRouting.Builder builder : observeEndpointRouting) {
112-
builder.any(endpoint + "/*", (req, res) -> {
113-
throw new HttpException("Config endpoint is disabled", Status.SERVICE_UNAVAILABLE_503, true);
114-
});
112+
builder.addFeature(DisabledObserverFeature.create("Config", endpoint + "/*"));
115113
}
116114
}
117115
}
@@ -132,4 +130,23 @@ private static String findProfile() {
132130
}
133131
return "";
134132
}
133+
134+
private static class ConfigHttpFeature implements HttpFeature {
135+
private final String endpoint;
136+
private final List<Pattern> patterns;
137+
private final String profile;
138+
private final boolean permitAll;
139+
140+
private ConfigHttpFeature(String endpoint, List<Pattern> patterns, String profile, boolean permitAll) {
141+
this.endpoint = endpoint;
142+
this.patterns = patterns;
143+
this.profile = profile;
144+
this.permitAll = permitAll;
145+
}
146+
147+
@Override
148+
public void setup(HttpRouting.Builder routing) {
149+
routing.register(endpoint, new ConfigService(patterns, profile, permitAll));
150+
}
151+
}
135152
}

webserver/observe/health/src/main/java/io/helidon/webserver/observe/health/HealthObserver.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,9 +29,9 @@
2929
import io.helidon.common.config.Config;
3030
import io.helidon.health.HealthCheck;
3131
import io.helidon.health.spi.HealthCheckProvider;
32-
import io.helidon.http.HttpException;
33-
import io.helidon.http.Status;
32+
import io.helidon.webserver.http.HttpFeature;
3433
import io.helidon.webserver.http.HttpRouting;
34+
import io.helidon.webserver.observe.DisabledObserverFeature;
3535
import io.helidon.webserver.observe.spi.Observer;
3636
import io.helidon.webserver.spi.ServerFeature;
3737

@@ -112,15 +112,13 @@ public void register(ServerFeature.ServerFeatureContext featureContext,
112112

113113
String endpoint = endpointFunction.apply(config.endpoint());
114114
if (config.enabled()) {
115-
for (HttpRouting.Builder routing : observeEndpointRouting) {
116-
// register the service itself
117-
routing.register(endpoint, new HealthService(config, all));
115+
for (HttpRouting.Builder builder : observeEndpointRouting) {
116+
// we must use a feature to honor weight of the observer feature itself
117+
builder.addFeature(new HealthHttpFeature(endpoint, config, all));
118118
}
119119
} else {
120120
for (HttpRouting.Builder builder : observeEndpointRouting) {
121-
builder.any(endpoint + "/*", (req, res) -> {
122-
throw new HttpException("Health endpoint is disabled", Status.SERVICE_UNAVAILABLE_503, true);
123-
});
121+
builder.addFeature(DisabledObserverFeature.create("Health", endpoint + "/*"));
124122
}
125123
}
126124
}
@@ -134,4 +132,21 @@ public String type() {
134132
public HealthObserverConfig prototype() {
135133
return config;
136134
}
135+
136+
private static class HealthHttpFeature implements HttpFeature {
137+
private final String endpoint;
138+
private final HealthObserverConfig config;
139+
private final List<HealthCheck> all;
140+
141+
private HealthHttpFeature(String endpoint, HealthObserverConfig config, List<HealthCheck> all) {
142+
this.endpoint = endpoint;
143+
this.config = config;
144+
this.all = all;
145+
}
146+
147+
@Override
148+
public void setup(HttpRouting.Builder routing) {
149+
routing.register(endpoint, new HealthService(config, all));
150+
}
151+
}
137152
}

0 commit comments

Comments
 (0)