In JAX-RS How to accept HeaderParam and JSON body in single class? - jax-rs

I want to encapsulate the resource and header/query params in same class.
class AddCommentRequest {
#HeaderParam("X-Session-Id")
private String sessionId;
#HeaderParam("X-Request-Id")
private String requestId;
// This will be part of POST body.
private Comment comment;
}
#Path("/")
interface CommentResources {
#POST
#Consumes({ "application/json" })
#Produces({ "application/json" })
#Path("/comments")
Response addComment(#BeanParam AddCommentRequest request);
}
I know I can do something like below:
#Path("/")
interface CommentResources {
#POST
#Consumes({ "application/json" })
#Produces({ "application/json" })
#Path("/comments")
Response addComment(#HeaderParam("X-Session-Id") String sessionId, #HeaderParam("X-Request-Id") String requestId, Comment comment);
}
But I don't want to opt for this:
1. It bloats method arguments and also this forces me to update method signature everytime I add a new header/query param.
2. These params are common for all APIs and I don't want to repeat myself.
If payload would had been URI encoded I could have used #FormParam but in my case the payload is JSON.

Related

ASP.NET Core 2.1 API POST body is null when called using HttpWebRequest, seems it can't be parsed as JSON

I'm facing a strange bug, where .NET Core 2.1 API seems to ignore a JSON body on certain cases.
I advised many other questions (e.g this one, which itself references others), but couldn't resolve my problem.
I have something like the following API method:
[Route("api/v1/accounting")]
public class AccountingController
{ sometimes it's null
||
[HttpPost("invoice/{invoiceId}/send")] ||
public async Task<int?> SendInvoice( \/
[FromRoute] int invoiceId, [FromBody] JObject body
)
{
// ...
}
}
And the relevant configuration is:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new TestJsonConverter());
})
.AddJsonFormatters()
.AddApiExplorer();
// ...
}
Where TestJsonConverter is a simple converter I created for testing why things doesn't work as they should, and it's simple as that:
public class TestJsonConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return token;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary (would be neccesary if used for serialization)");
}
}
Calling the api method using Postman works, meaning it goes through the JSON converter's CanConvert, CanRead, ReadJson, and then routed to SendInvoice with body containing the parsed json.
However, calling the api method using HttpWebRequest (From a .NET Framework 4, if that matters) only goes through CanConvert, then routes to SendInvoice with body being null.
The request body is just a simple json, something like:
{
"customerId": 1234,
"externalId": 5678
}
When I read the body directly, I get the expected value on both cases:
using (var reader = new StreamReader(context.Request.Body))
{
var requestBody = await reader.ReadToEndAsync(); // works
var parsed = JObject.Parse(requestBody);
}
I don't see any meaningful difference between the two kinds of requests - to the left is Postman's request, to the right is the HttpWebRequest:
To be sure, the Content-Type header is set to application/json. Also, FWIW, the HttpWebRequest body is set as follows:
using(var requestStream = httpWebRequest.GetRequestStream())
{
JsonSerializer.Serialize(payload, requestStream);
}
And called with:
var response = (HttpWebResponse)request.GetResponse();
Question
Why does body is null when used with HttpWebRequest? Why does the JSON converter read methods are skipped in such cases?
The problem was in the underlying code of the serialization. So this line:
JsonSerializer.Serialize(payload, requestStream);
Was implemented using the default UTF8 property:
public void Serialize<T>(T instance, Stream stream)
{
using(var streamWriter = new StreamWriter(stream, Encoding.UTF8) // <-- Adds a BOM
using(var jsonWriter = new JsonTextWriter(streamWriter))
{
jsonSerializer.Serialize(jsonWriter, instance); // Newtonsoft.Json's JsonSerializer
}
}
The default UTF8 property adds a BOM character, as noted in the documentation:
It returns a UTF8Encoding object that provides a Unicode byte order
mark (BOM). To instantiate a UTF8 encoding that doesn't provide a BOM,
call any overload of the UTF8Encoding constructor.
It turns out that passing the BOM in a json is not allowed per the spec:
Implementations MUST NOT add a byte order mark (U+FEFF) to the
beginning of a networked-transmitted JSON text.
Hence .NET Core [FromBody] internal deserialization failed.
Lastly, as for why the following did work (see demo here):
using (var reader = new StreamReader(context.Request.Body))
{
var requestBody = await reader.ReadToEndAsync(); // works
var parsed = JObject.Parse(requestBody);
}
I'm not very sure. Certainly, StreamReader also uses UTF8 property by default (see remarks here), so it shouldn't remove the BOM, and indeed it doesn't. Per a test I did (see it here), it seems that ReadToEnd is responsible for removing the BOM.
For elaboration:
StreamWriter and UTF-8 Byte Order Marks
The Curious Case of the JSON BOM

webClient response using bodyToFlux method merging all the received response instead separating it out

I am using ParallelFlux for running many task.
but when i am receiving webClient response using bodyToFlux method its merging all the output response instead of getting one by one.
i want the output should be one by one not single string, is there any other method instead of bodyToFLux need to use.
request method:
Flux<String> responsePost = webClient.build()
//removed get,url and retrieve here
.bodyToFlux(String.class);
responsePost.subscribe(s -> {
//display response
});
response method:
public ParallelFlux<String> convertListToMap() {
//created list of string str
return Flux.fromIterable(str)
.parallel(3)
.runOn(Schedulers.parallel())
.map( s -> {
//some logic here
});
}
output:
parallel fulx reponse: springwebfluxparellelExample
Here instead of returning ParallelFlux create an
ResponseBody class with variable you want to return, assign your response to
variable inside ResponseBody and return it to caller of API function.
public ParallelFlux<ResponseBody> convertListToMap() {
//created list of string str
return Flux.fromIterable(str)
.parallel(3)
.runOn(Schedulers.parallel())
.map( s -> {
//some logic here
});
}
Flux<String> responsePost = webClient.build()
//removed get,url and retrieve here
.bodyToFlux(ResponseBody.class);

How to get ResponseEntity's value in Spring Boot?

I'm newbie in Spring Boot. I got response from API with responseEntity in Spring Boot. I returned value of response. But I want define an array/json and set response value to this array/json. After that, I want to get specific value of this array. For example;
returned value:
{"id":"123456789","license":"6688","code":"D8B1H832EE45"}
I want to take only id's value 123456789.
something like;
array['id']
How can I do it? Help me please.
#GetMapping("/test")
public ResponseEntity<String> getmc() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + restTemplate.getAccessToken());
HttpEntity<String> entity = new HttpEntity<>(null, headers);
ResponseEntity x = restTemplate.exchange("https://api.x.com/v1/demo", HttpMethod.GET, entity, String.class);
return restTemplate.exchange("https://api.x.com/v1/demo", HttpMethod.GET, entity, String.class);
}
it's wrong i know but i just want something like: x.getBody('id');
result: 123456789
You could model a Pojo (Plain old java object) based on the json like the following:
public class Pojo {
private String id;
private int license;
private String code;
public String getId() { return this.id;}
public String getLicense() { return this.license;}
public String getCode() { return this.code;}
}
Then change your endpoint to the following signature:
#GetMapping("/test")
public ResponseEntity<String> getmc() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + restTemplate.getAccessToken());
HttpEntity<Pojo> entity = new HttpEntity<>(null, headers);
ResponseEntity x = restTemplate.exchange("https://api.x.com/v1/demo", HttpMethod.GET, entity, Pojo.class);
return ResponseEntity.ok(entity.getId());
}

JAX-RS : HTTP Status 415 - Unsupported Media Type

I got this error when making an ajax request to my WebService :
HTTP Status 415 - Unsupported Media Type
I tried to add the good MediaType (Text/Html, i think), but it doesn't work. I have still this error. What could this be, do you think ?
Thank you !
My request :
$(document).on('submit','.form-add-edit',function(e){
e.preventDefault();
var idDisruptive = $(e.target).find('input[name=idDisruptive]').val();
var url = "api/disruptive";
var method = "POST";
if (idDisruptive){
url = url + '/' + idDisruptive;
method = "PUT";
}
$.ajax({
url: url,
method : method,
data : getDisruptiveParams(),
success : function (response){
console.log('EDIT')
console.log(response);
//editDisruptive(response);
},
error : function(response){
console.log('EDIT ERROR')
console.log(response);
}
});
});
The Web Service :
#Stateless
#Path("disruptive")
#Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_HTML, MediaType.APPLICATION_XML, MediaType.APPLICATION_FORM_URLENCODED})
#Produces({MediaType.APPLICATION_JSON})
public class DisruptiveFacadeREST extends AbstractFacade<Disruptive> {
#PersistenceContext(unitName = "StoryGeneratorPU")
private EntityManager em;
public DisruptiveFacadeREST() {
super(Disruptive.class);
}
#POST
#Override
public void create(Disruptive entity) {
super.create(entity);
}
#PUT
#Path("{id}")
public void edit(#PathParam("id") Integer id, Disruptive entity) {
super.edit(entity);
}
#Override
protected EntityManager getEntityManager() {
return em;
}
}
You need to set the content-type on the jQuery request. If you don't, it will default to application/x-www-form-urlencoded. And just because you add #Consumes(MediaType.APPLICATION_FORM_URLENCODED) doesn't mean that JAX-RS will not how to convert the form data to Disruptive. There need to be a MessageBodyReader to handle that conversion, which there isn't. Same goes for MediaType.TEXT_HTML. Just adding that means nothing if there is no converter to handle the conversion. Remove those two. What you want is to handle JSON conversion, and there should already be a MessageBodyReader included in the EE server that will convert JSON data to arbitrary POJOs.
So for the jQuery, just add
$.ajax({
contentType: 'application/json'
})
That should solve the problem.

Using #Consume with GET request in Jersey Rest

I'm trying to bind values in a GET request to a POJO.
The values are parameters in a HTTP GET request. I'm using JSONP to pass the parameters however it looks like JSONP pushes the JSON object up onto the Request line so its not really a JSON object which is being sent but instead just name value pairs on the URL.
Is it possible to map the values in my GET request to a POJO? Jersey gives the following exception when i try binding
A HTTP GET method, public void handleJSONP(MyPojo), should not consume any entity.
The binding code is looking in the request body however it doesnt exist because it is a GET request. Is there any other way to bind the values in the request without having to manually include a #QueryParam entry for each ?
Thanks
I was able resolve this by using #com.sun.jersey.api.core.InjectParam of jersey
public JSONWithPadding doSomething(#InjectParam final MyPojo argPojo)
Then the Pojo looks like this
public class MyPojo
{
/** */
#QueryParam("value1")
private String value1;
/** */
#QueryParam("value2")
private String value2;
/** */
#QueryParam("value3")
private List<String> value3;
HTTP GET by specification includes the parameters in the URL - therefore it only accepts value pairs. So, what you are trying to do is not feasible. why don't you use a POST instead to bundle a JSON object together with the request?
I am proposing a more expanded example.
jQuery client side:
var argPojo = {
callback:"myPojoCallback",
value1:"val1",
value2:"val2",
value3:["val1", "val2", "val3"]
};
var url = 'xxx.xx.xx.xx/testPojo';
$.ajax({
type: 'GET',
async: false,
jsonpCallback: argPojo.callback,
url: url,
data:argPojo,
contentType: "application/json",
dataType: 'jsonp',
beforeSend:function(){
console.log("sending:",argPojo);
},
success: function(response) {
console.log("reciving",response);
},
error: function(e) {
console.error("error",e);
}
});
on the server
#Path("testPojo")
#GET
#Consumes(MediaType.APPLICATION_JSON)
#Produces("application/x-javascript")
public JSONWithPadding testPojo(#InjectParam MyPojo argPojo){
System.out.println(argPojo);
System.out.println(argPojo.callback);
System.out.println(argPojo.value1);
System.out.println(argPojo.value2);
System.out.println(argPojo.value3);
return new JSONWithPadding(argPojo, argPojo.callback);
}
the actual class object
public class MyPojo {
#QueryParam("callback")
public String callback;
#QueryParam("value1")
public String value1;
#QueryParam("value2")
public String value2;
#QueryParam("value3[]")
public List<String> value3;
public MyPojo(){}
}
chrome console result
sending: Object
callback: "myPojoCallback"
value1: "val1"
value2: "val2"
value3: Array[3]
__proto__: Object
receiving Object
callback: "myPojoCallback"
value1: "val1"
value2: "val2"
value3: Array[3]
__proto__: Object
As we know GET request cannot consume any entity, we need to pass each parameter as params. To be simple we can do like the below using javax.ws.rs.BeanParam
(We can use the #BeanParam instead of #InjectParam
public JSONWithPadding doSomething(#BeanParam final MyPojo argPojo)
....
public class MyPojo
{
/** */
#QueryParam("value1")
private String value1;
/** */
#QueryParam("value2")
private String value2;
/** */
#QueryParam("value3")
private List<String> value3;
GET request cannot consume any entity.
Instead, use POST or PUT methods (provided request is for insert or update).
Otherwise, go with standard way of passing attributes in URL.