I have the below code as my restful service operation.
#GET
#UnitOfWork
#Timed(name = "get-requests")
#Path("/{referenceId}")
public Response get(#Auth #ApiParam(access = "internal") UserPrincipal user,
#ApiParam(name = "id", value = "reference ID", required = true)
#PathParam("referenceId") String id) {
return Response.ok(id).build();
}
However, I noticed if I pass in m1234;5678, I get only m1234 returned. I tried #Path("/{referenceId:.*}"), but it doesn't work.
I also tried use #Encode at the top of the method to make sure the url is not decoded and then try to replace %3B with ";" in the code. But it seems not working also.
Please note that I cannot use Spring framework. Thanks.
The ; denotes a matrix parameter. Use #MatrixParam to get its value.
See also the answers to this question: URL matrix parameters vs. request parameters
Edit: The key of the matrix parameter would be 5678, the value would be null.
There is a way to get achieve what you want by using PathSegment as the type of the parameter instead of String:
#PathParam("referenceId) PathSegment id
In the body of the method, you can use
String idValue = id.getPath();
to get m1234;5678.
Related
I am new to Java and Vertx and I have a query string with the following format:
GET /examples/1/data?date_1[gt]=2021-09-28&date_1[lt]=2021-10-28
Here I have this date_1 parameter which is within a certain range. I have been using HttpServerRequest class to extract simple parameters like integers but not sure how to proceed with these kind of range parameters.
With the simple parameters, I can do something like:
String param = request.getParam(paramName);
paramAsInteger = Integer.valueOf(paramAsString);
However, confused as to how to deal with the gt and lt options and the fact that we have same parameter twice.
You say that you have difficulties parsing out these tokens. Here's how you can handle this.
The first thing to understand is that the parameter name is NOT "date1"
There are actually two parameters here
2.1. "date_1[gt]" with a value of "2021-09-28"
2.2. "date_1[lt]" with a value of "2021-10-28"
This is because in the URI parameter definition everything before the "=" sign is the parameter name and everything after is the parameter value.
You can just do
String dateAsString = request.getParam("date1[gt]");
paramAsInteger = toDate(dateAsString)
To implement the toDate() function read this simple article how to convert a string object into a data object using a standard library
(link)
Vert.x will treat these parameters as two separate ones. So RoutingContext#queryParam("date_1[gt]") will only give you the value for [gt]. If you want the value for [lt] you need to get that separately.
That being said, you can move this tedious logic into an extra handler and store the values in the RoutingContext. Something like this might be easier:
private void extractDates(RoutingContext ctx) {
var startDate = ctx.queryParam("date_1[gt]");
var endDate = ctx.queryParam("date_1[lt]");
var parsedStartDate = DateTimeFormatter.ISO_LOCAL_DATE.parse(startDate.get(0));
var parsedEndDate = DateTimeFormatter.ISO_LOCAL_DATE.parse(endDate.get(0));
// things we put in the context here can be retrieved by later handlers
ctx.put("startDate", parsedStartDate);
ctx.put("endDate", parsedEndDate);
ctx.next();
}
Then, in your actual handler you can access the two dates as follows:
router.get("/date")
.handler(this::extractDates)
.handler(ctx -> {
var responseBody = ctx.get("startDate") + " - " + ctx.get("endDate");
ctx.end(responseBody);
});
This allows you to keep your actual business logic concise.
I have some problem with slashes in variable thats hould be send in url.
For example id could be like this:
ID: /gfdge1/DkxA8P+jYw43
URL: localhost:8080/find-user//gfdge1/DkxA8P+jYw43
"error": "Internal Server Error",
"exception": "org.springframework.security.web.firewall.RequestRejectedException",
"message": "The request was rejected because the URL was not normalized.",
because of this slash on first place it make problem
Before that i had problems with these slash in middle of ID but I've solved that with this code:
#RequestMapping(name = "Get user data by ID", value = "/find-user/{userId}/**", method = RequestMethod.GET)
#ResponseBody
public User getUserData(#PathVariable String userId, HttpServletRequest request) {
final String path =
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
final String bestMatchingPattern =
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
String arguments = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path);
String id="";
if (null != arguments && !arguments.isEmpty()) {
id = userId + '/' + arguments;
} else {
id = userId;
}
return userService.getUserData(id);
}
but this doesn't work for this case when slash is on first place.
I've also try to user RequestParam instead of PathVariable, but it have problems with some special characters for example when I user RequestParam it replace '+' with empty space,...
Does anyone can help me how to solve this problem?
Its an inherent issue with using Strings as path variables, it's not an issue with your code but how the HTTP request is interpreted so you can't do anything in your code to make this work.
You do have some options though:
Ensure the values you use cannot be created with special characters such as "/"
Avoid using Strings in path variables completely.
I lean more towards 2 as maintaining 1 for all possible problem characters/strings is pretty messy and unnecessary.
To do 2 correctly you should consider having all your REST getters finding their related entities by a numeric ID only e.g.
localhost:8080/find-user/3
If you need to add additional search parameters e.g. username in your case then you should use something like QueryDSL to create a predicate of search parameters which are passed as query parameters instead of path variables e.g.:
localhost:8080/find-user?username=/gfdge1/DkxA8P+jYw43
I have an endpoint I created using spring.io. My GetMapping declaration can be seen below
#ApiOperation(
value = "Returns a pageable list of CustomerInvoiceProducts for an array of CustomerInvoices.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public Page<CustomerInvoiceProduct> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#PathVariable String[] invoiceIds,
Pageable pageInfo) {
//Do something fun here
for (string i: invoiceIds){
//invoiceIds is always empty
}
}
Here is how I am calling the url from postman and passing the data.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
{
"invoiceIds": [
"123456",
"234566",
"343939"
]
}
My string array for invoiceIds is always empty in the for loop Nothing gets passed to the array. What am I doing wrong?
The mapping you are using is this:
customers/{customerId}/getProductsForInvoices/{invoiceIds}
Both customerId and invoiceIds are Path variables here.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
The call you are making contains customerId but no invoiceIds. Either you can pass the list in place of invoiceIds as String and read it as a String and then create a List by breaking up the List - which will be a bad practice.
Other way is to change your path variable - invoiceId to RequestBody.
Generally, Path Variables are used for single id or say navigating through some structured data. When you want to deal in a group of ids, the recommended practice would be to pass them as RequestBody in a Post method call rather than a Get method call.
Sample code snippet for REST API (post calls):
Here, say you are trying to pass Employee object to the POST call, the REST API will look like something below
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
//.. perform some operation on newEmployee
}
This link will give you a better understanding of using RequestBody and PathVariables -
https://javarevisited.blogspot.com/2017/10/differences-between-requestparam-and-pathvariable-annotations-spring-mvc.html
https://spring.io/guides/tutorials/rest/
I am trying to write a Spring REST Controller getting an array of strings as input parameter of a HTTP GET request.
The problem arises when in the GET request, in some of the strings of the array, I use special characters like commas ,, blank spaces or forward slash /, no matter if I URL encode the query part of the URL HTTP GET request.
That means that the string "1/4 cup ricotta, yogurt" (edit which needs to be considered as a unique ingredient contained as a string element of the input array) in either this format:
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1/4 cup ricotta, yogurt
This format (please note the blank spaces encoded as + plus, rather than the hex code):
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1%2F4+cup+ricotta%2C+yogurt
Or this format (please note the blank space encoded as hex code %20):
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1%2F4%20cup%20ricotta%2C%20yogurt
is not rendered properly.
The system does not recognize the input string as one single element of the array.
In the 2nd and 3rd case the system splits the input string on the comma and returns an array of 2 elements rather than 1 element. I am expecting 1 element here.
The relevant code for the controller is:
#RequestMapping(
value = "/parseThis",
params = {
"language",
"ingredients"
}, method = RequestMethod.GET, headers = HttpHeaders.ACCEPT + "=" + MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public HttpEntity<CustomOutputObject> parseThis(
#RequestParam String language,
#RequestParam String[] ingredients){
try {
CustomOutputObject responseFullData = parsingService.parseThis(ingredients, language);
return new ResponseEntity<>(responseFullData, HttpStatus.OK);
} catch (Exception e) {
// TODO
}
}
I need to perform HTTP GET request against this Spring controller, that's a requirement (so no HTTP POST can be used here).
Edit 1:
If I add HttpServletRequest request to the signature of the method in the controller, then I add a log statement like log.debug("The query string is: '" + request.getQueryString() + "'"); then I am seeing in the log a line like The query string is: '&language=en&ingredients=1%2F4+cup+ricotta%2C+yogurt' (So still URL encoded).
Edit 2:
On the other hand if I add WebRequest request to the signature of the method, the the log as log.debug("The query string is: '" + request.getParameter("ingredients") + "'"); then I am getting a string in the log as The query string is: '1/4 cup ricotta, yogurt' (So URL decoded).
I am using Apache Tomcat as a server.
Is there any filter or something I need to add/review to the Spring/webapp configuration files?
Edit 3:
The main problem is in the interpretation of a comma:
#ResponseBody
#RequestMapping(value="test", method=RequestMethod.GET)
public String renderTest(#RequestParam("test") String[] test) {
return test.length + ": " + Arrays.toString(test);
// /app/test?test=foo,bar => 2: [foo, bar]
// /app/test?test=foo,bar&test=baz => 2: [foo,bar, baz]
}
Can this behavior be prevented?
The path of a request parameter to your method argument goes through parameter value extraction and then parameter value conversion. Now what happens is:
Extraction:
The parameter is extracted as a single String value. This is probably to allow simple attributes to be passed as simple string values for later value conversion.
Conversion:
Spring uses ConversionService for the value conversion. In its default setup StringToArrayConverter is used, which unfortunately handles the string as comma delimited list.
What to do:
You are pretty much screwed with the way Spring handles single valued request parameters. So I would do the binding manually:
// Method annotations
public HttpEntity<CustomOutputObject> handlerMethod(WebRequest request) {
String[] ingredients = request.getParameterValues("ingredients");
// Do other stuff
}
You can also check what Spring guys have to say about this.. and the related SO question.
Well, you could register a custom conversion service (from this SO answer), but that seems like a lot of work. :) If it were me, I would ignore the declaration the #RequestParam in the method signature and parse the value using the incoming request object.
May I suggest you try the following format:
ingredients=egg&ingredients=milk&ingredients=butter
Appending &ingredients to the end will handle the case where the array only has a single value.
ingredients=egg&ingredients=milk&ingredients=butter&ingredients
ingredients=milk,skimmed&ingredients
The extra entry would need to be removed from the array, using a List<String> would make this easier.
Alternatively if you are trying to implement a REST controller to pipe straight into a database with spring-data-jpa, you should take a look at spring-data-rest. Here is an example.
You basically annotate your repository with #RepositoryRestResource and spring does the rest :)
A solution from here
public String get(WebRequest req) {
String[] ingredients = req.getParameterValues("ingredients");
for(String ingredient:ingredients ) {
System.out.println(ingredient);
}
...
}
This works for the case when you have a single ingredient containing commas
I am wondering how spring split each parameters of a http request.
By example i have this method definition :
#RequestMapping(value = "/search.do", method = RequestMethod.GET)
public String searchGet(ModelMap model,
#RequestParam(value = "memberId", required = false) Integer memberId,
#RequestParam(value = "member", required = false) String member) {...}
and i use this url :
/search.do?member=T&O=
i get member = T and not member =T&O=
The request params are limited to only memberId and member.
Can i configure spring for solving this problem ?
Some characters in URLs have a special meaning. If they are supposed to be part of a value they need to be escaped.
If your value is T&O= then it needs to be changed to T%26O%3D
Looking at your controller code, your URL should have been
/search.do?memberId=T&member=
Then request parameter names will get mapped correctly.
If you wish to use same URL as mentioned in your question, change controller code to :
public String searchGet(ModelMap model,
#RequestParam(value = "O", required = false) Integer memberId,
#RequestParam(value = "member", required = false) String member) {...}
& is used to seperate request parameters.
URL contain request param name and value in following format
http://host_port_and_url?name1=value1&name2=value2&so_on
In your case
/search.do?member=T&O=
Name -> Value
member -> T
O -> (No value- Blank)
So you are getting correct values