I need to pass some parameters from javascript client to the Java endpoint in Tomcat.
With Tomcat you can annote your java class with #ServerEndpoint(value="/websocket") and then this class will be the endpoint for your websocket.
I.e. every time when a javascript calls new WebSocket("ws://localhost/websocket") your annoted class will be instantiated.
Now I want to pass a short string parameter (or int, does not really matter) from the javascript client side to my annoted class preferably during instantiation (i.e. in the constructor).
How shall I do this?
I have thought of putting it into the url, but then I would need to do something else instead of the annotation thus it does not feel too safe.
you could pass it as a header and Java tomcat endpoint which is exposing web socket has to read the header and set it to user properties map of endpoint config
Example
public class WsHandShakeInterceptor extends ServerEndpointConfig.Configurator {
#Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
super.modifyHandshake(sec, request, response);
setUserHeadersAsProps(sec, request);
}
private void setUserHeadersAsProps(ServerEndpointConfig sec, HandshakeRequest request) {
Map<String, List<String>> headers = request.getHeaders();
if (headers != null) {
List<String> userNameHeader = headers.get(HeadersConst.OIDC_CLAIM_USERNAME);
if (userNameHeader != null && !userNameHeader.isEmpty()) {
sec.getUserProperties().put(HeadersConst.X_FORWARDED_USER, userNameHeader.get(0));
}
}
}
}
Related
I am currently writing an application in Spring Boot 2.4.0 that is required to listen on multiple ports (3, to be specific - but might be 4 in the future). The idea is that each port makes a different API available for other services/apps to connect to it.
So, for a minimal working example, I'd say we have a SpringBootApp like this:
#SpringBootApplication
public class MultiportSpringBoot {
public static void main(String[] args)
{
SpringApplication.run(MultiportSpringBoot.class, args);
}
}
Now, I'd want to have this listening on 3 different ports, say 8080, 8081, and 8082. For all (!) requests to one of these ports, a specific controller should be "in charge". One of the reasons for this requirement is that one controller needs to handle a (regular) web frontend and another an API. In case an invalid request is received, the API-controller needs to give a different error message than the frontend should. Hence, the requirement given is a clear separation.
So I imagine multiple controllers for the different ports, such as:
#Controller
public class Controller8080
{
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView test8080()
{
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("test8080");
return modelAndView;
}
}
with similar controllers for the other ports:
#Controller
public class Controller8081
{
#RequestMapping(value = "/", method = RequestMethod.GET)
public ResponseEntity test8081()
{
JSONObject stuff = doSomeStuffForPort8081();
return new ResponseEntity<String>(stuff, HttpStatus.OK);
}
}
I hoped for an annotation similar to #RequestMapping to be able to match and fix the port numbers for the controllers, but this seems to be no option as no such annotation seems to exist.
Now, this topic seems to be a bit specific, which is probably why you don't find all too much info on the web. I found Starting Spring boot REST controller in two ports, but I can also only have ONE instance running. I looked at https://tech.asimio.net/2016/12/15/Configuring-Tomcat-to-Listen-on-Multiple-ports-using-Spring-Boot.html, but this is outdated for Spring Boot 2.4.0 and a bit bloated with JavaMelody examples.
Anyone can provide a minimum working example for a solution for this?
--
EDIT:
To clarify a bit more: I need multiple, separate RESTControllers that each handle requests on different ports. I.e. a request to domain.com:8080/ should be handled by a different controller than a request to domain.com:8081/.
As an example, consider the two following controllers that should handle requests on ports 8080 and 8081 respectively:
//controller for port 8080
#RestController
public class ControllerA
{
#GetMapping("/")
String helloA(HttpServletRequest request)
{
return "ControllerA at port " + request.getLocalPort();
}
}
and
//controller for port 8081
#RestController
public class ControllerB
{
#GetMapping("/")
String helloB(HttpServletRequest request)
{
return "ControllerB at port " + request.getLocalPort();
}
}
The tomcat class names changed a little bit so the link you provide has the old code but it is enough for the new code. Code below shows how you can open multiple ports in spring boot 2.4
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(additionalConnector());
return tomcat;
}
private Connector[] additionalConnector() {
if (!StringUtils.hasLength(this.additionalPorts)) {
return null;
}
String[] ports = this.additionalPorts.split(",");
List<Connector> result = new ArrayList<>();
for (String port : ports) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(Integer.valueOf(port));
result.add(connector);
}
return result.toArray(new Connector[]{});
}
And for responding to different ports with different controller you can implement the logic like check getLocalPort and respond it accordingly.
#GetMapping("/hello")
String hello(HttpServletRequest request) {
return "hello from " + request.getLocalPort();
}
Or you can write a logical controller in filter. example code below
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getLocalPort() == 8882 && req.getRequestURI().startsWith("/somefunction")) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
} else {
fc.doFilter(request, response);
}
}
You can find all running example here https://github.com/ozkanpakdil/spring-examples/tree/master/multiport
This is how it looks in my local
In order to have same path with different controllers you can use #RequestMapping("/controllerNO") on top of the classes(check), NO should be number 1 , 2, otherwise spring will complain "you have same path" and will give you this exception
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testController2' method
com.mascix.multiport.TestController2#hello(HttpServletRequest)
to {GET [/hello]}: There is already 'testController1' bean method
Because from design spring will allow only one path to correspond to one controller, after requestmapping you can change the filter as this. Good thing about reflection you will learn very different exceptions. java.lang.NoSuchMethodException or java.lang.IllegalArgumentException
Latest code how it works in my local
I must say this approach is not right and against the design of spring, in order to have different ports with different controllers, have multiple JVMs. If you mix the logic it will be harder for you to solve future problems and implement new features.
If you have to do it in one jvm, write a service layer and call the functions separately from one controller and write a logic like below
#GetMapping("/hello")
String hello(HttpServletRequest request) {
if (request.getLocalPort() == 8888) {
return service.hellofrom8888();
}
if (request.getLocalPort() == 8889) {
return service.hellofrom8889();
}
return "no repsonse ";
}
At least this will be easy to maintain and debug. Still looks "ugly" though :)
Özkan has already provided detailed information on how to get Tomcat to listen to multiple ports by supplying your own ServletWebServerFactory #Bean based on TomcatServletWebServerFactory.
As for the mapping, how about this approach:
Add a #RequestMapping("/8080") to your controller (methods keep their specific #RequestMapping)
#Controller
#RequestMapping("/8080")
public class Controller8080
{
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView test8080()
{
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("test8080");
return modelAndView;
}
}
Define your own RequestMappingHandlerMapping as
public class PortBasedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Override
protected HandlerMethod lookupHandlerMethod(final String lookupPath, final HttpServletRequest request) throws Exception {
return super.lookupHandlerMethod(request.getLocalPort() + lookupPath, request);
}
}
and use it by
#Bean
public WebMvcRegistrations webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrations() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new PortBasedRequestMappingHandlerMapping();
}
};
}
This will attempt to map a request to /foobar on port 8080 to /8080/foobar.
Another approach is by using org.springframework.web.servlet.mvc.condition.RequestCondition which I think is cleaner https://stackoverflow.com/a/69397870/6166627
We are working on a solution which is like this;
Request: (We receive the request via API call and send to third-party via a library we use)
OUR-Client --> OUR-API --> THIRD-PARTY
Response: (This response we receive from third-party asynchronously through a callback method given in the library we are using)
THIRD-PARTY --> OUR-CODE --> OUR-Client
Here is the below code and want to get rid of Thread.sleep() call and make use of the callback to provide response.
----- API Method -------------
#GetMapping
public ResponseEntity<String> getData(#RequestBody String requestId) throws SessionNotFound, InterruptedException {
dataService.get(requestId);
String msg;
long start = System.currentTimeMillis();
do {
// We want to get rid of this sleep() statement and some way to callback here as soon there is message.
Thread.sleep(30);
msg = clientApp.getRespnse(requestId);
} while(msg == null);
return ResponseEntity.ok(msg);
}
------- Service Class and Methods ---------------
#Service
public class DataService {
#Autowired
private ClientApp clientApp;
public void get(String requestId) throws SessionNotFound {
// This method is from the library we use. This only submits the request, response is received on different method.
send(requestId);
}
------- Component Class and Methods ---------------
#Component
public class ClientFixApp {
private Map<String, String> responseMap = new HashMap<>();
// This method is callback from the third party library, whenever there is response this method will get invoked and this message we need to send as response of the API call.
#Override
public void onResponse(String requestId)
throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
responseMap.put(msgId, jsonMsg);
}
public String getRespnse(String requestId) {
return responseMap.get(requestId);
}
}
DataService and ClientFixApp are flawed by design (the very fact it is 2 different classes while there must be one, speaks a lot). Truly asynchronous programs must allow to register user procedure as a callack, called when the I/O operation finished (successfully or not). ClientFixApp silently writes the result in a table, leaving for client no other option except polling.
You can use any existing asynchronous http library. For example, below is the code for the library java.net.http included in Java 11 and later. Other libraries have similar functionality.
public static CompletableFuture<HttpResponse<String>> doGet(String uri) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
}
public static void main(String[] args) {
CompletableFuture<HttpResponse<String>> future = doGet("https://postman-echo.com/get");
future.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}
How do I get the header and body of the current request from an application which called my Springboot application? I need to extract this information. Unfortunately this does not work. I tried to get the current request with this code sample (https://stackoverflow.com/a/26323545/5762515):
public static HttpServletRequest getCurrentHttpRequest(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
return request;
}
throw new IllegalArgumentException("Request must not be null!");
}
And then I tried to get the body
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) currentRequest;
String requestBody = new String(requestWrapper.getContentAsByteArray());
Can someone tell me what im doing wrong?
Thanks in advance
#RestController
public class SampleController {
#PostMapping("/RestEndpoint")
public ResponseEntity<?> sampleEndpoint(#RequestHeader Map<String, String> headers,#RequestBody Map<String,String> body) {
//Do something with header / body
return null;
}
}
If the application's are communicating through a rest endpoint I believe this would be the simplest solution. In spring you can add RequestHeader and RequestBody annotations to method arguments to have them setup to be used.
Of course you can map RequestBody directly to some POJO instead of using a map but just as an example.
Let me know if this is what you were looking for !
#TryHard, You're using spring boot then following way is more preferable for you,
#RestController
public class SampleController {
#RequestMapping("/get-header-data")
public ResponseEntity<?> sampleEndpoint(HttpServletRequest request) {
// request object comes with various in-built methods use as per your requirement.
request.getHeader("<key>");
}
}
you can get header with your code but need apply some changes.
private String getRequest() throws Exception {
RequestAttributes attribs = RequestContextHolder.getRequestAttributes();
if (attribs != null) {
HttpServletRequest request = ((ServletRequestAttributes) attribs).getRequest();
return request ;
}
throw new IllegalArgumentException("Request must not be null!");
}
after you can extract header info from request. For example if you want get Accept-Encoding
String headerEncoding = getRequest().getHeader("Accept-Encoding");
obliviusly you don't use this approce if not necessary.
If you want exract the body NOT use this solution
I have a controller mapping, where I pass 2 request params instead of 1. But when done like that Spring is not throwing any exception rather it is ignoring the additional request params.
For eg:
#RequestMapping(value="/test", method = RequestMethod.GET)
public ModelAndView eGiftActivation(#RequestParam("value") String value)
When I hit my app using /test.do?value=abcd it is working fine. But when I pass additional params like /test.do?value=abcd&extra=unwanted also it's working fine.
In this case I want Spring to restrict the second URL where additional params are passed.
How can I achieve this?
You could check it manually, like this:
#RequestMapping("/test")
public ModelAndView eGiftActivation(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
if (params.size() != 1 || !params.containsKey("value")) {
throw new RuntimeException("Extra parameters are present"); // or do redirect
}
...
}
I don't think it's possible (For Spring to prevent the request to flow to any controller's method). The reason being that:
Your controller handles request based on the URI path like, /app/hello/{name} rather than the request parameters
Request parameters are there to give extra set of meta-info for the request rather than endpoint specification of request.
But, if you wanted to restrict the URI path as such, you can use regex and you can avoid. I'm afraid it's not feasible and even the requirement for that never arose.
Programmatical Way:
Having said that, you can take HttpServletRequest for parameters and loop through the parameters to check for extra ones:
#RequestMapping(value="/test", method = RequestMethod.GET)
public Object eGiftActivation(#RequestParam("value") String value, HttpServletRequest request){
//check the request.getParameterMap() and throw custom exception if you need and handle using Exception handler or throw invalid request
return new ResponseEntity<String>(HttpStatus.SC_BAD_REQUEST);
}
I prefer handling these kind of validations (if required, what ever may be the reason) inside the Filter generically so that the requests will not even reach the Controller methods.
Please find the required code to handle inside the Filter as below (logic is almost similar to Slava).
#Component
public class InvalidParamsRequestFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Map<String, String[]> params = request.getParameterMap();
if (request.getRequestURI().contains("/test") && (params.size() != 1 || !params.containsKey("value"))) {
//Here, Send back the Error Response OR Redirect to Error Page
} else {
filterChain.doFilter(request, response);
}
}
}
I am developing REST services with two types.
before login no session token will be passed to HTTP header.
after login session token will be passed in each request.
I dont want to include #HeaderParam in each and every REST method. I want to intercept it first and based on that I want to check the validity of session. Please let me know
how I can intercept based on headers in RESTEasy
How to avoid intercepting few methods
Thanks.
I solved this problem using PreProcessInterceptor
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Securable {
String header() default "session-token";
}
#Provider
#ServerInterceptor
public class ValidationInterceptor implements PreProcessInterceptor, AcceptedByMethod {
#Context
private HttpServletRequest servletRequest;
#Override
public boolean accept(Class clazz, Method method) {
return method.isAnnotationPresent(Securable.class);
}
#Override
public ServerResponse preProcess(HttpRequest httpRequest, ResourceMethod resourceMethod) throws Failure,
WebApplicationException {
Securable securable = resourceMethod.getMethod().getAnnotation(Securable.class);
String headerValue = servletRequest.getHeader(securable.header());
if (headerValue == null){
return (ServerResponse)Response.status(Status.BAD_REQUEST).entity("Invalid Session").build();
}else{
// Validatation logic goes here
}
return null;
}
}
The annotation #Securable will be used on REST service which needs to be validated.
#Securable
#PUT
public Response updateUser(User user)
There are two approaches
Use JAX-RS interceptors - you have access to request object in the interceptor, so you can read headers
Use good old JavaServlet Filters - it is not a problem that you are using JAX-RS, you can filter REST requests as well. Similarly to interceptors, filters have access to request object, which has header information
In both cases you can check if HttpSession exists (request.getSession() method) and it has required attribute.
You can include/exclude requests filtered either in configuration or programatically in Java code, looking at request path.