-
Notifications
You must be signed in to change notification settings - Fork 71
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
HttpPayloadError
in server logic gets turned into a 400
#1438
Comments
Not characterising it as a bug, but it is an unfortunate consequence of the combinations of two features that are working well individually. I think we're gonna have to target 0.19 for it : whatever road we take to solve this is gonna bring a behavioural change that will impact users. I think I can agree that distinguishing |
eh, fair enough. I can't think of a case where you'd want this behavior by default, so to me that's still a bug :P 0.19 is fine by me - there's a workaround that users can apply in the meantime: adapt the client throwables by catching HPE and wrapping it in something else / replacing it with something else entirely. |
hmm after some thinking, and seeing what we built as a "workaround" for this internally (or rather extra feature, because this isn't considered a bug) I'm starting to think this is... not something that needs fixing. Hear me out: the immediate problem was that the client is returning some sort of Response(
status = Status.InternalServerError,
body = fs2.Stream.emits("goodbye world".getBytes)
)
❓ But why is it doing that? ❗ Well, it appears to be the "best effort" logic of decoding errors based on a discriminator or lack of it. Proof:
//> using dep "com.disneystreaming.smithy4s::smithy4s-http4s:0.18.23"
//> using dep "com.disneystreaming.smithy4s::smithy4s-tests:0.18.23"
//> using option "-Wunused:imports"
import cats.effect.IO
import cats.effect.IOApp
import cats.implicits._
import org.http4s.Request
import org.http4s.implicits._
import smithy4s.example.PizzaAdminService
import smithy4s.http4s.SimpleRestJsonBuilder
import org.http4s.client.Client
import cats.effect.kernel.Resource
import org.http4s.Response
import org.http4s.Status
object Main extends IOApp.Simple {
val route =
SimpleRestJsonBuilder
.routes(
SimpleRestJsonBuilder(PizzaAdminService)
.client[IO](
Client(_ =>
Resource.pure(
Response(
status = Status.BadGateway,
body = fs2.Stream.emits("goodbye world".getBytes)
)
)
)
)
.make
.toTry
.get
)
.make
.toTry
.get
override def run: IO[Unit] = route
.run(Request(uri = uri"/health"))
.value
.debug()
.flatMap(_.traverse_(resp => resp.bodyText.compile.string.debug()))
} This fails:
Internally, we've redefined our protocol to ignore the status code as a discriminator, so any error response that doesn't have a discriminator header results in an .withErrorDiscriminator(resp =>
MonadThrowLike[F].pure {
val discriminator = HttpDiscriminator.fromResponse(errorHeaders, resp)
discriminator match {
case HttpDiscriminator.StatusCode(_) => HttpDiscriminator.Undetermined
case other => other
}
}
) and indeed, when an unparseable 500 is raised, the client doesn't even try, but instead it fails with Regardless of the status code, an Does this make sense? |
I was actually concerned about another thing. The remaining issue, is when the external service returns a known (to the discriminator) but malformed response. In your example that would be 500 but with incorrect body payload. This would fail with //> using dep "com.disneystreaming.smithy4s::smithy4s-http4s:0.18.23"
//> using dep "com.disneystreaming.smithy4s::smithy4s-tests:0.18.23"
//> using option "-Wunused:imports"
import cats.effect.IO
import cats.effect.IOApp
import cats.implicits._
import org.http4s.Request
import org.http4s.implicits._
import smithy4s.example.PizzaAdminService
import smithy4s.http4s.SimpleRestJsonBuilder
import org.http4s.client.Client
import cats.effect.kernel.Resource
import org.http4s.Response
import org.http4s.Status
object Main extends IOApp.Simple {
val route =
SimpleRestJsonBuilder
.routes(
SimpleRestJsonBuilder(PizzaAdminService)
.client[IO](
Client(_ =>
Resource.pure(
Response(
status = Status.InternalServerError,
body = fs2.Stream.emits("goodbye world".getBytes)
)
)
)
)
.make
.toTry
.get
)
.make
.toTry
.get
override def run: IO[Unit] = route
.run(Request(uri = uri"/health"))
.value
.debug()
.flatMap(_.traverse_(resp => resp.bodyText.compile.string.debug()))
} which returns:
|
Well I'm not sure whether your reasoning makes sense as a justification to not address the problem. As a matter of fact, it reinforces the justification to fix it, as in a situation where the server would issue a response that is not parseable by the client (which seems to happen often), the client would lose the original payload, which is detrimental to the user. I think the correct solution would be that, upon failing to parse, always surface the same information we surface in |
So we just enhance |
Yeah, I'm thinking we should get rid of the
|
Sounds good to me. @ghostbuster91 @GaryAghedo any objections? |
LGTM 👍 |
Closed in #1570 |
Similar to #1256, but not a regression from 0.17: it appears to have been here all along.
Consider this case: a smithy4s server wrapping a smithy4s client (doesn't have to be the same Service, but it can be, e.g. in a proxy scenario).
Now here's what may happen:
HttpPayloadError
.HttpPayloadError
).This appears incorrect: the error is clearly not a problem of the ultimate caller of the service: ideally, the smithy4s server here would return a 500 (the serialization of which is up for debate). At the moment, it makes it seem like the client has made a mistake in its request. This may be very difficult to debug.
Reproduction: this prints a 400 response with a JSON body. I would expect it to fail on IO level.
I believe this is due to this line:
smithy4s/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala
Line 201 in 3233108
and this function is later called here:
smithy4s/modules/core/src/smithy4s/server/UnaryServerEndpoint.scala
Line 37 in 3233108
HPEs are special-cased in the first snippet, as far as I understand it's because they're normally raised in the request decoding logic. However, this is clearly not a request decoding problem, so we should probably circumvent that code path.
I'm not sure if this is a server problem or a client problem:
The text was updated successfully, but these errors were encountered: