Spring Boot: specify port at the mapping level - spring

Spring Boot: I want to have achieved the following: some URL paths are mapped to a port, some to another.
In other words I'd like something like:
public class Controller1 {
#RequestMapping(value="/path1", port="8080") public...
#RequestMapping(value="/path2", port="8081") public...
}
So that my app responds to both localhost:8080/path1 and localhost:8081/path2
It's acceptable to have 2 separate controllers within the app.
I have managed to partially succeed by implementing an EmbeddedServletContainerCustomizer for tomcat, but it would be nice to be able to achieve this inside the controller if possible.
Is it possible?

What you are trying to do would imply that the application is listening on multiple ports. This would in turn mean that you start multiple tomcat, since spring-boot packages one container started on a single port.
What you can do
You can launch the same application twice, using different spring profiles. Each profile would configure a different port.
2 properties:
application-one.properties: server.port=8080
application-two.properties: server.port=8081
2 controllers
#Profile("one")
public class Controller1 {
#RequestMapping(value="/path1") public...
}
#Profile("two")
public class Controller2 {
#RequestMapping(value="/path2") public...
}
Each controller is activated when the specified spring profile is provided.
Launch twice
$ java -jar -Dspring.profiles.active=one YourApp.jar
$ java -jar -Dspring.profiles.active=two YourApp.jar

While you cannot prevent making call on the undesired port, you can specify HttpServletRequest among other parameters of the method of the controller, and then use HttpServletRequest.getLocalPort() to obtain the port the call is made on.
Then you can manually return the HTTP error code if the request is made on the wrong port, or forward to another controller if the design is such that same path on different ports must be differently processed.

Related

Spring cloud gateway proxy to controller in same application

I want to achieve the following with a spring boot webflux application:
I have an endpoint api/test. I would like the same controller to be available on dynamically configured sub paths. E.g. if configured a sub-route "app" then a request to app/api/test should end up in the same controller.
To facilitate this I did the following using a RouteLocatorBuilder:
route(id = "proxy_api_test") {
host(location.host)
path("/${location.route}/api/test/**" )
filters{
filter(setPathGatewayFilter.apply(createSetPathConfig("/api/test")))
}
uri(location.uri)
}
In case of testing on localhost for example location.host would be "localhost:8080" and location.route could be "app" and location.uri would be "http://localhost:8080".
And createSetPathConfig is given as:
fun createSetPathConfig(template: String): SetPathGatewayFilterFactory.Config{
val config = SetPathGatewayFilterFactory.Config()
config.template = template
return config
}
When running the application that would work like a charm because requests to http://localhost:8080/app/api/test would be redirected to http://localhost:8080/api/test with the help of spring cloud gateway. I have chose this approach also because it could be various sub paths at the same time, so the same controller must be available from different entry paths.
Now what I see is that this does not work in unit-tests using an #Autowired val client: WebTestClient because when executing the unit-test in fact no web-server is running. Is there a way to indicate with the uri in with RouteLocatorBuilder that the request should be executed on the same host such that unit-test would also work with the same logic? Because in fact in this case I would like spring cloud gateway not to forward to another host but to just change the routes dynamically.

Kubernetes and Spring Boot #Service load balancing

I have Kubernetes running on two nodes and one application deployed on the two nodes (two pods, one per node).
It's a Spring Boot application. It uses OpenFeign for service discoverability. In the app i have a RestController defined and it has a few APIs and an #Autowired #Service which is called from inside the APIs.
Whenever i do a request on one of the APIs Kubernetes uses some sort of load-balancing to route the traffic to one of the pods, and the apps RestController is called. This is fine and i want this to be load-balanced.
The problem happens once that API is called and it calls the #Autowired #Service. Somehow this too gets load-balanced and the call to the #Service might end up on the other node.
Heres and example:
we have two nodes: node1, node2
we make a request to node1's IP address.
this might get load-balanced to node2 (this is fine)
node1 gets the request and calls the #Autowired #Service
the call jumps to node2 (this is where the problem happens)
And in code:
Controller:
#Autowired
private lateinit var userService: UserService
#PostMapping("/getUser")
fun uploadNewPC(#RequestParam("userId") userId: String): User {
println(System.getEnv("hostIP")) //123.45.67.01
return userService.getUser(userId)
}
Service:
#Service
class UserService {
fun getUser(userId: String) : User {
println(System.getEnv("hostIP")) //123.45.67.02
...
}
}
I want the load-balancing to happen only on the REST requests not the internal calls of the app to its #Service components. How would i achieve this? Is there any configuration to the way Spring Boot's #service components operate in Kubernetes clusters? Can i change this?
Thanks in advance.
Edit:
After some debugging i found that It wasn't the Service that was load balanced to another node but the initial http request. Even though the request is specifically send to the url of node1... And since i was debugging both nodes at the same time, i didn't notice this.
Well, I haven't used openfeign, but in my understanding it can loadbalance only REST requests indeed.
If I've got your question right, you say that when the REST controller calls the service component (UserService in this case) the network call is issued and this is undesirable.
In this case, I believe, following points for consideration will be beneficial:
Spring boot has nothing to do with load balancing at this level by default, it should be a configured in the spring boot application somehow.
This also has nothing to do with the fact that this application runs in a Kubernetes environment, again its only a spring boot configuration.
Assuming, you have a UserService interface that obviously doesn't have any load balancing logic, spring boot must wrap it into some kind of proxy that adds these capabilities. So try to debug the application startup, place a breakpoint in the controller method and check out what is the actual type of the user service, again it must be some sort of proxy
If the assumption in 3 is correct, there must be some kind of bean post processor (possibly in spring.factories file of some dependency) class that gets registered within the application context. Probably if you'll create some custom method that will print all beans (Bean Post Processor is also a bean), you'll see the suspicious bean.

Starting Spring boot REST controller in two ports

Is there a way to have two rest controller running on two different ports from one spring boot application ?
Say for example Controller_A running in http://localhost:8080 and Controller_B running in http://localhost:9090 in one SpringBoot main Application ?
One way of doing this is actually creating two application properties;
app-A.properties
server.port=8080
app-B.properties
server.port=9090
And then in your controllers, put annotation like below;
#Profile("A")
public class ControllerA {
...
}
#Profile("B")
public class ControllerB {
...
}
Finally you need to launch your application twice with following settings;
java -jar -Dspring.profiles.active=A awesomeSpringApp.jar
java -jar -Dspring.profiles.active=B awesomeSpringApp.jar

Spring Boot: Retrieve config via rest call upon application startup

I d like to make a REST call once on application startup to retrieve some configuration parameters.
For example, we need to retrieve an entity called FleetConfiguration from another server. I d like to do a GET once and save the keep the data in memory for the rest of the runtime.
What s the best way of doing this in Spring? using Bean, Config annotations ..?
I found this for example : https://stackoverflow.com/a/44923402/494659
I might as well use POJOs handle the lifecycle of it myself but I am sure there s a way to do it in Spring without re-inventing the wheel.
Thanks in advance.
The following method will run once the application starts, call the remote server and return a FleetConfiguration object which will be available throughout your app. The FleetConfiguration object will be a singleton and won't change.
#Bean
#EventListener(ApplicationReadyEvent.class)
public FleetConfiguration getFleetConfiguration(){
RestTemplate rest = new RestTemplate();
String url = "http://remoteserver/fleetConfiguration";
return rest.getForObject(url, FleetConfiguration.class);
}
The method should be declared in a #Configuration class or #Service class.
Ideally the call should test for the response code from the remote server and act accordingly.
Better approach is to use Spring Cloud Config to externalize every application's configuration here and it can be updated at runtime for any config change so no downtime either around same.

Spring Boot - Multiple context paths - have one redirect to the other?

We have a spring boot 2 app that is typically accessed behind a proxy server for all requests at, say, /blah/**.
We set up this app to use a context path called /blah to simplify this (so we no longer had to add /blah to all of our controller mappings, resource imports, etc)
server:
port: 9876
servlet:
context-path: /blah
This works fine, but there are some cases where people might access the app at root (/) that we'd like to handle.
It looks like I need to create a new servlet that listens to "/" and just redirects to "/blah"? What is the easiest way to set that up? I don't want any of my existing beans in this other context, just a simple controller or html file that performs a redirect.
This is what I think would be easiest way.
#RequestMapping("/*")
public void redirect(HttpServletResponse response) throws IOException{
response.sendRedirect("/blah");
}

Resources