ktor receive json body twice - exception

I cannot receive the json body of the ktor HttpClient twice.
For the server there is a DoubleReceive feature but I don't see how I can use this when doing client calls.
I want to call a different microservice which either returns some json or when there is an error it return e.g. status 500 and a error description a json payload.
so I tried to HttpResponseValidator and in this only allows a readBytes with this code
HttpResponseValidator {
validateResponse { response ->
val statusCode = response.status.value
val originCall = response.call
if (statusCode < 300 || originCall.attributes.contains(ValidateMark)) return#validateResponse
response.use {
val exceptionCall = originCall.save().apply {
attributes.put(ValidateMark, Unit)
}
//try parse error from json payload which other microservice usually send
exceptionCall.response.receiveError()?.also { throw mapErrors(response, it) }
//default ktor exception mapping
when (statusCode) {
in 300..399 -> throw RedirectResponseException(response)
in 400..499 -> throw ClientRequestException(response)
in 500..599 -> throw ServerResponseException(response)
}
if (statusCode >= 600) {
throw ResponseException(response)
}
}
}
}
receiveError can be used as JacksonConfig.defaultMapper.readValue<ServiceErrorResponse>(this.readBytes()) but will throw a DoubleReceivException if you just call response.receive<ServiceErrorResponse>()
The reason for this is that the receive function first checks a received atomicBoolean.
TL;DR
Now I wonder if there are any ideas on how you handle error payloads or do you just not use them? I am new to microservice in such a manner and it was a requirement to add them. Ktor is a new addition. How do you communicate error infromation between services?
Also is there a way to use the DoubleReceive feature in the client. Because HttpClient(){install(DoubleReceive)} does not work as it is not an ApplicationFeature and not a ClientFeature.

Ktor has developed an experimental plugin called Double Receive, you can use it to receive request body as much as you want.
Read more: https://ktor.io/docs/double-receive.html

Related

Angular: Observable with subscribe returns error 200 OK as the response is not JSON

I am developing a front-end web application using Angular 11. This application uses several services which return data in JSON format.
I use the async / await javascript constructs and the Observables to get the answers from these services. This is an example my call:
let myResponse = await this.myService(this.myData);
myResponse.subscribe(
res => {
console.log("Res: ",res)
}, (error) => {
console.log("Error: ",error)
}
);
where this.myService contains the code doing the HTTP call using Angular httpClient.
Unfortunately a specific service (only one!) doesn't return data in JSON format but it returns a byte array (string that identifies a pdf -format application/pdf-).
Unfortunately this invocation causes a very strange error with code 200 OK:
How can I do to prevent res from being interpreted as JSON and therefore this error being reported? How can I read resreporting that it will not be in json format?
This service has no errors (with Postman it works perfectly). The problem is Javascript and Observable which are interpreted as JSON. How can I read the content of res in this case?
If a HTTP API call is not returning a JSON, just provide the proper value in the responseType option:
this.httpClient.get('<URL>', {
responseType: 'arraybuffer'
});
Ref: https://angular.io/api/common/http/HttpClient#description

Make a NestJS route send in response a pretty formatted JSON

I have a NestJS route which sends back in response, a JSON not well formatted (like minified),
I want to make this JSON easier to read, like a JSON prettier or JSON formatted,
Do someone knows how to do it in NestJS ? I accept answers for other NodeJS frameworks like Express, maybe it will work in NestJS too...
Prettifying the JSON response should be the responsibility of the client, not the server. Otherwise you could be sending a lot of white space which will bloat the response size and could lead to a crash due to having a response too large. If you are using something like Postman, it should be easy to prettify it, I think Postman might do it by default. If you are looking at the response in some web browser, you could use JSON.parse() on the response and it should make the response an actual JSON which console.log() would then print in a pretty way.
You should try https://www.postman.com or https://insomnia.rest/. It can save you a lot of time when it comes to testing an API.
While you shouldn't do it in prod as mentioned above, there's number of cases where it makes a lot of sense (e.g. in dev env). You can achieve this in a bit hacky way:
Access express instance inside nest through breaking abstraction. It's not exposed on INest interface, so you'll need to cast it to any type to bypass Typescript check
Set undocumented express property "json spaces", which will set formatting for all JSON responses over the app
const app = await NestFactory.create(AppModule);
if (process.env.NODE_ENV === 'development') {
(app as any).httpAdapter.instance.set('json spaces', 2);
}
await app.listen(3000);
It works for me:
// src/main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
if (process.env.NODE_ENV !== 'production') {
app.getHttpAdapter().getInstance().set('json spaces', 2);
}
await app.listen(process.env.PORT);
}
Define return type string on your controller
Set the Content-Type: application/json response header
Use JSON.stringify to format your object with whitespace
Example controller code:
import { Controller, Get, Header } from '#nestjs/common';
#Controller('diagnostics')
export class DiagnosticsController {
#Get()
#Header('Content-Type', 'application/json')
findAll(): string {
const statusCode = 200;
const statusText = 'OK';
const info = {
self: 'NestJS Diagnostics Report',
status: {
statusCode,
statusText,
},
};
return JSON.stringify(info, null, 2);
}
}

How do I convert json into a kotlin object in ktor using content negotiation?

I'm new to Ktor, and I'm trying to build a backend that processes login credentials. I'm attempting to use the content negotiation feature to convert JSON into a native Kotlin object, but I keep getting an unsupported media type exception. Here is my code:
fun Application.main() {
install(CallLogging)
install(DefaultHeaders)
install(ContentNegotiation) {
register(ContentType.Application.Json, GsonConverter())
}
routing {
get("/") {
call.respondHtml {
head {
title("Kotlin Webapp")
script {
type = ScriptType.textJScript
src = "main.bundle.js"
}
}
body {
div {
id = "root"
}
}
}
}
post("/login") {
val credentials = call.receive<Credentials>()
println(credentials)
}
}
}
data class Credentials(val username: String, val password: String)
And here is the incoming Json I am trying to convert, which I am sending via XMLHttpRequest:
{"username":"Jamdan2","password":"sometext"}
I have searched the web for answers, but could not find what I am doing wrong. Can anybody help?
For completeness, since my comment seems to have helped:
You need to be sure that the request's Content-Type header is set to the correct value, in this case, application/json, otherwise the server cannot be entirely sure what to do with the received content.
In requests, (such as POST or PUT), the client tells the server what type of data is actually sent.
xhr.setRequestHeader("Content-Type", "application/json");

How do I catch json parse error when using acceptWithActor?

I use websockets with playframework 2.3.
This is a snippet from official how-to page.
def socket = WebSocket.acceptWithActor[JsValue, JsValue] { request => out =>
MyWebSocketActor.props(out)
}
When I use the code, How do I catch json parse error(RuntimeException: Error parsing JSON)?
Using the built in json frame formatter, you can't, here's the source code:
https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/api/mvc/WebSocket.scala#L80
If Json.parse throws an exception, it will throw that exception to Netty, which will alert the Netty exception handler, which will close the WebSocket.
What you can do, is define your own json frame formatter that handles the exception:
import play.api.mvc.WebSocket.FrameFormatter
implicit val myJsonFrame: FrameFormatter[JsValue] = implicitly[FrameFormatter[String]].transform(Json.stringify, { text =>
try {
Json.parse(text)
} catch {
case NonFatal(e) => Json.obj("error" -> e.getMessage)
}
})
def socket = WebSocket.acceptWithActor[JsValue, JsValue] { request => out =>
MyWebSocketActor.props(out)
}
In your WebSocket actor, you can then check for json messages that have an error field, and respond to them according to how you wish.

Xamarin Social component fails to retrieve Twitter json stream

I'm using the following to read Twitter json. It works with one uri and not another. The uri's work with the Twitter API console but not Xamarin.Social. I have read and write permissions on the Twitter app so I can't see where I'm going wrong.
https://api.twitter.com/1.1/account/settings.json <-- works
https://api.twitter.com/1.1/users/show.json?screen_name=AUserName <-- fails (see error below)
request.GetResponseAsync ().ContinueWith (response => {
if (response.IsFaulted)
{
Console.WriteLine (response.Exception.Flatten ());
}
var json = response.Result.GetResponseText ();
System.AggregateException: One or more errors occured ---> System.Net.WebException: The remote server returned an error: (401) Unauthorized.
at System.Net.HttpWebRequest.CheckFinalStatus (System.Net.WebAsyncResult result) [0x0030c] in /Developer/MonoTouch/Source/mono/mcs/class/System/System.Net/HttpWebRequest.cs:1606
at System.Net.HttpWebRequest.SetResponseData (System.Net.WebConnectionData data) [0x00141] in /Developer/MonoTouch/Source/mono/mcs/class/System/System.Net/HttpWebRequest.cs:1423
--- End of inner exception stack trace ---
--> (Inner exception 0) System.Net.WebException: The remote server returned an error: (401) Unauthorized.
at System.Net.HttpWebRequest.CheckFinalStatus (System.Net.WebAsyncResult result) [0x0030c] in /Developer/MonoTouch/Source/mono/mcs/class/System/System.Net/HttpWebRequest.cs:1606
at System.Net.HttpWebRequest.SetResponseData (System.Net.WebConnectionData data) [0x00141] in /Developer/MonoTouch/Source/mono/mcs/class/System/System.Net/HttpWebRequest.cs:1423
[quick google search gave this but not sure if its relevant: https://dev.twitter.com/discussions/15206]
// UPDATE ***********
Does this extra infor help or you need more details? If so then what details are required?
public Account Account
{
get
{
var task = Service.GetAccountsAsync ()
.ContinueWith (accounts =>
{
return accounts.Result.ToList ().FirstOrDefault ();
});
return task.Result;
}
set
{
AccountStore.Create ().Save (value, SocialPlatform.ToString ());
}
}
// later on
// when endpoint = "https://api.twitter.com/1.1/account/settings.json" <-- works, json returned
// when endpoint = "https://api.twitter.com/1.1/users/show.json?screen_name=XXXX" <-- IsFaulted with above error,
var request = Service.CreateRequest ("GET", endpoint, Account);
request.GetResponseAsync ().ContinueWith (response => {
if (response.IsFaulted)
{
Console.WriteLine (response.Exception.Flatten ());
return;
}
var json = response.Result.GetResponseText ();
Console.WriteLine (json);
});
It seems like you are not authorised when you make this call.
From Xamarin.Social documentation.
Xamarin.Social uses the Xamarin.Auth library to fetch and store
Account objects.
Each service exposes a GetAuthenticateUI method that returns a
Xamarin.Auth.Authenticator object that you can use to authenticate the
user. Doing so will automatically store the authenticated account so
that it can be used later.
The reason why it works in Twitter API console is that you have authorised there prior to making a call.
If you are already authorising in your app then please post the code you use to authorise.