I'm trying to get a boolean parameter from an Angular 6 app and Spring can't handle it. I got the following error :
org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'text/plain;charset=UTF-8' not supported for bodyType=java.lang.Boolean"
That's my front end code :
updateConfirmationDate(reportInfo: number, isItTrue: boolean): Observable<Boolean> {
const url = this.url;
return this.httpClient.patch(url, isItTrue).pipe(first(), catchError(err => console.log(err)));
}
And that's how i handle it on back :
Router :
public RouterFunction<ServerResponse> router() {
return RouterFunctions.route()
.path(apiPrefix, builder -> builder
.PATCH("/patchBooleanEndpoint", diagnosticHandler::updateBooleanEndpoint)
)
)
.build();
}
Handle :
#NonNull
public Mono<ServerResponse> updateMyBoolean(ServerRequest request) {
final Long id = Long.parseLong(request.pathVariable("id"));
return request.bodyToMono(Boolean.class)
.flatMap(myBoolean -> service.updateBooleanById(id, myBoolean))
.flatMap(savedBoolean ->
status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(savedBoolean), Boolean.class));
}
Thank you very much and have a nice day
You have to set content-Type to 'application/json' when you call the backend server from your Angular app.
something like
const headers = new HttpHeaders()
.set("Content-Type", "application/json");
And then set it to your patch request.
Based on the stack trace, the fronted call the backend server with content-Type = 'text/plain;charset=UTF-8' so it (the backend server) fails to parse the request body.
Related
I'm falling into a problem this morning with a custom request between two application, what i need to do is to let application able to talk eachother with two Rest API cause i need to do some actions on the first application by the second. The two applications are developed with springboot.
Suppose to call this two applications admin and superadmin
superadmin send a request with a RestAPI and a customized header -> name = key value = 1234
admin recieve the request and first of all check if the header is present or not, after that the header is finded it can proceed to do all the task.
Here's the code that i've developed :
SUPERADMIN :
#PostMapping(value="/test_api_header")
public ResponseEntity<String> test_API(#RequestParam String url) {
RestTemplate template = new RestTemplate();
URI targetUrl = UriComponentsBuilder.fromUriString(url) // Build the base link
.path("/test_API") // Add path
.build() // Build the URL
.encode() // Encode any URI items that need to be encoded
.toUri(); // Convert to URI
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Content-Type", "application/json");
headers.add("superadminKey", "123456abc");
// build the request
ResponseEntity<String> entity = template.exchange(targetUrl, HttpMethod.GET, new HttpEntity<String>(headers), String.class);
return entity;
}
ADMIN :
#Value("123456abc")
private String saKey;
#GetMapping(value = "/superadmin/test_API")
public String test_API(HttpServletRequest request) {
if (request.getHeader("superadminKey") == saKey) {
return "Finally";
} else {
return "Nothing to do, header not present";
}
}
The SUPERADMIN is able to communicate with the RESTApi in the ADMIN application, in fact on postman i received the answer : Nothing to do, header not present, but i really cannot be able to set that customized header in the superadmin request cause i cannot found it also on postman request in the section "headers".
I've seen that i could also create a customized API Key for this special case, but really don't know how it works, if someone could help me I would be very grateful!
I store my session in database, I have columns with user id, UUID and expiration date. Before any request from Angular I would like to send request to the method in api which check expiration date and let angular send another request if is valid or logout user and remove local storage with token with message about expiration date of my session. I'm looking for similar solution to HTTPInterceptor which add headers automatically to every request instead of add headers to any method with request before.
I'm using Angular 10 and Spring Boot 2.3.1.
EDIT.
I found the solution for catching errors in any request in my interceptor on the Angular side.
#Injectable()
export class HttpInterceptorService implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (localStorage.getItem('username') && localStorage.getItem('basicauth')) {
req = req.clone({
setHeaders: {
Authorization: localStorage.getItem('basicauth')
}
})
}
return next.handle(req).pipe(
catchError(response => {
console.log(response.status)
// do something, example clear LocalStorage...
return throwError(response);
})
)
}
}
EDIT 2.
I made Interceptor with preHandle() method in Spring-Boot to check session expiration date before request and if session is expired I set unique response status to 495 which tell Angular to logout the user and clear LocalStorage.
preHandle() method in Spring-Boot:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//if (request.getRequestURL().toString().endsWith("/api/basicauth"))
String GUID = request.getHeader("GUID") == null? "1" : request.getHeader("GUID");
if (sessionService.getSessionByGuid(GUID).isPresent()) {
Session session = sessionService.getSessionByGuid(GUID).get();
if(session.getExpirationDate().isBefore(LocalDateTime.now())) {
sessionService.deleteSession(GUID);
response.setStatus(495);
return false;
} else {
sessionService.renewSession(session);
return true;
}
}
return true;
}
Interceptor method in Angular:
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (localStorage.getItem('username') && localStorage.getItem('basicauth')) {
req = req.clone({
setHeaders: {
Authorization: localStorage.getItem('basicauth'),
GUID: localStorage.getItem('GUID')
}
})
}
return next.handle(req).pipe(
catchError(response => {
if (response.status == 495) {
this.auth.removeSessionAndStorage();
this.openSnackBar("Twoja sesja wygasłą!",);
this.router.navigate(['login', 'session-expired']);
}
return throwError(response);
})
);
}
I think your design is a little flawed. It really sounds like you are wanting to do JWT authentication but you're getting confused with how the token is validated before actually following through and executing your request.
In your interceptor, as long as you are adding Authorization: Bearer <token> with every request, you just need to create some auth middleware in your backend. This middleware would check the header of every request, grab the JWT and validate it. If it's valid, it then follows through and executes the request. If it fails, it returns an Unauthorized/Forbid result (up to you on what you want to return).
There's a couple of different ways to set this up, but here's a nice medium article that describes how to do it with Node.js. Some other frameworks, like .NET, make it a lot easier. So, you may want to evaluate the options before going with one.
Example code from article:
let jwt = require('jsonwebtoken');
const config = require('./config.js');
let checkToken = (req, res, next) => {
let token = req.headers['x-access-token'] || req.headers['authorization']; // Express headers are auto converted to lowercase
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}
if (token) {
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.json({
success: false,
message: 'Token is not valid'
});
} else {
req.decoded = decoded;
next();
}
});
} else {
return res.json({
success: false,
message: 'Auth token is not supplied'
});
}
};
module.exports = {
checkToken: checkToken
}
So, essentially, the process you should be going for is made up of a couple of things:
The token is sent as a header with every single request
A middleware exists on your backend to validate the token before any API method execution
https://medium.com/dev-bits/a-guide-for-adding-jwt-token-based-authentication-to-your-single-page-nodejs-applications-c403f7cf04f4
If you stick with Spring Boot, it looks like there is some sample code on how to set this up here
I am working on part of an API, which requires making a call to another external API to retrieve data for one of its functions. The call was returning an HTTP 500 error, with description "Content type 'application/octet-stream' not supported." The call is expected to return a type of 'application/json."
I found that this is because the response received doesn't explicitly specify a content type in its header, even though its content is formatted as JSON, so my API defaulted to assuming it was an octet stream.
The problem is, I'm not sure how to adjust for this. How would I get my API to treat the data it receives from the other API as an application/json even if the other API doesn't specify a content type? Changing the other API to include a contenttype attribute in its response is infeasible.
Code:
The API class:
#RestController
#RequestMapping(path={Constants.API_DISPATCH_PROFILE_CONTEXT_PATH},produces = {MediaType.APPLICATION_JSON_VALUE})
public class GetProfileApi {
#Autowired
private GetProfile GetProfile;
#GetMapping(path = {"/{id}"})
public Mono<GetProfileResponse> getProfile(#Valid #PathVariable String id){
return GetProfile.getDispatchProfile(id);
}
The service calling the external API:
#Autowired
private RestClient restClient;
#Value("${dispatch.api.get_profile}")
private String getDispatchProfileUrl;
#Override
public Mono<GetProfileResponse> getDispatchProfile(String id) {
return Mono.just(id)
.flatMap(aLong -> {
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return restClient.get(getDispatchProfileUrl, headers);
}).flatMap(clientResponse -> {
HttpStatus status = clientResponse.statusCode();
log.info("HTTP Status : {}", status.value());
return clientResponse.bodyToMono(GetProfileClientResponse.class);
// the code does not get past the above line before returning the error
}).map(GetProfileClientResponse -> {
log.debug("Response : {}",GetProfileClientResponse);
String id = GetProfileClientResponse.getId();
log.info("SubscriberResponse Code : {}",id);
return GetProfileResponse.builder()
// builder call to be completed later
.build();
});
}
The GET method for the RestClient:
public <T> Mono<ClientResponse> get(String baseURL, MultiValueMap<String,String> headers){
log.info("Executing REST GET method for URL : {}",baseURL);
WebClient client = WebClient.builder()
.baseUrl(baseURL)
.defaultHeaders(httpHeaders -> httpHeaders.addAll(headers))
.build();
return client.get()
.exchange();
}
One solution I had attempted was setting produces= {MediaType.APPLICATION_JSON_VALUE} in the #RequestMapping of the API to produces= {MediaType.APPLICATION_OCTET_STREAM_VALUE}, but this caused a different error, HTTP 406 Not Acceptable. I found that the server could not give the client the data in a representation that was requested, but I could not figure out how to correct it.
How would I be able to treat the response as JSON successfully even though it does not come with a content type?
Hopefully I have framed my question well enough, I've kinda been thrust into this and I'm still trying to figure out what's going on.
Are u using jackson library or jaxb library for marshalling/unmarshalling?
Try annotating Mono entity class with #XmlRootElement and see what happens.
Currently I’m having an issue with new Spring 5 WebClient and I need some help to sort it out.
The issue is:
I request some url that returns json response with content type text/html;charset=utf-8.
But unfortunately I’m still getting an exception:
org.springframework.web.reactive.function.UnsupportedMediaTypeException:
Content type 'text/html;charset=utf-8' not supported. So I can’t
convert response to DTO.
For request I use following code:
Flux<SomeDTO> response = WebClient.create("https://someUrl")
.get()
.uri("/someUri").accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(SomeDTO.class);
response.subscribe(System.out::println);
Btw, it really doesn’t matter which type I point in accept header, always returning text/html. So how could I get my response converted eventually?
As mentioned in previous answer, you can use exchangeStrategies method,
example:
Flux<SomeDTO> response = WebClient.builder()
.baseUrl(url)
.exchangeStrategies(ExchangeStrategies.builder().codecs(this::acceptedCodecs).build())
.build()
.get()
.uri(builder.toUriString(), 1L)
.retrieve()
.bodyToFlux( // .. business logic
private void acceptedCodecs(ClientCodecConfigurer clientCodecConfigurer) {
clientCodecConfigurer.customCodecs().encoder(new Jackson2JsonEncoder(new ObjectMapper(), TEXT_HTML));
clientCodecConfigurer.customCodecs().decoder(new Jackson2JsonDecoder(new ObjectMapper(), TEXT_HTML));
}
If you need to set the maxInMemorySize along with text/html response use:
WebClient invoicesWebClient() {
return WebClient.builder()
.exchangeStrategies(ExchangeStrategies.builder().codecs(this::acceptedCodecs).build())
.build();
}
private void acceptedCodecs(ClientCodecConfigurer clientCodecConfigurer) {
clientCodecConfigurer.defaultCodecs().maxInMemorySize(BUFFER_SIZE_16MB);
clientCodecConfigurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonDecoder(new ObjectMapper(), TEXT_HTML));
clientCodecConfigurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonEncoder(new ObjectMapper(), TEXT_HTML));
}
Having a service send JSON with a "text/html" Content-Type is rather unusual.
There are two ways to deal with this:
configure the Jackson decoder to decode "text/html" content as well; look into the WebClient.builder().exchangeStrategies(ExchangeStrategies) setup method
change the "Content-Type" response header on the fly
Here's a proposal for the second solution:
WebClient client = WebClient.builder().filter((request, next) -> next.exchange(request)
.map(response -> {
MyClientHttpResponseDecorator decorated = new
MyClientHttpResponseDecorator(response);
return decorated;
})).build();
class MyClientHttpResponseDecorator extends ClientHttpResponseDecorator {
private final HttpHeaders httpHeaders;
public MyClientHttpResponseDecorator(ClientHttpResponse delegate) {
super(delegate);
this.httpHeaders = new HttpHeaders(this.getDelegate().getHeaders());
// mutate the content-type header when necessary
}
#Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
}
Note that you should only use that client in that context (for this host).
I'd strongly suggest to try and fix that strange content-type returned by the server, if you can.
I'm trying to communicate with Instagram's API but the reply I get back from my request says that the parameters I passed onto the body weren't detected.
{"error_type":"OAuthException","code":400,"error_message":"You must provide a client_id"}
I tried to send the request by passing a JsonNode or a string inside .post(), like below, but both where unsuccessful.
public CompletionStage<Result> getInstagramToken() {
String code = request().getQueryString("code");
if(code != null) {
WSRequest request = ws.url("https://api.instagram.com/oauth/access_token").setContentType("application/x-wwww-form-urlencoded");
// Json body
/*JsonNode body = Json.newObject()
.put("client_id", insta_clientId)
.put("client_secret", insta_clientSecret)
.put("grant_type", "authorization_code")
.put("redirect_uri", redirect_uri)
.put("code", code);*/
// String body
String body = "client_id="+insta_clientId+"&client_secret="+insta_clientSecret+"&grant_type=authorization_code&redirect_uri="+redirect_uri+"&code="+code;
CompletionStage<WSResponse> response = request.post(body);
return response.thenApplyAsync(resp -> ok(resp.asJson()), exec);
}
return null;
}
The same request passed flawlessly when trying to send it by using a curl command on a terminal or with the Rested plugin on chrome ( where "content type" is set to "application/x-www-form-urlencoded" and the parameters are placed inside "Request body" )
Does anyone have any idea as to how I am supposed to send this request ?
ps: I am also looking for a way to retrieve the value received from my request and store it in a variable instead of returning it to the client.
It seems you are missing a:
.setContentType("application/x-www-form-urlencoded")
Look at our code below. In the post() you can also use a Json object so you can send a HashMap:
CompletionStage<Result> out = ws.url(cbUrl)
.setAuth(<<your user>> , <<your password>>, WSAuthScheme.BASIC)
.setRequestTimeout(Duration.ofSeconds(5))
.setContentType("application/x-www-form-urlencoded")
.post("param=value")
.handle((response, error) -> {
// Was the Chargebee API successful?
if (error == null) {
// Debugging purposes
JsonNode jn = response.asJson();
Logger.debug(Json.toJson(postMap).toString());
Logger.debug(jn.toString());
// Success stuff
return ok("good");
} else {
// Error stuff
return ok("bad");
}
});
Hope this helps you.