How do I catch json parse error when using acceptWithActor? - json

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.

Related

ktor Apache client post request - No transformation found: class kotlinx.coroutines.io.ByteBufferChannel

I have to use very old ktor version (1.2.6) - please don't tell me to upgrade I can't do it now.
I am trying to send a post request to another service which I mocked for testing with wiremock.
This is my client config:
object HTTP {
val client = HttpClient(Apache) {
followRedirects = false
engine {
customizeClient {
setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
}
}
install(JsonFeature) {
serializer = JacksonSerializer()
}
}
}
And this is code that actually tries to do post:
val url = "http://localhost:9099/v0/test"
val response = HTTP.client.post<TestResponse>(url) {
header(HttpHeaders.ContentType, "application/json")
contentType(ContentType.Application.Json)
body = TestRequest()
}
and this is my wiremock configuration:
WireMock.stubFor(
WireMock.post(WireMock.urlMatching("/v0/test"))
.willReturn(
WireMock.aResponse().withStatus(200)
.withBody(jacksonObjectMapper().writeValueAsString(testResponse))
)
)
When I hit this wiremock with curl it works fine, but calling it with the code above results in:
No transformation found: class kotlinx.coroutines.io.ByteBufferChannel -> class com.test.TestResponse
io.ktor.client.call.NoTransformationFoundException: No transformation found: class kotlinx.coroutines.io.ByteBufferChannel -> class com.test.TestResponse
at io.ktor.client.call.HttpClientCall.receive(HttpClientCall.kt:88)
Can someone please help ?
To solve your problem add the Content-Type: application/json header to the response:
stubFor(
post(urlMatching("/v0/test"))
.willReturn(
aResponse().withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(jacksonObjectMapper().writeValueAsString(TestResponse(123)))
)
)

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

ktor receive json body twice

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

Unable to access data inside a string (i.e. [ object Object ]) that was originally sent as a JSON object

I'm using axios to send a JSON object as a parameter to my api. Before it post request is fired, my data starts of as a JSON object. On the server side, when I console.log(req.params) the data is returned as such
[object Object]
When I used typeof, it returned a string. So then I went to use JSON.parse(). However, when I used that, it returned an error as such
SyntaxError: Unexpected token o in JSON at position 1
I looked for solutions, but nothing I tried seemed to work. Now I'm thinking I'm sending the data to the server incorrectly.
Here's my post request using axios:
createMedia: async function(mediaData) {
console.log("SAVING MEDIA OBJECT");
console.log(typeof mediaData)
let json = await axios.post(`http://localhost:3001/api/media/new/${mediaData}`)
return json;
}
Any thoughts on how I can solve this?
You need to update your code using axios to provide the mediaData in the body of the request instead of the URL:
createMedia: async function(mediaData) {
console.log("SAVING MEDIA OBJECT");
console.log(typeof mediaData)
let json = await axios.post(`http://localhost:3001/api/media/new/`, mediaData)
return json;
}
In the backend (assuming you're using express here), you need to configure your application to use bodyParser:
var express = require('express')
, app = express.createServer();
app.use(express.bodyParser());
And then in your controller update your console.log(req.params) to console.log(req.body); then restart your node server

Custom Action composition to authorize based on JSON token in Play (Scala)

Unlike most discussions on Action composition (such as this one), I need to parse the incoming JSON request in my Action. This is because our application delivers a security token embedded in the JSON (not in the header, as is typical).
What I'd like to achieve is this:
object AuthenticatedAction extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
// Do something magical here that will:
// 1. parse the inbound request.body.validate[GPToken]
// 2. (do stuff with the token to check authorization)
// 3. if NOT authorized return an HTTP NOTAUTHORIZED or FORBIDDEN
// 4. otherwise, forward the request to the desired endpoint
}
object SomeController extends Controller
val action = AuthenticatedAction(parse.json) { implicit request =>
request.body.validate[SomeRequest] match {
// Do whatever... totally transparent and already authorized
}
}
...
The inbound JSON will always have a token, e.g.:
{
"token":"af75e4ad7564cfde",
// other parameters we don't care about
}
So, I'm thinking to parse just want we want (and not end up parsing complex, deeply nested JSON structures) I could just have a GPToken object:
class GPToken(token: String)
object GPToken { implicit val readsToken = Json.reads[GPToken] }
Then in the "magic" of AuthenticationAction I could deserialize just the token, do my thing with the database to check authorization, and either pass the request on or send back a NOTAUTHORIZED. But this is where I'm getting lost... how do I get the json body, parse it, and filter all incoming requests through my security layer?
I think it would be better to move that token to your request headers. Doing so will allow you to use Play's AuthententicatedBuilder which is an ActionBuilder to help with authentication.
If you can do this then you could have a trait like so:
trait Authentication {
object Authenticated extends play.api.mvc.Security.AuthenticatedBuilder(checkGPToken(_), onUnauthorized(_))
def checkGPToken(request: RequestHeader): Option[User] = {
request.headers.get("GPToken") flatMap { token =>
// Do the check with the token
// Return something about the user that will be available inside your actions
}
}
def onUnauthorized(request: RequestHeader) = {
// Do something when it doesn't pass authorization
Results.Unauthorized
}
}
Now with your controllers, you can create an action that requires authentication quite simply.
object SomeController extends Controller with Authentication {
def someAction = Authenticated { req =>
// Your user your header check is available
val user = req.user
// Do something in the context of being authenticated
Ok
}
}