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

Provide example on how to use handleServerError #1178

Open
omerlh opened this issue Jun 8, 2023 · 9 comments · May be fixed by #1181
Open

Provide example on how to use handleServerError #1178

omerlh opened this issue Jun 8, 2023 · 9 comments · May be fixed by #1181
Labels

Comments

@omerlh
Copy link

omerlh commented Jun 8, 2023

I've tried using handleServerError so I can handle errors like invalid body passed and return 400 instead of 500 but I can't figure how to use it, can you help with example? I had issues with the right generic type to set for org.http4s.Request and I couldn't figure what to pass there.

Thanks for this really cool library!

@julienrf
Copy link
Member

julienrf commented Jun 8, 2023

Could you please elaborate on the type of errors you would like to handle? handleServerError is called by endpoints4s when an exception occurs during the request processing. To handle validation errors, you may want to use handleClientsErrors instead.

@omerlh
Copy link
Author

omerlh commented Jun 8, 2023 via email

@julienrf
Copy link
Member

julienrf commented Jun 8, 2023

I wonder if this is a bug in the http4s-server interpreter. The logic for decoding a JSON body is here:

req => {
val decodeResult = entityDecoder
.decode(req, strict = true)
.leftMap[Throwable](identity)(endpoints.Effect)
.rethrowT(endpoints.Effect)
endpoints.Effect.flatMap(decodeResult) { value =>
decoder.decode(value) match {
case Valid(a) => endpoints.Effect.pure(Right(a))
case inv: Invalid =>
endpoints.Effect.map(endpoints.handleClientErrors(req, inv))(Left.apply)
}
}

I am not familiar with http4s myself but the rethrowT looks suspicious. Do you mind sending me a PR with a failing test here? Or could you please show the exact response returned by your server?

@julienrf julienrf added the bug label Jun 10, 2023
@julienrf julienrf linked a pull request Jun 11, 2023 that will close this issue
@omerlh
Copy link
Author

omerlh commented Jun 11, 2023

I am trying to reproduce it with tests, but so far no dice.
This curl request reproduce it:

curl --location --request POST 'http://localhost:8080/produce' \
--data-raw ''

I tried many things, including passing empty array as body but it is not reproducing.

This is my code: https://github.com/osskit/dafka-producer/blob/main/src/main/scala/ports/ProduceEndpoint.scala
I had to use JsonEntitiesFromCodecs because I wanted to accept as parameter any object.

@julienrf
Copy link
Member

Thank you for providing those details! I’ve investigated a bit and noticed a difference between Blaze and Ember. Blaze returns a 4xx error response in case of media type mismatch, whereas Ember returns a 5xx response. This is why we did not manage to reproduce your issue in the test suite (it uses Blaze whereas in your code you use Ember).

I am not sure whether the error should be handled by endpoints4s (instead of letting the underlying HTTP server implementation handle it).

The other way to solve your issue is to do it with handleServerError, but unfortunately handleServerError is not called is that situation, which is a bug in the http4s-server interpreter, IMHO. That can be fixed by adding yet another recoverWith here:

val handler = { (http4sRequest: http4s.Request[Effect]) =>
try {
request
.matches(http4sRequest)
.map(_.flatMap {
case Right(a) =>
try {
implementation(a)
.map(response)
.recoverWith { case NonFatal(t) =>
handleServerError(http4sRequest, t)
}
} catch {
case NonFatal(t) => handleServerError(http4sRequest, t)
}
case Left(errorResponse) => errorResponse.pure[Effect]
})
} catch {
case NonFatal(t) => Some(handleServerError(http4sRequest, t))
}
}

I think I should at least fix that thing where we omit calling handleServerError, but there is still the open question on what should endpoints4s do when the incoming request has an incompatible type. In #544, we decided to check that all the server interpreters return a 415 response status (and there is no constraint on the format fo the response entity). If we continue with this approach, we should add an Ember-based test suite and make sure the behavior is consistent with the behavior we get with Blaze.

@omerlh
Copy link
Author

omerlh commented Jun 13, 2023

Thanks for spending the time looking it and reproducing it!

@omerlh
Copy link
Author

omerlh commented Jun 18, 2023

Update: there is similar error when not passing content-type header correctly - it will return 500 if content type is not application/json, I guess it is the same problem. I'll check if the same thing happens with Blaze

@julienrf
Copy link
Member

Hey @omerlh. I’ve worked on a modification of the http4s-server interpreter in #1184 that would catch any failure wrapped in the underlying IO[Response] and let handleServerError handle it. I guess it would solve your problem. However, it breaks the current behavior of the interpreter and I would like to hear from you and @harpocrates what you think is the most appropriate thing to do.

Previously, when decoding the request entity failed with a failed IO, the failure was managed by the underlying http4s server, which, in the case of blaze, returned a 415 response status in case of media type mismatch. Now, in the same situation if we don’t give the underlying http4s server a chance to see that failure and call handleServerError instead, we return a 500 response status: https://github.com/endpoints4s/endpoints4s/actions/runs/5326705613/jobs/9649090178?pr=1184#step:6:1211.

What do you think endpoints4s should do here? I remember being reluctant to letting the underlying HTTP server return their own response in case of such failure. I do believe in the case of a media type mismatch we (endpoints4s) should return a 400 response instead of hoping that the underlying server will return a 415 response.

@omerlh
Copy link
Author

omerlh commented Jun 28, 2023

Actually for the second case (not passing content type headers) 415 is the right status code, where for the first case (empty body) 400 is the right one. I think that if your code return the right status code it's improvement as it allows us to switch between Ember / Blaze without worrying about this kind of problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants