Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserializer throws exception when request body is empty. #16

Closed
BennieCopeland opened this issue Dec 24, 2020 · 6 comments
Closed

Deserializer throws exception when request body is empty. #16

BennieCopeland opened this issue Dec 24, 2020 · 6 comments
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@BennieCopeland
Copy link
Contributor

When using the ThothSerializer in Giraffe, attempting to deserialize an empty request body generates an exception from Newtonsoft.Json with Error reading JToken from JsonReader. The default Giraffe serializer (Utf8Json) returns null for the same case.

This happens whether I use ctx.BindJsonAsync, Controller.getJson, or ThothSerializer.ReadBody. For ReadBody, I would prefer this to just be an Error allowing me to return a 403 to the client.

@MangelMaxime
Copy link
Contributor

Hello, in theory, Thoth.Json.Net should capture all the errors coming from Newtonsoft and map them into Error ....

Source:
https://github.com/thoth-org/Thoth.Json.Net/blob/35402a9d8b8b5efd7c61cb1959907a6606fd83f6/src/Decode.fs#L97-L106

Can you please check what is the type of exception you are having?

@BennieCopeland
Copy link
Contributor Author

Using let! request = ThothSerializer.ReadBody ctx AccountRequest.decode causes:

Giraffe.Middleware.GiraffeErrorHandlerMiddleware[0]
      An unhandled exception has occurred while executing the request.
      Newtonsoft.Json.JsonReaderException: Error reading JToken from JsonReader. Path '', line 0, position 0.
         at Newtonsoft.Json.Linq.JToken.ReadFromAsync(JsonReader reader, JsonLoadSettings settings, CancellationToken cancellationToken)
         at FSharp.Control.Tasks.TaskBuilder.bindTask[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.tryFinally[a](FSharpFunc`2 step, FSharpFunc`2 fin)
         at FSharp.Control.Tasks.TaskBuilder.using[a,a](a disp, FSharpFunc`2 body)
         at FSharp.Control.Tasks.TaskBuilder.tryFinally[a](FSharpFunc`2 step, FSharpFunc`2 fin)
         at FSharp.Control.Tasks.TaskBuilder.using[a,a](a disp, FSharpFunc`2 body)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTask[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at Api.Session.Controller.postAction@69.Invoke(Unit unitVar) in C:\Development\IdentityManager\src\TSC.IdentityManager.Server\Api\SessionController.fs:line 69
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at FSharp.Control.Tasks.TaskBuilder.bindTaskConfigureFalse[a,b](Task`1 task, FSharpFunc`2 continuation)
         at FSharp.Control.Tasks.TaskBuilder.run[a](FSharpFunc`2 firstStep)
      --- End of stack trace from previous location ---
         at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService)
         at IdentityServer4.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
         at Giraffe.Middleware.Invoke@63-5.Invoke(Unit unitVar)
         at FSharp.Control.Tasks.TaskBuilder.tryWith[a](FSharpFunc`2 step, FSharpFunc`2 catch)

@BennieCopeland
Copy link
Contributor Author

The code you linked looks like it is from the string decoder and is wrapped in a try catch, but the call in ThothSerializer is not wrapped in a try catch.

static member ReadBodyRaw (ctx: HttpContext) =
task {
use stream = new System.IO.StreamReader(ctx.Request.Body, Utf8EncodingWithoutBom, true, DefaultBufferSize, true)
use jsonReader = new JsonTextReader(stream)
let! json = JValue.ReadFromAsync jsonReader
return json
}
static member ReadBody (ctx: HttpContext) (decoder: Decoder<'T>) =
task {
let! json = ThothSerializer.ReadBodyRaw ctx
return Decode.fromValue "$" decoder json
}

@MangelMaxime
Copy link
Contributor

Hum, you are right it is using Decode.fromValue I tought it was Decode.fromString.

I guess we are using Decode.fromValue here because we want to use a StreamReader instead of JsonTextReader. Probably for performance or something like that.

We should definitely port the try-catch from Decode.fromString here.

@MangelMaxime MangelMaxime added bug Something isn't working help wanted Extra attention is needed labels Dec 25, 2020
@BennieCopeland
Copy link
Contributor Author

I forked it to take a look. Did you know the solution file (.sln) is missing?

@MangelMaxime
Copy link
Contributor

MangelMaxime commented Jan 13, 2021

@BennieCopeland I just released a new version 4.3.0 with your changes in it.

I forked it to take a look. Did you know the solution file (.sln) is missing?

I don't remember if Visual Studio requires it but Ionide, Rider, dotnet cli can work without it. As you can open a folder instead of a solution.

I find sln files not really useful in general because it hides files from you or you have to add all your files to it. Where the file system already does that (kind of).

Also, I am not really a .NET user more of a Fable user where sln are not needed either so I can be wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants