I am currently working on a monitoring application using Spring Cloud Sleuth. Currently I try to collect as much information about my requests as possible.
To keep the approach as scalable as possible I use GenericFilterBeans and HandlerInterceptorAdapter to access information from the requests sent to the REST-API.
I am struggling with getting parameters of a REST-call where the parameters are mapped from the URL like in the following code snippet:
#RequestMapping(
value = {"/{service}/{route_id}/book", "/accounting-core-service/{service}/{route_id}/book"},
method = RequestMethod.GET)
#ResponseBody
public ModelAndView book(#PathVariable(value="service") String serviceName,
#PathVariable(value = "route_id") int routeId,
HttpServletResponse response) {
/*Do something*/
}
The question is not, whether it is good practice or not to write it like so. The question is whether there is an approach similar to Filter or Interceptor (or the proper use of them) to access those parameters.
A requirement is, that it can be applied easily to an application by adding very few lines of code. Annotating every Method call manually or manually inserting the code to write the parameters into the trace from within the method is not feasible for me.
If you need more information feel free to ask. I will provide you with all information you need to help me with my problem.
Although not officially supported (as it's not written in the reference documentation), Spring MVC holds that information as request attributes.
You could create your own HandlerInterceptor, ordered right after the Sleuth one, and get that information from the request like this:
// "/{service}/{route_id}/book"
String matchingPattern = (String) request
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
// "service" => "fooService", "route_id" => "42"
Map<String, String> templateVariables = (Map<String, String>) request
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Note, the template variables are already decoded at that point, which is probably what you want anyway.
You can create a Filter that intercept all the requests.
For each request you can retrieve also this informations:
query parameters
body of request
url
header parameters
You can save all this data as you need.
This is the best way you can do that.
If you need to group all urls like /{service}/{route_id}/book in a "family" of urls you can do that splitting the url and check if it is part of the family, but when a new family is added in your code you need to update the filter (or configure something in an external file or database).
Related
We have a proxy API set up where we accept any path after our proxy (i.e. /proxy/some/path) and then everything after the proxy path should be forwarded on to another API using RESTeasy (so it would call /some/path in my example).
This is necessary for us for several reasons including avoiding CORS issues and it works most of the time for what we need. However, some routes require query parameters some or all of the time, and because we are handling all routes the same, this causes a RESTeasy error today since the URL gets URL encoded to something like /some/path%3ffilter=value.
Our endpoint today looks like:
#Path("proxy/{someUri : .*}")
#GET
public Response proxyGet(
#PathParam("someUri") String someUri,
#HeaderParam(COOKIE) String cookieHeader,
#Context HttpServletRequest request
) {
return prepareProxyResponse(someUri, cookieHeader, request, null);
}
Our RESTeasy interface looks like:
#GET
#Path("/{requestPath}")
Response proxyGet(
#PathParam("requestPath") String requestPath,
#HeaderParam(COOKIE) String sessionCookie,
#HeaderParam(CONTENT_TYPE) String contentType
);
The code that calls this looks like:
return client.proxyGet(fullPath, cookieHeader, contentTypeHeader);
Prior to understanding this issue, we just took the request URI and added the query string to it so the requestPath above looked like some/path?filter=value which gets mangled. Removing the query string avoids that, however, it also means we cannot pass any query params through.
Is there a way to accept any query params and submit them to the other API using RESTeasy? I know we can break these out where needed to manually specify query params with #QueryParam but since there are many (and more getting added regularly) this will be a lot of effort. I think accomplishing what we are trying to do is not possible based on my research in other questions and the docs, but wanted to double-check before we embarked on a long process to convert. Thanks!
Hello i am looking if i can handle with only one RestController method multiple params...
with controllers method it could be done... but i couldnt find project with 2 like that.
#PostMapping(value ="upload")
public upload(#RequestParam MultipartFile file,#RequestParam List<String> myParams ){
some code here ....
return;
}
I am just wondering if is also a good practise ... having two deferent type of objects in same controller and if its possible,,, any idea????
Simple answer: Yes, that's possible.
But as you asked for good practice, here's some context:
It is very helpful to understand how HTTP actually transports data.
If your request uses GET as request method, parameters are added to the URL as a query string. That could look like this: http://example.com/index?param1=value1¶m2=value2
In this case, Spring maps the key-value pairs from the query string to your method arguments. But this will only work for text.
If you're using POST, the data is sent inside the request body. How that is encoded depends on the media type of your data. For example, the default media type application/x-www-form-urlencoded would encode the data to the same query string as above.
If you want to upload mixed-type form data like a file/blob along with some textual parameters, your data should be encoded with multipart/form-data.
As long as the request body contains a key-value format, Spring Boot will still be able to distinguish and map the parameters via #RequestParam (If the keys don't differ from your attribute names, you don't even need to assign a name to the value attribute).
I highly recommend you to take a look at the #RequestBody and #RequestPart annotations as i think it often is best practice to use a model class (DTO) for the whole request body (or rather the form, semantically), especially if there are a lot of parameters to process.
You will need to specify the names of the variables.
#PostMapping(value ="upload")
public upload(
#RequestParam(value = "file") MultipartFile file,
#RequestParam(value = "myParams") List<String> myParams
){
some code here ....
return;
}
I've been using a generic map for query parameters in a spring-boot (4.3.3.RELEASE) application, which has been live and taking traffic for a while now. This application has dozens of different endpoints and recently I've seen that a number requests result in errors due to improperly formatted query parameters.
I've been seeing a significant number of the requests are coming through like http://web.com/url/path?param1=1?param2=2. Unfortunately, I don't control or know why these urls are showing up, and they are all resulting in error pages for the users using them.
Can spring be customized to parse maps of query parameters using both '&' and '?' as separators?
I suspect this can be supported with a custom interceptor to inspect modify the incoming urls, but I wanted to know if there is an easier solution.
This is an example the one endpoint
#RequestMapping(value = "/url/path", method = {GET, POST})
public ResponseEntity<Void> handleRequest(
#RequestHeader HttpHeaders requestHeaders,
#RequestParam MultiValueMap<String, String> requestParams) throws Exception
{
....
}
I use org.springframework.security.web.csrf.CookieCsrfTokenRepository to secure my spring based web application from CSRF attacks. This enables all the Controller methods to be called only by passing a X-XSRF-TOKEN header or a _csrf request parameter.
So in order for me get the response of a GET URL in browser, I will have to write something like the below in browser address bar.
http://localhost:8080/api/someresource?_csrf=99e3b824-d0c9-409d-91ee-c7ccbdce313f&filter1=value1&filter2=value2&so=on
However, Some of these urls have filter mechanism based on the request parameters and unfortunately this extra _csrf parameter breaks that logic.
As I see it, this request parameter should be needed if the request had passed the csrf validation. But I couldn't do anything in the documentation to remove the _csrf request parameter on the application level.
At the moment, I do something like below.
#ResponseStatus(OK)
#RequestMapping(method = RequestMethod.GET, value = "/search/advanced")
#ResponseBody
public ResponseVO advancedSearch( #RequestParam MultiValueMap<String, String> queryParameters, Pageable pageable) {
queryParameters.remove(MyApplicatonConstants.CSRF_PARAM_NAME); //this line is the hack that I wrote
return doStuffAndGetFilteredData(queryParameters);
}
This implementation has its drawbacks.
I will have to change all 143 controller methods to have this one line on the top.
I have to remember to add this for new methods in future.
it's a cheap hack and there should be some better and cleaner way of doing it.
Is there a way I can acheive this without rewriting that one line again and again?
Note:
I fully understand that I can use CURL or Postman so I can pass X-XSRF-TOKEN in header. But it's not as quick as opening the URL in a new tab.
I'm trying to understand how to create and modify links in Spring HATEOAS.
For example, say I have two collections, one at api/users and another at api/event. I would like to associate a user api/user/56 with an event api/event/21. For arguments sake this is a many-to-many - a user may attend many events, an event may have many users.
As I understand it, the restful way of doing this is to use the URIs as primary keys, so I might post the following to api/user/56/events;
{
attends: "http://localhost:9090/api/event/21"
}
The endpoint then needs to be able to parse that URL and extract the ID (in this case 21) and the controller (EventController.class) so that I can persist this.
Question 1: Is this the correct way of dealing with relationships in Spring Hateoas in terms of the REST API?
Question 2: How can I resolve this url in a controller to a usable handle on the data (for example a reference to the appropriate controller/method, a primary key, etc)
Research
RestTemplate can be used to request the data from the controller inside the request mapped method, like so;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<EventResource> response = restTemplate.getForEntity(attendsUrl, EventResource.class);
EventResource eventResource = response.getBody();
However I don't believe that eventResource should return an Id field as part of the data - it's not very restful and this would be exposed on the API. One approach is to have a parameter "includePK=true" but again this doesn't feel right - it's just hiding the problem. Moreover the idea of the server making requests to it's own API in this manner seems circuitous.
Update
There is an open question for this here https://github.com/spring-projects/spring-hateoas/issues/292. Based loosely on some of the comments (by user kevinconaway) from that issue I have made a quick util class that offers an easy solution here: SpringHateoasUtils. The solution boils down to;
String mapping = DISCOVERER.getMapping(targetClass, targetMethod);
UriTemplate template = new UriTemplate(mapping);
//values is key/value map of parameters that the referenced method accepts
Map<String, String> values = uriTemplate.match(uri);
SpringHateoasUtils makes this slightly nicer but it still feels like it should be a feature. I'll seek to get something in the spring code for this - when it's clear what is happening with this I'll answer this question.
Look at the answer here:
POSTing a #OneToMany sub-resource association in Spring Data REST
Question 1) Yes this is how you post links/relations. With URIs.
Question 2) The URI of the resource actually IS its ID from the client's perspective. The server internally automatically resolves this URI into the actual model instance with
org.springframework.data.rest.core.UriToEntityConverter.convert(...)