I am trying to create a spring-boot-2 REST api using spring-boot-starter-webflux and reactive Netty. I am trying to set the context-path as per the new properties to be defined in application.yml defined in Spring-Boot-2.
server.servlet.context-path: /api # Define the server context path
However it looks like Webflux, Netty doesn't use/recognise this property defined in application.yml.
If I use spring-boot-starter-web and Tomcat as the default server then it works fine and recognises context-path properly.
Didn't find anything mentioned about Netty's context-path in Spring Boot 2 documentation.
Spring Boot Version = 2.0.3.RELEASE
Please let me know if I missed something or this is the default behaviour of Webflux Netty ?
Configuring the context path is servlet-specific. when using WebFlux,
the configuration property was renamed to server.servlet.context-path and only for servlet based deployment.
You can read below thread to how you can deal with context path in webflux, please see comment
https://github.com/spring-projects/spring-boot/issues/10129#issuecomment-351953449
Webflux Context path issue thread
In spring boot 2.3.x you can set spring.webflux.base-path property
It worked for me with
spring.webflux.base-path=/myPath
but only when adding the hint in this comment: https://stackoverflow.com/a/67840678/8376373
which suggests to inject a WebFluxProperties Bean
#Bean
fun webFluxProperties(): WebFluxProperties {
return WebFluxProperties()
}
You can use a WebFilter to work around this limitation:
#Autowired
lateinit var serverProperties: ServerProperties
#Bean
fun contextPathWebFilter(): WebFilter {
val contextPath = serverProperties.servlet.contextPath
return WebFilter { exchange, chain ->
val request = exchange.request
if (request.uri.path.startsWith(contextPath)) {
chain.filter(
exchange.mutate()
.request(request.mutate().contextPath(contextPath).build())
.build())
} else {
chain.filter(exchange)
}
}
}
Related
I am wondering how I can configure kotlinx as a default serialization in Spring Boot app.
For Jackson, I would use e.g. spring.jackson.deserialization.fail-on-unknown-properties=false
But I haven't found any configuration options for kotlinx in Spring.
I know kotlinx supports this, I only cannot find a way to configure it on Spring level so that it works for example in controller method signatures:
#PostMapping("/foo")
fun fooMethod(
#RequestBody fooJsonRequest: SomeDataClassRepresentingTheJson,
) {}
↑ Throws HttpMessageNotReadableException exception suggesting I use ignoreUnknownKeys=true when constructing kotlinx.serialization.json.Json. However I do not know where to do this so it would apply to Spring itself.
I have tried creating a bean providing Json object to no avail.
Publish a #Bean of the following type and customize Json constructor as needed:
#Bean
fun messageConverter(): KotlinSerializationJsonHttpMessageConverter {
return KotlinSerializationJsonHttpMessageConverter(Json {
ignoreUnknownKeys = true
})
}
Due to firewall/proxy configuration (outside of the spring application), I have created an actuator endpoint that simply forwards all requests to it to another endpoint with Webflux. The class looks like this:
#Component
#RestControllerEndpoint(id = "my-proxy", enableByDefault = true)
class ApiProxyEndpoint {
#Value("\${management.endpoints.web.base-path}")
lateinit var managementPath: String
#Autowired
lateinit var webHandler: WebHandler
#RequestMapping("**")
fun myProxyEndpoint(exchange: ServerWebExchange): Mono<Void> {
val originalPath = exchange.request.path.toString()
val pathToUse = originalPath.substringAfter(managementPath)
val updatedRequest = exchange.request.mutate().contextPath("/").path(pathToUse)
.header("someheader", "some value").build()
val updatedExchange = exchange.mutate().request(updatedRequest).build()
return webHandler.handle(updatedExchange)
}
}
This works great when using webflux. However, I want to do exactly the same for a non-webflux (i.e. blocking) Spring Boot MVC application. How can I do this? What (I think) I'm looking for is the equalivant of ServerWebExchange and WebHandler but for non-webflux applications. Or is there another way to do it? Note that it's important that this endpoint is only available from the "management api" (actuator).
I'm using Spring Boot 2.6.7 if that matters.
Could we add Actuator endpoints as a groupedOpenApi that will be gourped separately ?
ex :
#bean
public GroupedOpenApi actuatorApi() {
return GroupedOpenApi.builder().setGroup("Actuator")
.packagesToScan("org.springframework.boot.actuate")
.pathsToMatch("/actuator/**")
.build();
}
Thanks
First you need to enable actuator on the swagger-ui:
springdoc.show-actuator=true
You just need to declare GroupedOpenApi bean:
#Bean
public GroupedOpenApi actuatorApi(){
String[] paths = {"/actuator/**"};
return GroupedOpenApi.builder()
.setGroup("groups")
.pathsToMatch(paths)
.build();
}
Here are the steps to add actuator:
https://github.com/springdoc/springdoc-openapi-demos/commit/aa6bcf1f0312c8fc36f94aeb4653718b36c308f6
The solution provided by #brianbro mostly works but there is an issue where some endpoints that use parameters in URL, e.g. /actuator/metrics/{requiredMetricName} due to way spring boot handles the actuator endpoints (single handler method, no #PathParam) the PathParam fields don't show in Swagger. See Spring boot actuator endpoint parameters issue.
Example of what shows in Swagger without resolving the issue:
Example of what shows in Swagger after resolving the issue:
How to fix this is discussed in Adding Actuator as a groupedOpenApi but some of the links are broken. The key issue is there is an ActuatorOpenApiCustomiser class that is used to fix the PathParams for Actuator, and that class is not being called when Actuator resides within a GroupedOpenApi.
Full solution (working with springdoc:1.6.9)...
First you need to enable actuator on the swagger-ui:
springdoc.show-actuator=true
Declare GroupedOpenApi bean using code (you can't use the springdoc.group-configs[0] properties because you need to add a OpenApiCustomiser to the group):
#Bean
public GroupedOpenApi actuatorApi(OpenApiCustomiser actuatorOpenApiCustomiser){
String[] paths = {"/actuator/**"};
return GroupedOpenApi.builder()
.setGroup("Actuator")
.pathsToMatch(paths)
.addOpenApiCustomiser(actuatorOpenApiCustomiser)
.build();
}
Addtional Sample from SpringDoc
I am trying to set up Spring Actuator with existing Gradle Spring MVC project. I am not able to use #EnableAutoConfiguration.
Unfortunately, I am not able to reach actuator endpoints, I think I am missing something.
The Spring dependencies in the project are:
// springVersion = 5.1.+
implementation(
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-webmvc:$springVersion",
"org.springframework:spring-jdbc:$springVersion")
implementation 'org.springframework.boot:spring-boot-starter-actuator'
I am trying to configure project with following:
#Configuration
#Import({EndpointAutoConfiguration.class,
MetricsEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
MappingsEndpointAutoConfiguration.class,
InfoEndpointAutoConfiguration.class})
#EnableWebMvc
public class DI_App {
}
In properties file, I added:
management.endpoints.web.exposure.include=*
Non of actuator endpoints is enabled, I am getting 404 when trying to access them.
I went through many related questions, but non of the solutions worked for me.
I might need to define custom EndpointHandlerMapping but not sure how to do this, it seems unavailable.
(Ref: https://stackoverflow.com/a/53010693)
EDIT:
Currently, my app config looks like this:
#Configuration
#EnableWebMvc
#ComponentScan("com.test.springtest")
#Import({
ConfigurationPropertiesReportEndpointAutoConfiguration.class,
EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class,
InfoEndpointAutoConfiguration.class,
InfoContributorAutoConfiguration.class,
LogFileWebEndpointAutoConfiguration.class,
LoggersEndpointAutoConfiguration.class,
WebMvcMetricsAutoConfiguration.class,
ManagementWebSecurityAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class
})
public class DI_App {
private final ApplicationContext _applicationContext;
DI_App(ApplicationContext applicationContext) {
_applicationContext = applicationContext;
System.setProperty("management.endpoints.web.exposure.include", "*");
System.setProperty("management.endpoints.jmx.exposure.exclude", "*");
System.setProperty("management.endpoints.web.base-path", "/manage");
System.setProperty("management.server.port", "10100");
}
#Bean
public WebMvcEndpointHandlerMapping endpointHandlerMapping(Collection<ExposableWebEndpoint> endpoints) {
List<String> mediaTypes = List.of(MediaType.APPLICATION_JSON_VALUE, ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes, mediaTypes);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(_applicationContext,
new ConversionServiceParameterValueMapper(),
endpointMediaTypes,
List.of(EndpointId::toString),
emptyList(),
emptyList());
return new WebMvcEndpointHandlerMapping(new EndpointMapping("/manage"),
endpoints,
endpointMediaTypes,
new CorsConfiguration(),
new EndpointLinksResolver(discoverer.getEndpoints()));
}
}
I had to add dispatcherServlet bean, in order to be able to add ManagementContextAutoConfiguration.class to Imports:
#Component
public class AppDispatcherServlet implements DispatcherServletPath {
#Override
public String getPath() {
return "/";
}
}
Current state is that when going to /manage endpoint I get this:
{"_links":{"self":{"href":"http://localhost:10100/dev/manage","templated":false},"info":{"href":"http://localhost:10100/dev/manage/info","templated":false}}}
But http://localhost:10100/dev/manage/info returns 404 and no other endpoints are available.
I'm using Maven, not Gradle, but was in a similar situation. I had a working spring-boot-actuator 1.4.2.RELEASE Health actuator endpoint with Spring MVC 4.3.21. Upgraded to spring-boot-starter-actuator 2.6.1 and Spring MVC 5.3.13 and the following works for me to reach /myAppContext/health.
The DispatcherServletAutoConfiguration import may be able to replace your explicit DispatcherServlet bean. My case doesn't include the Info actuator endpoint but the key thing for me was the specific Imports below. Order is somewhat important for certain imports, at least in my testing.
I know very little about spring boot so this is the result of enabling auto configuration, pouring through spring boot TRACE log output, and trying lots of different import combinations.
#Configuration
#EnableWebMvc
#Import({
DispatcherServletAutoConfiguration.class,
WebMvcAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
EndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
WebMvcEndpointManagementContextConfiguration.class
})
#PropertySource("classpath:/health.properties")
public class MyAppActuatorConfig {
// 1.x version had EndpointHandlerMapping and HealthMvcEndpoint beans here.
// There may be a more spring-boot-ish way to get this done : )
}
And a minimal health.properties that suited my deployment specifics where security was already in place:
management.endpoints.web.base-path=/
management.endpoint.health.show-details=when-authorized
management.endpoint.health.show-components=when-authorized
Can anybody tell me how 2 ports (for HTTP and HTTPS) can be configured when using Spring Boot 2 and WebFlux? Any hint is appreciated!
This isn't directly supported by Spring Boot 2 yet.
But, you may be able to get it to work in a couple of ways.
By default, Spring Boot WebFlux uses Netty. If you are already configured for ssl, then Spring Boot will start up and open port 8443 (or whatever you have configured).
Then, to add 8080, you can do:
#Autowired
HttpHandler httpHandler;
WebServer http;
#PostConstruct
public void start() {
ReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(8080);
this.http = factory.getWebServer(this.httpHandler);
this.http.start();
}
#PreDestroy
public void stop() {
this.http.stop();
}
Which is a bit clunky since your https configuration is in one spot (application.yml) and your http configuration is in Java config, but I have tested this myself with a toy application. Not sure how robust of a solution it is, though.
Another option that may work is to try the equivalent of other suggestions, but use the reactive version of the class, which is TomcatReactiveWebServerFactory. I'm not aware of any way to provide more than one connector for it, but you could possibly override its getWebServer method:
#Bean
TomcatReactiveWebServerFactory twoPorts() {
return new TomcatReactiveWebServerFactory(8443) {
#Override
public WebServer getWebServer(HttpHandler httpHandler) {
// .. copy lines from parent class
// .. and add your own Connector, similar to how tutorials describe for servlet-based support
}
}
}
Also, a bit messy, and I have not tried that approach myself.
Of course, keep track of the ticket so you know when Spring Boot 2 provides official support.
Follow the instructions listed in the link provided by jojo_berlin (here's the link). Instead of using his EmbeddedTomcatConfiguration class though, use this below
#Configuration
public class TomcatConfig {
#Value("${server.http.port}")
private int httpPort;
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
factory.addAdditionalTomcatConnectors(connector);
return factory;
}
}
Actually you can define a second connector as described here . So you can define a https connector as your default and an additional HTTP Connector