I am using a UriComponentsBuilder to create my GET request endpoint and it works fine.
But I am trying to make it reusable such that I can pass in as many request params and
path variables as I want without limiting it. This is fine for path variables since I
can pass as many params as I want as a map inside buildAndExpand.
But how could I do it for request param too? Please advice.
This is what I am currently doing but this is not reusable.
public Map get(String a, String b, String c, String d, String e) {
String url = "domain.com/get/{a}/{b}";
String endPoint = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("c" ,c)
.queryParam("d" ,d)
.queryParam("e" ,e)
.buildAndExpand(
new HashMap<String, String>() {{
put("a", a);
put("b", b);
}}
).toUriString();
return restTemplate.getForEntity(endPoint, Map.class);
}
I want to instead pull out the endpoint creation as a separate method as follows.
private UriComponents getUriComponent(String url, Map<String, String> params) {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
return builder.buildAndExpand(params);
}
This would work fine for the path params a and b.
But how can I take in an argument here for b,c,e too which are query params.
Or some other suggestions to keep it dynamic. So I can pass in 5 queryParam for one request
and 10 queryParam for another without having to rewrite the UriComponentsBuilder each time.
Please note that preferably, I do not want to modify the url to like the following for this.
String url = "domain.com/get/{a}/{b}?c={c}&d={d}&e={e}";
So, using UriComponentsBuilder you can add new parameters to the URL.
String old = UriComponentsBuilder.newInstance()
.scheme("http")
.host("localhost")
.queryParam("a", "b")
.build()
.toUriString();
// probably other method
String res = UriComponentsBuilder
.fromHttpUrl(old)
.queryParam("new", "new")
.build()
.toUriString();
System.out.println(res); // output: http://localhost?a=b&new=new
If you need to add params stored in a Map, create an instance of org.springframework.util.MultiValueMap and call queryParams or replaceQueryParams depending on your needs to add parameters or replace parameters.
// I've changed method signature to accept Map<String, List<String>>, but you can leave just String and wrap String to a singleton list
private UriComponents getUriComponent(String url, Map<String, List<String>> params) {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParams(new LinkedMultiValueMap<>(params));
return builder.build();
}
Related
I'm trying to display the content of HashMap received from my controller, however I get an empty map.
this is my controller:
#PostMapping(value = "getAllUsers")
public HashMap<String, Object> getAllUsers(#RequestBody String s)
{
List<User> list = userRepository.getAll(s);
HashMap<String, Object> userList = new HashMap<String, Object>();
userList.put("list", list);
userList.put("total", list.size());
return userList;
}
and this is my angular code:
getUsers() {
this.userService.getUsers("canada").subscribe((data) => {
console.log(data); // this returns an empty object
data.forEach((value: string, key: string) => { // returns an error which is forEach is not a function
console.log(key, value);
});
this.userList = data; // empty
console.log(this.userList);
});
}
this is my api service:
let URL = "api/v1";
getUsers(country: String): Observable<any> {
return this.http.post(URL + "getAllUsers", country);
}
I want to know how to manipulate and iterate the HashMap received from my controller. Thank you in advance.
Typescript is assuming the response as an Object of type any instead of a Map, so Try this:
Object.entries(data).forEach((value: string, key: string) => {
console.log(key, value);
});
The point here is not HashMap iteration but your are getting empty response data even here.
this.userService.getUsers("canada").subscribe((data) => {
console.log(data); // this returns an empty object
Add #ResponseBody
#PostMapping(value = "getAllUsers")
#ResponseBody
public HashMap<String, Object> getAllUsers(#RequestBody String s)
Now coming to iteration once you get the response which is not empty there are multiple ways to iterate map.
Object.keys(data).forEach(key => {
console.log(key, data[key]);
});
=> Better to use Http convention of using get to fetch data over post. Although data will still be received in Post but prefer get().
You are performing a POST request but in your use case you should use the GET http method in order to retrieve a resource. POST http method is used to add a resource. You have to change your controller to use #GetMapping()
#GetMapping("/getAllUsers")
public HashMap<String, Object> getAllUsers(#PathVariable String s)
{
List<User> list = userRepository.getAll(s);
HashMap<String, Object> userList = new HashMap<String, Object>();
userList.put("list", list);
userList.put("total", list.size());
return userList;
}
You should also change your parameter to that controller from #RequestBody to #PathVariable
#PathVariable annotation is used to receive bind the URI variable to the method
parameter.
#RequestBody annotation binds the content sent in (POST / PUT)
request body with the annotated variable. (Generally, you can only use
#RequestBody for the requests which can have 'body' content e.g. POST
or PUT.)
You can read about HTTP Methods here.
I have a problem with passing parameters.
I have names in my income app and i want to update them by passing a parameters of new names from my rest app.
This is my code:
String url = "http://localhost:8084/rest/api/income/UpdateName/{oldName}/{newName}"; // the Url of the rest
Map<String, String> params = new HashMap<>();
params.put("oldName", oldName);
params.put("newName", newName);
Income income = new Income();
RestTemplate restTemplate = new RestTemplate();
restTemplate.put(url, income, params);
System.out.println(params);
}
This code is not working unfortunately, what can I do?
This is the put method in the controller :
#CrossOrigin
#GetMapping("/UpdateName/{oldName}/{clientName}") // view all incomes ..
public GeneralResponse viewAllIncome(#PathVariable("oldName") String oldName,#PathVariable("clientName") String clientName) {
return new GeneralResponse(incomeServiceTemplate.updateClientName(oldName,clientName));
}
The endpoint is GET not PUT, just append the parameters in the URL and call:
String url = "http://localhost:8084/rest/api/income/UpdateName/"+oldName+"/"+newName;
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForObject(url, Income.class);
Or there's a better way, use uriComponentsBuilder. It takes care of url encoding:
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("oldName", oldName)
.queryParam("newName", newName);
restTemplate.getForEntity(builder.build().encode().toUri(), Income.class);
I have a SpringBoot application which simply acts as a middleman. It receives an API request in JSON and forwards this to another server S by calling S's API with the exact same body.
I was exploring the solutions and came across a solution which involved the usage of RestTemplate and MultiValueMap. However, since the json body contains objects rather than simple String, I believe I have to create a DTO with corresponding POJO for the solution to work.
May I ask is the above the only solution, or there is a simple way to forward the request over and get back the response?
Even complex and nested JSON objects can be taken into a Map with key as String and value as Object.
I believe you should just use such a map as your request body and transfer the same to another api.
The middleman server can expose a endpoint that accepts a #RequestBody of Object and
HttpServletRequest then use RestTemplate to forward it to the remote server.
The middleman
#RestController
#RequestMapping("/middleman")
public class MiddleManRestController {
private RestTemplate restTemplate;
#PostConstruct
public void init() {
this.restTemplate = new RestTemplate();
this.restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(this.restTemplate.getRequestFactory()));
}
#RequestMapping(value = "/forward", method = RequestMethod.POST)
public ResponseEntity<Object> forward(#RequestBody Object object, HttpServletRequest request) throws RestClientException {
//setup the url and path
final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("Remote server URL").path("EnpointPath");
//add query params from previous request
addQueryParams(request, builder);
//specify the method
final RequestEntity.BodyBuilder requestBuilder = RequestEntity.method(HttpMethod.POST, builder.build().toUri());
//add headers from previous request
addHeaders(request, requestBuilder);
RequestEntity<Object> requestEntity = requestBuilder.body(object);
ParameterizedTypeReference<Object> returnType = new ParameterizedTypeReference<Object>() {};
//forward to the remote server
return this.restTemplate.exchange(requestEntity, returnType);
}
private void addHeaders(HttpServletRequest request, RequestEntity.BodyBuilder requestBuilder) {
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
requestBuilder.header(headerName, headerValue);
}
}
private void addQueryParams(HttpServletRequest request, UriComponentsBuilder builder) {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null) {
parameterMap.forEach((key, value) -> queryParams.addAll(key, Arrays.asList(value)));
}
builder.queryParams(queryParams);
}
}
I am trying to access the contents of an API and I need to send a URL using RestTemplate.
String url1 = "http://api.example.com/Search?key=52ddafbe3ee659bad97fcce7c53592916a6bfd73&term=&limit=100&sort={\"price\":\"desc\"}";
OutputPage page = restTemplate.getForObject(url1, OutputPage .class);
But, I am getting the following error.
Exception in thread "main" java.lang.IllegalArgumentException: Not enough variable values available to expand '"price"'
at org.springframework.web.util.UriComponents$VarArgsTemplateVariables.getValue(UriComponents.java:284)
at org.springframework.web.util.UriComponents.expandUriComponent(UriComponents.java:220)
at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:317)
at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:46)
at org.springframework.web.util.UriComponents.expand(UriComponents.java:162)
at org.springframework.web.util.UriTemplate.expand(UriTemplate.java:119)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:501)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:239)
at hello.Application.main(Application.java:26)
If I remove the sort criteria, it is working properly.
I need to parse the JSON using sort criteria.
Any help will be much appreciated.
Thanks
The root cause is that RestTemplate considers curly braces {...} in the given URL as a placeholder for URI variables and tries to replace them based on their name. For example
{pageSize}
would try to get a URI variable called pageSize. These URI variables are specified with some of the other overloaded getForObject methods. You haven't provided any, but your URL expects one, so the method throws an exception.
One solution is to make a String object containing the value
String sort = "{\"price\":\"desc\"}";
and provide a real URI variable in your URL
String url1 = "http://api.example.com/Search?key=52ddafbe3ee659bad97fcce7c53592916a6bfd73&term=&limit=100&sort={sort}";
You would call your getForObject() like so
OutputPage page = restTemplate.getForObject(url1, OutputPage.class, sort);
I strongly suggest you do not send any JSON in a request parameter of a GET request but rather send it in the body of a POST request.
If the solution suggested by sotirios-delimanolis is a little difficult to implement in a scenario, and if the URI string containing curly braces and other characters is guaranteed to be correct, it might be simpler to pass the encoded URI string to a method of RestTemplate that hits the ReST server.
The URI string can be built using UriComponentsBuilder.build(), encoded using UriComponents.encode(), and sent using RestTemplate.exchange() like this:
public ResponseEntity<Object> requestRestServer()
{
HttpEntity<?> entity = new HttpEntity<>(requestHeaders);
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(rawValidUrl)
.queryParams(
(LinkedMultiValueMap<String, String>) allRequestParams);
UriComponents uriComponents = builder.build().encode();
ResponseEntity<Object> responseEntity = restTemplate.exchange(uriComponents.toUri(), HttpMethod.GET,
entity, String.class);
return responseEntity;
}
Building, encoding, and extracting URI have been seperated out for clarity in the above code snippet.
You can URL encode the parameter values:
String url1 = "http://api.example.com/Search?key=52ddafbe3ee659bad97fcce7c53592916a6bfd73&term=&limit=100&sort=";
org.apache.commons.codec.net.URLCodec codec = new org.apache.commons.codec.net.URLCodec();
url1 = url1 + codec.encode("{\"price\":\"desc\"}");
OutputPage page = restTemplate.getForObject(url1, OutputPage.class);
You can set a specific UriTemplateHandler in your restTemplate. This handler would just ignore uriVariables :
UriTemplateHandler skipVariablePlaceHolderUriTemplateHandler = new UriTemplateHandler() {
#Override
public URI expand(String uriTemplate, Object... uriVariables) {
return retrieveURI(uriTemplate);
}
#Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return retrieveURI(uriTemplate);
}
private URI retrieveURI(String uriTemplate) {
return UriComponentsBuilder.fromUriString(uriTemplate).build().toUri();
}
};
restTemplate.setUriTemplateHandler(skipVariablePlaceHolderUriTemplateHandler);
You can encode url before using RestTemplate
URLEncoder.encode(data, StandardCharsets.UTF_8.toString());
You can simply append a variable key to the URL and give the value using the restTemplate.getForObject() method.
Example:
String url = "http://example.com/api?key=12345&sort={data}";
String data="{\"price\":\"desc\"}";
OutputPage page = restTemplate.getForObject(url, OutputPage.class, data);
I am trying to pass path param and query params in a URL but I am getting a weird error. Below is the code.
String url = "http://test.com/Services/rest/{id}/Identifier"
Map<String, String> params = new HashMap<String, String>();
params.put("id", "1234");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url)
.queryParam("name", "myName");
String uriBuilder = builder.build().encode().toUriString();
restTemplate.exchange(uriBuilder , HttpMethod.PUT, requestEntity,
class_p, params);
and my url is becoming http://test.com/Services/rest/%7Bid%7D/Identifier?name=myName
What should I do to make it work? I am expecting http://test.com/Services/rest/{id}/Identifier?name=myName so that params will add id to the url.
I would use buildAndExpand from UriComponentsBuilder to pass all types of URI parameters.
For example:
String url = "http://test.com/solarSystem/planets/{planet}/moons/{moon}";
// URI (URL) parameters
Map<String, String> urlParams = new HashMap<>();
urlParams.put("planet", "Mars");
urlParams.put("moon", "Phobos");
// Query parameters
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url)
// Add query parameter
.queryParam("firstName", "Mark")
.queryParam("lastName", "Watney");
System.out.println(builder.buildAndExpand(urlParams).toUri());
/**
* Console output:
* http://test.com/solarSystem/planets/Mars/moons/Phobos?firstName=Mark&lastName=Watney
*/
restTemplate.exchange(builder.buildAndExpand(urlParams).toUri() , HttpMethod.PUT,
requestEntity, class_p);
/**
* Log entry:
* org.springframework.web.client.RestTemplate Created PUT request for "http://test.com/solarSystem/planets/Mars/moons/Phobos?firstName=Mark&lastName=Watney"
*/
An issue with the answer from Michal Foksa is that it adds the query parameters first, and then expands the path variables. If query parameter contains parenthesis, e.g. {foobar}, this will cause an exception.
The safe way is to expand the path variables first, and then add the query parameters:
String url = "http://test.com/Services/rest/{id}/Identifier";
Map<String, String> params = new HashMap<String, String>();
params.put("id", "1234");
URI uri = UriComponentsBuilder.fromUriString(url)
.buildAndExpand(params)
.toUri();
uri = UriComponentsBuilder
.fromUri(uri)
.queryParam("name", "myName")
.build()
.toUri();
restTemplate.exchange(uri , HttpMethod.PUT, requestEntity, class_p);
One-liner using TestRestTemplate.exchange function with parameters map.
restTemplate.exchange("/someUrl?id={id}", HttpMethod.GET, reqEntity, respType, ["id": id])
The params map initialized like this is a groovy initializer*
String url = "http://test.com/Services/rest/{id}/Identifier";
Map<String, String> params = new HashMap<String, String>();
params.put("id", "1234");
URI uri = UriComponentsBuilder.fromUriString(url)
.buildAndExpand(params)
.toUri();
uri = UriComponentsBuilder
.fromUri(uri)
.queryParam("name", "myName")
.build()
.toUri();
restTemplate.exchange(uri , HttpMethod.PUT, requestEntity, class_p);
The safe way is to expand the path variables first, and then add the query parameters:
For me this resulted in duplicated encoding, e.g. a space was decoded to %2520 (space -> %20 -> %25).
I solved it by:
String url = "http://test.com/Services/rest/{id}/Identifier";
Map<String, String> params = new HashMap<String, String>();
params.put("id", "1234");
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(url);
uriComponentsBuilder.uriVariables(params);
Uri uri = uriComponentsBuilder.queryParam("name", "myName");
.build()
.toUri();
restTemplate.exchange(uri , HttpMethod.PUT, requestEntity, class_p);
Essentially I am using uriComponentsBuilder.uriVariables(params); to add path parameters. The documentation says:
... In contrast to UriComponents.expand(Map) or buildAndExpand(Map), this method is useful when you need to supply URI variables without building the UriComponents instance just yet, or perhaps pre-expand some shared default values such as host and port. ...
Source: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/UriComponentsBuilder.html#uriVariables-java.util.Map-
Since version 5.3 you can use this API to do this.
RequestEntity.post(urlString, urlParam1, urlParam2).headers(...).body(requestBody);
public static RequestEntity.BodyBuilder post(String uriTemplate,
Object... uriVariables)
Create an HTTP POST builder with the given string base uri template.
At the docs:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/RequestEntity.html#post-java.net.URI-
Or
template.exchange(..., uriVariables)
One simple way to do that is:
String url = "http://test.com/Services/rest/{id}/Identifier"
UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build();
uriComponents = uriComponents.expand(Collections.singletonMap("id", "1234"));
and then adds the query params.
Below is the working code, I had to pass two values in the respective placeholders while making the query parameter.
String queryParam = "Key=Project_{ProdjectCode}_IN_{AccountCode}"
Map<String, String> queryParamMap = new HashMap<>();
queryParamMap.put("ProjectCode","Project1");
queryParamMap.put("AccountCode","Account1");
UriComponents builder = UriComponentsBuilder.fromHttpUrl("http://myservice.com/accountsDetails").query(queryParam).buildAndExpand(queryParamMap);
restTemplate.exchange(builder.toUriString(), HttpMethod.GET,httpEntity,MyResponse.class);
Above code will make a GET call to url
http://myservice.com/accountsDetails?Key=Project_Project1_IN_Account1