Problem in sending HTTP PUT request using Apache HTTP client & Spring REST - java

I have developed a simple REST API which serves a simple HTTP PUT request using Spring Boot 2.3.1.RELEASE & Oracle JDK 14. Below is the server side REST endpoint:
#Controller
#RequestMapping("/api/documents")
public class DocumentController {
#PutMapping(value = "/setCurrentTenant", consumes = "multipart/form-data")
public ResponseEntity<?> setCurrentTenant(
#RequestParam(value = "documentId", required = false) Long documentId,
#RequestParam("tenantId") Long tenantId) {
return documentService.setCurrentTenant(documentId, tenantId);
}
}
And the client side code:
HttpPut putRequest = new HttpPut("http://localhost:8080/api/document/setCurrentTenant");
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
builder.addTextBody("documentId", "1");
builder.addTextBody("tenantId", "10");
HttpEntity setCurrentTenantEntity = entityBuilder.build();
putRequest.setEntity(setCurrentTenantEntity);
httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(putRequest);
I tried to run the code and everything is okay but when the client request is sent to the server, Spring would log an exception like this:
Resolved [org.springframework.web.bind.MissingServletRequestParameterException:
Required Long parameter 'tenantId' is not present]
Am I missing something?

You should send tenantId as request parameter like
http://localhost:8080/api/document/setCurrentTenant?tenantId=10
I'm concern that why are you consume a multipart/form-data while you're not sending any file/binary data
class TenantRequest {
Long tenantId;
Long documentId;
//seter, getter
}
#PutMapping(value = "/setCurrentTenant", consumes = "multipart/form-data")
public ResponseEntity<?> setCurrentTenant(TenantRequest request) {
// TODO
}
or
#PutMapping(value = "/setCurrentTenant", consumes = "multipart/form-data")
public ResponseEntity<?> setCurrentTenant(#ModelAttribute TenantRequest request) {
// TODO
}

Related

How to fix ServerCode 302 with my restful springboot server

I just made a new Springboot Project and every time I want so implement the Controller for the Mapping from my API into my Database I get the statuscode 302.
The class I want to put into the db is a Movie, here is the Moviecontroller
#RestController()
#RequestMapping(path = "movie")
public class MovieController {
private final MovieService movieService;
#Autowired
public MovieController(MovieService movieService){ this.movieService = movieService; }
#GetMapping("getmovie")
public List<Movie> getMovie(){
return movieService.getMovie();
}
#PostMapping(path = "postmovie")
public void addNewMovie(#RequestBody Movie movie){
movieService.addNewMovie(movie);
}
}
The Class which handles the API has this method in it which is supposed to handle a Movie Json and send it via http to the Controller. Since I want to post the minimum code necessary for this Problem I do not post the complete class. The Route is...
#Route(value = "addmovie", layout = MainLayout.class)
public void saveMovieInDatabase(int movieId, String title, String posterSrc, int releaseDate,int length) throws IOException {
String movieString = new ObjectMapper().writeValueAsString(new Movie(movieId,title,posterSrc,releaseDate,length));
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost("http://localhost:8080/movie/postmovie");
post.setEntity(new StringEntity(movieString));
post.setHeader("Accept","application/json");
post.setHeader("Content-type", "application/json");
CloseableHttpResponse response = client.execute(post);
System.out.println(response.getStatusLine().getStatusCode());
}

GET Request Works in Postman but not with SpringBoot RestTemplate

I have a two Spring Boot application. One is a rest client that makes rest calls. Another app that has only Rest endpoint.
When the Rest client hits the rest endpoint, it fails.
This is the code used to hit the rest endpoint:
HttpHeaders headers = new HttpHeaders();
headers.set(ACCEPT, APPLICATION_JSON);
headers.set(CONTENT_TYPE, APPLICATION_JSON);
HttpEntity entity = new HttpEntity(headers);
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(url)
.queryParam(EMAIL, URLEncoder.encode(email, "UTF-8"))
.queryParam(ADDRESS, URLEncoder.encode(address, "UTF-8"));
ResponseEntity<Address> response =
commonRestTemplate
.exchange(builder.toUriString(), 
HttpMethod.GET, entity, Address.class);
This is the rest endpoint the client is trying to hit:
#RestController
#AllArgsConstructor
public class AddressController {
private final RestTemplate commonRestTemplate;
// constructor and other rest endpoints
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<Address> getAddress(#RequestParam String email, #RequestParam String address) {
try {
// do soemthing
} catch (RuntimeException e)
{
LOGGER.error(e.getMessage(), e);
return status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
This is the error I'm seeing in the app with the rest endpoint:
2020-03-26 16:33:53.619 WARN 9 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'address' is not present]
2020-03-26 16:50:02.691 ERROR 9 --- [nio-8080-exec-9] u.c.h.s.s.controller.AddressController : Key may not be empty
Why does the Rest call work with Postman but not my rest client?
I've also tried with and without encoding the special characters in the rest client with no luck. I can't seem to see what I am missing
Try below changes
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(url)
.queryParam("email", URLEncoder.encode(email, "UTF-8"))
.queryParam("address", URLEncoder.encode(address, "UTF-8"));
#RestController
#AllArgsConstructor
public class AddressController {
private final RestTemplate commonRestTemplate;
// constructor and other rest endpoints
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<Address> getAddress(#RequestParam("email") String email, #RequestParam("address") String address) {
try {
// do soemthing
} catch (RuntimeException e)
{
LOGGER.error(e.getMessage(), e);
return status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
I had this problem too. It was solved when I used uri instead string in exchange method.
ResponseEntity<String> responseEntity = null;
Map<String, String> map = generate map to keep key and value of necessaryparameters;
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl("SERVICE_URL");
map.forEach((k, v) -> {
uriComponentsBuilder.queryParam(k, v);
});
URI uri = uriComponentsBuilder.build(false).encode("windows-1256").toUri();
responseEntity = new RestTemplate().exchange(uri, HttpMethod.POST, request, String.class);
can be 2 issues:
static ADDRESS is properly defined and referring to "address".
another one, address value is not null. print address value before calling restTemplate.

Required String parameter is not present

I want to send post request from my Android apps to Spring Boot. I use okhttp to send the HTTP post request in JSON. The code is like this:
Every time I send post request using the Android request I got 400 bad request parameter 'name' is not present","path":"/newcustomer". But when I use postman it works.
Java
----------------------------------------------------------------
Log.d("okhttphandleruserreg", "called");
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
JSONObject jsonObject = new JSONObject();
try{
jsonObject.put("name", name);
jsonObject.put("email", email);
jsonObject.put("username", username);
jsonObject.put("password", password);
jsonObject.put("month", month);
jsonObject.put("dayOfMonth", dayOfMonth);
} catch (JSONException e) {
e.printStackTrace();
}
RequestBody body = RequestBody.create(JSON, jsonObject.toString());
Request.Builder builder = new Request.Builder();
builder.url(params[0]);
builder.post(body);
Request request = builder.build();
Spring Boot
-----------------------------------------------------------------
#RequestMapping(value = "/newcustomer", method= RequestMethod.POST)
public Customer newCust(#RequestParam(value="name") String name,
#RequestParam(value="email") String email,
#RequestParam(value="username") String username,
#RequestParam(value="password") String password,
#RequestParam(value="month") int month,
#RequestParam(value="dayOfMonth") int dayOfMonth
)
The way you have implemented your back-end /newcustomer API suggests you are expecting the request payload to be raw request params within the request form data.
Assuming the server side API is your contract, thus should remain as is, your client code should be updated as follows:
Log.d("okhttphandleruserreg", "called");
// here you create your request body as as a Form one
RequestBody formBody = new FormBody.Builder()
.add("name", "test")
.add("email", "test#domain.com")
.add("username", "test")
.add("username", "test")
.add("month", "january")
.add("dayOfMonth", "1")
.build();
Request request = new Request.Builder()
.url(params[0])
.post(formBody)
.build();
// call your request
You are using Request Params in Spring Boot but whereas in Android code you sending that as Request Body.
Please change any one of the above. Better if you use RequestBody in both places.
class Customer
{
String name:
String email:
String username;
String password;
int month;
int dayofthemonth;
//getter and setters
}
public Customer newCust(#RequestBody Customer newcustomer)
{
}

How can I make a Put rest call along with POJO using RestTemplate

How can I make a PUT request to a rest service using RestTemplate, so that I get a response also.
The rest service I have to call is:
#RequestMapping(value = /forgotpassword, method = RequestMethod.PUT, produces = "application/json")
public SuccessResponse resetUserPassword(#RequestBody ResetPasswordDTO resetPasswordDTO) throws GenericException {
logger.info("--->reset Password");
return new SuccessResponse(userservice.resetUserPassword(resetPasswordDTO));
}
I need to send one POJO also which has two String properties.
The method put of RestTempalte in (Spring)[https://spring.io/] has no return,so if your want get response from server,please try use POST method.I modify your code like this:
In server side:
#RequestMapping(value = "/forgotpassword", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<SuccessResponse> resetUserPassword(#RequestBody ResetPasswordDTO resetPasswordDTO) throws Exception {
log.info("--->reset Password");
SuccessResponse response = new SuccessResponse();
response.setName(resetPasswordDTO.getUsername());
response.setMessage("success");
return new ResponseEntity<SuccessResponse>(response, HttpStatus.OK);
}
In client side you can use RestTemplate do a request:
ResetPasswordDTO request = new ResetPasswordDTO();
request.setPasswork("Huawei#123");
request.setUsername("c00382802");
ResponseEntity<SuccessResponse> response =template.postForEntity("http://localhost:8080//forgotpassword",request,SuccessResponse.class);
System.out.println(response.getBody().toString());
More info you can get from (Spring)[https://spring.io/]
For PUT use RestTemplate.exchange() method
Example
MyJaxbRequestDataObjectrequest = createMyJaxbRequestDataObject();
Map<String, String> uriArguments= createUriArguments();
String url = restBaseUrl + "/myputservice/{usertId}?servicekey={servicekey}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<MyJaxbRequestDataObject> entity = new HttpEntity<MyJaxbRequestDataObject>(request, headers);
ResponseEntity<MyJaxbResponseDataObject> responseWrapper = shogunRestTemplate.exchange(url, HttpMethod.PUT, entity, MyJaxbResponseDataObject.class, uriArguments);
MyJaxbResponseDataObjectresponse = responseWrapper.getBody();

spring mvc rest service redirect / forward / proxy

I have build a web application using spring mvc framework to publish REST services.
For example:
#Controller
#RequestMapping("/movie")
public class MovieController {
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public #ResponseBody Movie getMovie(#PathVariable String id, #RequestBody user) {
return dataProvider.getMovieById(user,id);
}
Now I need to deploy my application but I have the following problem:
The clients do not have direct access to the computer on which the application resides (There is a firewall). Therefore I need a redirection layer on a proxy machine (accessible by the clients) which calls the actual rest service.
I tried making a new call using RestTemplate:
For Example:
#Controller
#RequestMapping("/movieProxy")
public class MovieProxyController {
private String address= "http://xxx.xxx.xxx.xxx:xx/MyApp";
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public #ResponseBody Movie getMovie(#PathVariable String id,#RequestBody user,final HttpServletResponse response,final HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
return restTemplate.exchange( address+ request.getPathInfo(), request.getMethod(), new HttpEntity<T>(user, headers), Movie.class);
}
This is ok but I need to rewrite each method in the controller to use the resttemplate. Also, this causes redundant serialization/deserialization on the proxy machine.
I tried writing a generic function using restemplate, but it did not work out:
#Controller
#RequestMapping("/movieProxy")
public class MovieProxyController {
private String address= "http://xxx.xxx.xxx.xxx:xx/MyApp";
#RequestMapping(value = "/**")
public ? redirect(final HttpServletResponse response,final HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
return restTemplate.exchange( address+ request.getPathInfo(), request.getMethod(), ? , ?);
}
I could not find a method of resttemplate which works with request and response objects.
I also tried spring redirect and forward. But redirect does not change the request's client ip address so i think it is useless in this case. I could not forward to another URL either.
Is there a more appropriate way to achieve this?
You can mirror/proxy all requests with this:
private String server = "localhost";
private int port = 8080;
#RequestMapping("/**")
#ResponseBody
public String mirrorRest(#RequestBody String body, HttpMethod method, HttpServletRequest request) throws URISyntaxException
{
URI uri = new URI("http", null, server, port, request.getRequestURI(), request.getQueryString(), null);
ResponseEntity<String> responseEntity =
restTemplate.exchange(uri, method, new HttpEntity<String>(body), String.class);
return responseEntity.getBody();
}
This will not mirror any headers.
Here's my modified version of the original answer, which differs in four points:
It does not make the request body mandatory, and as such does not let GET requests fail.
It copies all headers present in the original request. If you are using another proxy/web server, this can cause issues due to content length/gzip compression. Limit the headers to the ones you really need.
It does not reencode the query params or the path. We expect them to be encoded anyway. Note that other parts of your URL might also be encoded. If that is the case for you, leverage the full potential of UriComponentsBuilder.
It does return error codes from the server properly.
#RequestMapping("/**")
public ResponseEntity mirrorRest(#RequestBody(required = false) String body,
HttpMethod method, HttpServletRequest request, HttpServletResponse response)
throws URISyntaxException {
String requestUrl = request.getRequestURI();
URI uri = new URI("http", null, server, port, null, null, null);
uri = UriComponentsBuilder.fromUri(uri)
.path(requestUrl)
.query(request.getQueryString())
.build(true).toUri();
HttpHeaders headers = new HttpHeaders();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.set(headerName, request.getHeader(headerName));
}
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
try {
return restTemplate.exchange(uri, method, httpEntity, String.class);
} catch(HttpStatusCodeException e) {
return ResponseEntity.status(e.getRawStatusCode())
.headers(e.getResponseHeaders())
.body(e.getResponseBodyAsString());
}
}
You can use Netflix Zuul to route requests coming to a spring application to another spring application.
Let's say you have two application: 1.songs-app, 2.api-gateway
In the api-gateway application, first add the zuul dependecy, then you can simply define your routing rule in application.yml as follows:
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>LATEST</version>
</dependency>
application.yml
server:
port: 8080
zuul:
routes:
foos:
path: /api/songs/**
url: http://localhost:8081/songs/
and lastly run the api-gateway application like:
#EnableZuulProxy
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now, the gateway will route all the /api/songs/ requests to http://localhost:8081/songs/.
A working example is here: https://github.com/muatik/spring-playground/tree/master/spring-api-gateway
Another resource: http://www.baeldung.com/spring-rest-with-zuul-proxy
#derkoe has posted a great answer that helped me a lot!
Trying this in 2021, I was able to improve on it a little:
You don't need #ResponseBody if your class is a #RestController
#RequestBody(required = false) allows for requests without a body (e.g. GET)
https and port 443 for those ssl encrypted endpoints (if your server serves https on port 443)
If you return the entire responseEntity instead of only the body, you also get the headers and response code.
Example of added (optional) headers, e.g. headers.put("Authorization", Arrays.asList(String[] { "Bearer 234asdf234"})
Exception handling (catches and forwards HttpStatuses like 404 instead of throwing a 500 Server Error)
private String server = "localhost";
private int port = 443;
#Autowired
MultiValueMap<String, String> headers;
#Autowired
RestTemplate restTemplate;
#RequestMapping("/**")
public ResponseEntity<String> mirrorRest(#RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request) throws URISyntaxException
{
URI uri = new URI("https", null, server, port, request.getRequestURI(), request.getQueryString(), null);
HttpEntity<String> entity = new HttpEntity<>(body, headers);
try {
ResponseEntity<String> responseEntity =
restTemplate.exchange(uri, method, entity, String.class);
return responseEntity;
} catch (HttpClientErrorException ex) {
return ResponseEntity
.status(ex.getStatusCode())
.headers(ex.getResponseHeaders())
.body(ex.getResponseBodyAsString());
}
return responseEntity;
}
proxy controller with oauth2
#RequestMapping("v9")
#RestController
#EnableConfigurationProperties
public class ProxyRestController {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails;
#Autowired
private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
#Autowired
OAuth2RestTemplate oAuth2RestTemplate;
#Value("${gateway.url:http://gateway/}")
String gatewayUrl;
#RequestMapping(value = "/proxy/**")
public String proxy(#RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response,
#RequestHeader HttpHeaders headers) throws ServletException, IOException, URISyntaxException {
body = body == null ? "" : body;
String path = request.getRequestURI();
String query = request.getQueryString();
path = path.replaceAll(".*/v9/proxy", "");
StringBuffer urlBuilder = new StringBuffer(gatewayUrl);
if (path != null) {
urlBuilder.append(path);
}
if (query != null) {
urlBuilder.append('?');
urlBuilder.append(query);
}
URI url = new URI(urlBuilder.toString());
if (logger.isInfoEnabled()) {
logger.info("url: {} ", url);
logger.info("method: {} ", method);
logger.info("body: {} ", body);
logger.info("headers: {} ", headers);
}
ResponseEntity<String> responseEntity
= oAuth2RestTemplate.exchange(url, method, new HttpEntity<String>(body, headers), String.class);
return responseEntity.getBody();
}
#Bean
#ConfigurationProperties("security.oauth2.client")
#ConditionalOnMissingBean(ClientCredentialsResourceDetails.class)
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean
#ConditionalOnMissingBean
public OAuth2RestTemplate oAuth2RestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails);
}
If you can get away with using a lower-level solution like mod_proxy that would be the simpler way to go, but if you need more control (e.g. security, translation, business logic) you may want to take a look at Apache Camel: http://camel.apache.org/how-to-use-camel-as-a-http-proxy-between-a-client-and-server.html
I got inspired by Veluria's solution, but I had issues with gzip compression sent from the target resource.
The goal was to omit Accept-Encoding header:
#RequestMapping("/**")
public ResponseEntity mirrorRest(#RequestBody(required = false) String body,
HttpMethod method, HttpServletRequest request, HttpServletResponse response)
throws URISyntaxException {
String requestUrl = request.getRequestURI();
URI uri = new URI("http", null, server, port, null, null, null);
uri = UriComponentsBuilder.fromUri(uri)
.path(requestUrl)
.query(request.getQueryString())
.build(true).toUri();
HttpHeaders headers = new HttpHeaders();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (!headerName.equals("Accept-Encoding")) {
headers.set(headerName, request.getHeader(headerName));
}
}
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
try {
return restTemplate.exchange(uri, method, httpEntity, String.class);
} catch(HttpStatusCodeException e) {
return ResponseEntity.status(e.getRawStatusCode())
.headers(e.getResponseHeaders())
.body(e.getResponseBodyAsString());
}
}
You need something like jetty transparent proxy, which actually will redirect your call, and you get a chance to overwrite the request if you needed. You may get its detail at http://reanimatter.com/2016/01/25/embedded-jetty-as-http-proxy/

Categories