Spray.io: mapping request from text/json to application/json - spray

I am trying to replace a Jetty-based back-end by a pure spray-can + spray-routing one.
The front-end posts JSON content using the text/json media type. I've never had any problems with this with Jetty. I have just realized that it is not a standard type thanks to spray, which only accepts the correct and standard application/json media type.
Is there any easy way to map one to the other at the server side? I would really like to avoid having to introduce an ad-hoc release of the client side of the application to deal with this. Of course, I will make the change for the next scheduled release, but for now I need a "quick and dirty" fix.
I have tried changing the header from text/json to application/json using the following function:
def correctJsonHeaders(req:spray.http.HttpRequest) = {
val tweakedHeaders = req.headers.map{ hdr =>
if(hdr.name == "Content-Type" && hdr.value == "text/json")
`Content-Type`(`application/json`)
else
hdr
}
req.copy(headers = tweakedHeaders)
}
in my route directive, like so:
path("route"){
mapRequest(correctJsonHeaders){
post{
respondWithMediaType(`application/json`) {
handleWith{ x:TypeThatUnmarshallsFromJson =>
bizLogicReturningAJsonString(x)
}
}
}
}
}
Although the header is correctly changed, I still get a 415 error (which goes away if I change the media type to application/json at the client)

After reading the documentation on the spray-http Content-Type Header, I changed my function to:
def correctJsonHeaders(req:spray.http.HttpRequest) = {
if(req.headers.exists(hdr => hdr.name == "Content-Type" && hdr.value == "text/json")){
val tweakedEntity = spray.http.HttpEntity(`application/json`, req.entity.data)
req.copy(entity = tweakedEntity)
}
else req
}
which seems to work. The trick was to change the HttpEntity, instead of the header.

The lack of support for custom Accept and Content-Type headers is caused by the implementation of the JSON marshaller you use. Have a look at the source code of these traits to see where this happens:
spray.httpx.LiftJsonSupport
spray.httpx.Json4sJacksonSupport
spray.httpx.PlayJsonSupport
spray.httpx....
Simply solve this by implementing your own PlayJsonSupport trait and add your custom MediaType and ContentType to the two delegate functions. We needed this as well because we put our vendor version in our accept headers to support versioning on our REST services.

Related

Cross origin for POST

I have a Jetty http server with some Jersey rest services. Those services are called from a React website that runs on a Node server.
Due to the cross origin nature of this setup, I had to add some HTTP headers. Basically, all my webservices return a createOkResult() which is created as follows.
#POST
#Path("orders/quickfilter")
#Consumes(MediaType.APPLICATION_JSON)
public Response getQuickFilterProductionOrders(String data)
{
...
return createOkResult(json.toString());
}
protected Response createOkResult(Object result)
{
return buildCrossOrigin(Response.ok().entity(result));
}
protected static Response buildCrossOrigin(Response.ResponseBuilder responseBuilder)
{
return responseBuilder.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
.allow("OPTIONS")
.build();
}
For the #GET webservices that works fine. But when I create an #POST service, I just can't get it working.
Webbrowsers (chrome and firefox) return these kind of errors:
Access to XMLHttpRequest at 'http://localhost:59187/rs/production/orders/quickfilter' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So, at first sight I would be tempted to think that the headers are still missing. The thing is, when I test this service with a tool like Postman, then all headers turn out to be in place, and the service even returns the requested data.
This is a screenshot of a POST request.
From my front-end (which runs on the node server), I use the axios API, which uses promises, and my request looks like this:
const url = "http://localhost:59187/rs/production/orders/quickfilter";
const data = JSON.stringify(request);
const headers = { headers: { "Content-Type": "application/json" } };
const promise = axios.post(url, data, headers);
Right now I have a HTTP error 500, If I remove the content type header, I get an unsupported media exception. So, I have reasons to believe that the content type is ok.
Paul Samsotha pointed me in the right direction.
I ended up adding a filter to the ServletContextHandler. Unlike the linked article, I didn't really have to create that filter from scratch. There was an existing filter class that I could use: i.e. org.eclipse.jetty.servlets.CrossOriginFilter.
FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,PUT,POST,DELETE,OPTIONS");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin");
filterHolder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
filterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
Some of the above parameters can probably be left out, as they are default values. But what appeared to be crucial for me, was to set the CHAIN_PREFLIGHT_PARAM to false.
One nice side-effect, is that I can simplify the code of the actual services. They do not longer need to add special headers, by contrast they can now just return Response.ok().entity(result).build();.

.net core 2 rejects request with 415 when I set Accept to text/csv

When i POST a request to my .net core 2 mvc backend it returns json data.
I want to optionally change the headers as so , which i will then return a csv file of the data for download
'Accept': 'text/csv',
'Content-Type': `text/csv; charset=utf-8`
I set the controller base class with this Produces filter
[Produces("application/json", "text/csv")]
But those headers always cause .net to return 415 Unsupported Media Type
The controller action looks like this
[HttpPost]
public async Task<IActionResult> Post([FromBody] PostArgs args)
You source of problem is Content-Type: text/csv; charset=utf-8 header.
[FromBody] forces MVC middleware to use the input formatter for model binding (I am talking about PostArgs model). And by default, ASP.NET Core registers only one, JSON input formatter. Cause you set Content-Type, middleware cannot use that default formatter (as Content-Type header says that data in request body should be processed as CSV, not JSON) and so it throws 415 Unsupported Media Type error.
... I want to optionally change the headers as so , which i will then return a csv file of the data for download
Actually, it looks like you understand in wrong way what Content-Type header does:
In requests, (such as POST or PUT), the client tells the server what type of data is actually sent.
In other words, you only need to specify the Accept header, cause
The Accept request HTTP header advertises which content types, expressed as MIME types, the client is able to understand. Using content negotiation, the server then selects one of the proposals.
And it is the server then, who uses a Content-Type header in responses to tell the client what the content type of the returned content (in response) actually is.
To return csv data, you return a ContentResult rather than a JsonResult object. This allows you to define the Content-Type:
return new ContentResult("csv-data", "text/csv", 200);
If you want to return a physical file you could return a FileResult object.
By default, the Accepts header isn't enforced. You can enforce it via configuration:
services.AddMvc(config =>
{
config.RespectBrowserAcceptHeader = true;
});
In order to accept additional formats, you'll also need to add InputFormatters:
services.AddMvc(config =>
{
config.RespectBrowserAcceptHeader = true;
config.InputFormatters.Add(new TextInputFormatter())
config.OutputFormatters.Add(new StringOutputFormatter());
});

Change Response Header Encode .Net Core

I need help changing the encoding of the response header on .Net Core 1.1.
We're developing an application and we created a custom header called "X-Application-Error" where any error that happens at the backend of the aplication returns a response code 500 and inside the header the message. This way I use the "app.UseExceptionHandler" to catch the errors, put it inside the header, and the front end, if it recieves and response code 500, displays the message sent at the header.
This is working as expected, the trouble that I'm having is that I need to send chacters like "é", "ã" and others, and the default encoding of the header is UTF-8, so it doesn't display those characters.
At the .net framework, we can use the "HttpResponse.HeaderEncoding" Property to change it (https://msdn.microsoft.com/en-us/library/system.web.httpresponse.headerencoding(v=vs.110).aspx)
but I can't find the equivalent for the .Net Core (1.1)
I found a similar question here (C# WebClient non-english request header value encoding) but no answer too.
Ok, I have found a work around (and yes, it seems obvious to me now).
From what I understood .Net Core won't allow characters like "á", "ã" inside a header if they are not encoded. So I just used "WebUtility.UrlEncode(string)" and sent the message encoded. At the FrontEnd, Angular decoded the message automatically. The code is like:
app.UseExceptionHandler(
builder =>
{
builder.Run(
async context =>
{
//Some validations I make
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
try
{
context.Response.Headers.Add("Application-Error", WebUtility.UrlEncode(error.Error.Message));
}catch(Exception e)
{
context.Response.Headers.Add("Application-Error", e.Message);
}
//the rest of the code
}
});
});

Setting content type/ encoding in Jersey REST Client

HI I have been trying to call REST POST API using jersey REST Client. The API is docs is
URL:
METHOD: POST
Header Info:-
X-GWS-APP-NAME: XYZ
Accept: application/json or application/xml
My Sample Jersey client code is
Client client = Client.create();
WebResource resource=client.resource(URL);
resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML);
resource.type(javax.ws.rs.core.MediaType.APPLICATION_XML);
resource.type("charset=utf-8");
ClientResponse response = resource.post(ClientResponse.class,myReqObj);
I have been trying this code variation since last 1 week and it is not working. Any help in this regard is highly appreciated.
The tricky part is that the WebResource methods follows the Builder design pattern so it returns a Builder object which you need to preserve and carry on as you call further methods to set the full context of the request.
When you do resource.accept, it returns something you don't store, so it's lost when you do resource.type and therefore only your last call takes effect.
You'd typically set all the criterias in one line, but you could also save the output in a local variable.
ClientResponse response = client.resource(URL)
.accept(MediaType.APPLICATION_XML)
.type(MediaType.APPLICATION_XML)
.post(ClientResponse.class,myReqObj);
I do like that.
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(a, "application/json; charset=UTF-8"));
here, 'a' is account class instance which like
#XmlRootElement
public class account {
...
...
}

Microsoft Translator API answers 500 internal server error

I'm trying to use Microsoft's Translator API in my Rails app. Unfortunately and mostly unexpected, the server answers always with an internal server error. I also tried it manually with Poster[1] and I get the same results.
In more detail, what am I doing? I'm creating an XML string which goes into the body of the request. I used the C# Example of the API documentation. Well, and then I'm just invoking the RESTservice.
My code looks like this:
xmlns1 = "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2"
xmlns2 = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"
xml_builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.TranslateArrayRequest("xmlns:ms" => xmlns1, "xmlns:arr" => xmlns2) {
xml.AppId token #using temporary token instead of appId
xml.From source
xml.To target
xml.Options {
xml["ms"].ContentType {
xml.text "text/html"
}
}
xml.Texts {
translate.each do |key,val|
xml["arr"].string {
xml.text CGI::unescape(val)
}
end
}
}
end
headers = {
'Content-Type' => 'text/xml'
}
uri = URI.parse(##msTranslatorBase + "/TranslateArray" + "?appId=" + token)
req = Net::HTTP::Post.new(uri.path, headers)
req.body = xml_builder.to_xml
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
# [...]
The xml_builder produces something like the following XML. Differently to the example from the API page, I'm defining two namespaces instead of referencing them on the certain tags (mainly because I wanted to reduces the overhead) -- but this doesn't seem to be a problem, when I do it like the docu-example I also get an internal server error.
<?xml version="1.0" encoding="UTF-8"?>
<TranslateArrayRequest xmlns:ms="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<AppId>TX83NVx0MmIxxCzHjPwo2_HgYN7lmWIBqyjruYm7YzCpwnkZL5wtS5oucxqlEFKw9</AppId>
<From>de</From>
<To>en</To>
<Options>
<ms:ContentType>text/html</ms:ContentType>
</Options>
<Texts>
<arr:string>Bitte übersetze diesen Text.</arr:string>
<arr:string>Das hier muss auch noch übersetzt werden.</arr:string>
</Texts>
</TranslateArrayRequest>
Every time I request the service it answers with
#<Net::HTTPInternalServerError 500 The server encountered an error processing the request. Please see the server logs for more details.>
... except I do some unspecified things, like using GET instead of POST, then it answers with something like "method not allowed".
I thought it might be something wrong with the XML stuff, because I can request an AppIdToken and invoke the Translate method without problems. But to me, the XML looks just fine. The documentation states that there is a schema for the expected XML:
The request body is a xml string generated according to the schema specified at http:// api.microsofttranslator.com/v2/Http.svc/help
Unfortunately, I cannot find anything on that.
So now my question(s): Am I doing something wrong? Maybe someone experienced similar situations and can report on solutions or work-arounds?
[1] Poster FF plugin > addons.mozilla.org/en-US/firefox/addon/poster/
Well, after lot's of trial-and-error I think I made it. So in case someone has similar problems, here is how I fixed this:
Apparently, the API is kind of fussy with the incoming XML. But since there is no schema (or at least I couldn't find the one specified in the documentation) it's kind of hard to do it the right way: the ordering of the tags is crucial!
<TranslateArrayRequest>
<AppId/>
<From/>
<Options />
<Texts/>
<To/>
</TranslateArrayRequest>
When the XML has this ordering it works. Otherwise you'll only see the useless internal server error response. Furthermore, I read a couple of times that the API also breaks if the XML contains improper UTF-8. One can force untrusted UTF-8 (e.g. coming from a user form) this way:
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
valid_string = ic.iconv(untrusted_string + ' ')[0..-2]

Resources