Getting 400 Bad Request during POST string in spring mvc - spring

i have a rest api that accept a String in POST and return an object,
this is the method:
#RequestMapping(method = RequestMethod.POST, value = "/aValue", headers = "Accept=application/json")
public #ResponseBody
MyObject getMyObject(#RequestBody String string) {
MyObject response = myService.getMyObject(string);
return response;
}
now when i call the api from another service for example, if I do POST like this it gave me always 400 Bad Request:
List<Object> providers = new ArrayList<Object>();
providers.add(jsonProvider);
WebClient client = WebClient.create(baseUrl + myAPI, providers);
client.type(MediaType.APPLICATION_JSON);
client.accept(MediaType.APPLICATION_JSON);
MyObject response = client.post(userId, MyObject.class);
return response;
instead of the working solution i used which is this one:
MyObject response = client.post("\"" + userId + "\"", MyObject.class);
someone could help me ? thanks guys

You're having an issue 'cause what you're posting is not a valid JSON, yet you indicate that it is in your client-side code. As you seem to pass just a simple string property userId you can simply change your mapping to receive plain text by adding consumes = "text/plain",
#RequestMapping(method = RequestMethod.POST, value = "/aValue", headers = "Accept=application/json", consumes = "text/plain")
public #ResponseBody
MyObject getMyObject(#RequestBody String string) {
and have your client send plain text, so
client.type(MediaType.TEXT_PLAIN);

Related

How to return application/pdf through Mono in a Reactive way

I am currently using Spring WebFlux to try build an async end-point, which fetches a PDF from a third-party end-point via Web Client before returning the PDF back to our API consumer. However, I am struggling with returning a Mono<ResponseEntity> with content type application/pdf due to the below exception:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class reactor.core.publisher.MonoMapFuseable] with preset Content-Type 'application/pdf']
Here is controller implementation. My question is:
Is my implementation in the right direction, or would I need to create some sort of converter?
Does Mono<ResponseEntity> even support returning a PDF as a response body?
#RequestMapping(value="/get-pdf", method = RequestMethod.GET)
public Mono<ResponseEntity> getPDFAsync() {
String url = "http://some-end-point";
WebClient client = WebClient.create(url);
return client.get()
.accept(MediaType.APPLICATION_PDF)
.exchangeToMono(response ->
Mono.just(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF)
.body(response.bodyToMono(ByteArrayResource.class)
.map(byteArrayResource -> byteArrayResource.getByteArray())
)));
}
To download a file reactively, you could supply the file as a Flux<DataBuffer>, where DataBuffer is org.springframework.core.io.buffer.DataBuffer, like this:
// some shared buffer factory.
private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
#RequestMapping(value = "/download",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_PDF_VALUE}
)
public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
...
) {
return Mono.fromCallable(() -> {
return ResponseEntity.ok(
DataBufferUtils.read(
new File("somepdf.pdf").toPath(),
dataBufferFactory,
8096
))
});
}
Or more specifically, since you seem to be using the WebFlux WebClient, you can forward the response body flux directly to your own response, without having to buffer the complete response first:
#RequestMapping(value = "/download",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_PDF_VALUE}
)
public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
...
) {
String url = "http://some-end-point";
WebClient client = WebClient.create(url);
return client.get()
.accept(MediaType.APPLICATION_PDF)
.exchange()
.map(response -> response.bodyToFlux(DataBuffer.class))
.map(ResponseEntity::ok);
}
Hint: I hope you are reusing the WebClient instance and not instantiating a new one on each request.
I have found the answer! In short, returning Mono<byte[]>, and add produces = {MediaType.APPLICATION_PDF_VALUE} to #RequestMapping works. See example below.
#RequestMapping(value="/get-pdf", produces = {MediaType.APPLICATION_PDF_VALUE}, method = RequestMethod.GET)
public Mono<byte[]> getPdf() {
String url = "some-end-point";
WebClient client = WebClient.create(url);
return client.get()
.accept(MediaType.APPLICATION_PDF)
.exchangeToMono(response -> response
.bodyToMono(ByteArrayResource.class))
.map(byteArrayResource -> byteArrayResource.getByteArray());
}

Spring get all queries params as a string

I have an API gateway that handles all GET requests and forwards them to the correct url like so
#RequestMapping(value = "**", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<String> doGet(HttpServletRequest req) {
String uriString = (String) req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String targetHost = uriString.split("/")[0];
URI uri = UriComponentsBuilder.fromUriString(targetHost)
.path(uriString)
.build().normalize().encode().toUri();
try {
ClientHttpRequest request = requestFactory.createRequest(uri, HttpMethod.GET);
request.getHeaders().add(HttpHeaders.ACCEPT, "application/json");
ClientHttpResponse response = request.execute();
HttpStatus status = response.getStatusCode();
String json = readBodyAsString(response);
return new ResponseEntity<>(json, status);
} catch (IOException ioe) {
StringBuilder sb = new StringBuilder();
sb.append("{\"message\": \"").append(ioe.getMessage()).append("\"}");
return new ResponseEntity<>(sb.toString(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This works really well for all get requests that have any number of paths.
Problem is the
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
Only grabs the paths of a given URL and not the Query Params
So if this controller gets a request with /api/path/path/path it works but if it gets /api/path/path/path?query=1?search=2 for example it will only grab /api/path/path/path and then the subsequent request will fail since it required query params.
How can I get the entire path of the wild card match to include any queryParams that might be here
Thanks
Really simple just needed to use req.getQueryString() to get all the query params as a string.
Also important to note that I needed to pass the query as a .query() on the UriComponentsBuilder so that it gets encoded properly.
URI uri = UriComponentsBuilder.fromUriString(targetHost)
.path(uriString)
.query(queryParams)
.build().normalize().encode().toUri();

Angular 4 and Spring Rest: How to post FormData containing File and model object in a single request

I would like to send a File object along with custom model object in a single request.
let formData:FormData = new FormData();
let file = this.fileList[0];
formData.append('file', file, file.name);
formData.append('address', JSON.stringify(customObj));
...
this.http.post(fileServeUrl, formData)
My backend is in Spring Rest as below
#RequestMapping(value = "/fileServe",
produces = {"application/json"},
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE},
method = RequestMethod.POST)
ResponseEntity<Image> uploadFile(#RequestPart("file") MultipartFile imageData, #RequestPart("address") Address address) throws IOException {...}
I was able to receive the data if I pass simple String along with File though.
formData.append('file', file, file.name);
formData.append('address', addressText);
Backend
#RequestMapping(value = "/fileServe",
produces = {"application/json"},
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE},
method = RequestMethod.POST)
ResponseEntity<Image> uploadFile(#RequestPart("file") MultipartFile imageData, #RequestPart("address") String addressText) throws IOException {...}
I tried #RequestBody for my custom object but even that didn't work. Any advise please.
The problem with #Requestbody and #RequestPart annotation is that spring use the HttpMessageConverter to take convert the incoming json message into the your object. As you send form data with a file and a text value spring can not convert it into your object. I am afraid you have to pass the value of address seperatetly.
#RequestMapping(value = "/fileupload", headers = ("content-type=multipart/*"), method = RequestMethod.POST)
public ResponseEntity<AjaxResponseBody> upload(#RequestParam("file") MultipartFile file, #RequestParam String name, #RequestParam String postCode) {
AjaxResponseBody result = new AjaxResponseBody();
HttpHeaders headers = new HttpHeaders();
if (!file.isEmpty()) {
try {
Address address = new Address();
address.setName(name);
result.setMsg("ok");
return new ResponseEntity<AjaxResponseBody>(result, headers, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<AjaxResponseBody>(HttpStatus.BAD_REQUEST);
}
} else {
return new ResponseEntity<AjaxResponseBody>(HttpStatus.BAD_REQUEST);
}
}
Expept if you find a way your client app send a file with MimeType of image/jpg and and an address of application/json which allow spring to parse the json and map to your Address object which i couldn't do it.

Different encoding of an HTTP request result, depending on the Accept header

I have a controller with a method to upload files, using, on the client side, the dojo Uploader class that supports ajax uploads for all browsers except IE, and uploads with an IFrame for IE.
The result is a JSON object, but when the IFrame mechanism is used, the JSON must be enclosed in a <textarea>:
#RequestMapping(value = "/documentation/{appId:.+}/", method = RequestMethod.POST)
#ResponseBody
public String uploadDocumentation(HttpServletRequest request,
#PathVariable String appId, #RequestParam("uploadedfile") MultipartFile file)
throws Exception {
// ....
String json = JsonUtils.jsonify(map);
if (accepts(request, "application/json")) {
return json;
} else if (accepts(request, "text/html")) {
return "<textarea>" + json + "</textarea>";
} else {
throw new GinaException("Type de retour non supporté");
}
I was wondering if there is a way to register this encoding mechanism in the framework, so that we would just have to return an object, and let the framework do the rest.
Thanks in advance.
For the record, I simply added a second method:
#RequestMapping(value = "/documentation/{appId:.+}/", method = RequestMethod.POST,
produces="application/json")
#ResponseBody
public UploadResult uploadDocumentation(#PathVariable String appId,
#RequestParam("uploadedfile") MultipartFile file) throws Exception {
...
return new UploadResult(filename);
}
#RequestMapping(value = "/documentation/{appId:.+}/", method = RequestMethod.POST,
produces="text/html")
#ResponseBody
public String uploadDocumentationIE(#PathVariable String appId,
#RequestParam("uploadedfile") MultipartFile file) throws Exception {
UploadResult obj = uploadDocumentation(appId, file);
String json = JsonUtils.jsonify(obj);
return "<textarea>" + json + "</textarea>";
}

How to send GET request with headers by Spring

It will call another REST API with a GET request.
#RequestMapping(value = "xxxx/{id}", method = RequestMethod.GET)
public #ResponseBody GetObjet GET( #PathVariable("id") String id,
#RequestHeader(value="X-Auth-Token") String Token) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Auth-Token", Token);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<GetObjet> response = restTemplate.exchange(url, HttpMethod.GET, entity, GetObjet.class);
return response.getBody();
}
Always 400 Error. It means that bad request or some errors in the request body. But this is GET so the resquest bodys is always empty. So this way to add header may be not right. Any ideas?
You can obtain the headers including the notation #RequestHeader in your method
public void displayHeaderInfo(#RequestHeader("Accept-Encoding") String encoding,
#RequestHeader("Keep-Alive") long keepAlive) {
}
o
You can read more about the request here
And the other way to abtain the URL is:
#RequestMapping(value = "/restURL")
public String serveRest(#RequestBody String body, #RequestHeader HttpHeaders headers){
//Use headers to get the information about all the request headers
long contentLength = headers.getContentLength();
...
StreamSource source = new StreamSource(new StringReader(body));
YourObject obj = (YourObject) jaxb2Mashaller.unmarshal(source);
...
}
Try using:
RestTemplate.getForEntity(url, GetObject.class);
You have some methods to request data from a rest API, such as getForEntity and getForObject, use the one you needed.

Resources