HTTP2 fails for entity processing

We have the following sequence

post {
decodeRequest {
entity(as[String]) { entityString => … }
} ~ complete(StatusCodes.BadRequest, “Bad”)
}
If the client uses HTTP1.1, we pick up the entity properly. If you use HTTP2, we get the StatusCodes.BadRequest. I’ve put println() all over the place and it seems like it just “fails” in entity. I have a rejectionHandler and it doesn’t pick up anything. I’m guessing it is some unmarshalling failure though it is only converting to String (and I’ve tried all types of content types for the client). Wee are using org.json4s.Formats (trying to move to SprayJson). Is there a workaround to pickup the entity String for POST messages in HTTP2.0? Thanks much

Hey there,

My first question would be, have you enabled HTTP/2 by adding akka.http.server.preview.enable-http2 = on to the application’s configuration? If the answer to that question is yes, can you provide an example project (in Github, Gitlab, or whatever) that demonstrates the issue?

I’ve used HTTP/2 with Akka without any problem for a while now, with JSON and gRPC. If you take the the akka-http-quickstart as an example, enabling the HTTP/2 works just fine:

$ curl -v --http2 -X POST -H "Content-Type: application/json" -d '{"name":"John","age":45,"countryOfResidence":"US"}' localhost:8080/users

*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.3
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
> Content-Type: application/json
> Content-Length: 50
> 
* upload completely sent off: 50 out of 50 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 201 Created
< Server: akka-http/10.1.9
< Date: Wed, 28 Aug 2019 08:28:53 GMT
< Content-Type: application/json
< Content-Length: 36
< 
* Connection #0 to host localhost left intact
{"description":"User John created."}%

Yes, I have used preview.enable-http2 and it works for everything except for this particular scenario where I look at the entity. Will try to isolate and post some code. BTW, in your example, you finally negotiated down to HTTP/1.1? HTTP/1.1 works fine for me as well.

Right you are about the HTTP/2 upgrade, I forgot to use the http2-prior-knowledge option. Thanks for pointing that out.

Here’s an edit of the curl, with the right arguments, should anyone be interested to see what that looks like.

$ curl -v -k --http2-prior-knowledge -H "Content-Type: application/json" -d '{"name":"John","age":45,"countryOfResidence":"US"}' http://localhost:8080/users
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x55dcdab36f30)
> POST /users HTTP/2
> Host: localhost:8080
> User-Agent: curl/7.65.3
> Accept: */*
> Content-Type: application/json
> Content-Length: 50
> 
* We are completely uploaded and fine
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
< HTTP/2 201 
< content-type: application/json
< content-length: 36
< date: Wed, 28 Aug 2019 21:51:45 GMT
< server: akka-http/10.1.9
< 
* Connection #0 to host localhost left int

Thanks, that is good to know. For me it works as long as I don’t specify the -d option to curl. So, it is an unmarshalling issue in entity(as[String]). My question is: how do I debug what is happening inside entity? I cannot catch it via rejections. String seems to be fairly easy to unmarshall and so I’m stuck. Thanks @chmodas

I think it’s fair to say something exceptional happens in that particular instance, since you say all other scenarios work splendidly. Have you tried capturing any exceptions using an ExceptionHandler?

If you look at the entity(as[]) code, you’ll see it should be rejecting any errors from the marshaller. With the caveat that it also “Adds a TransformationRejection cancelling all rejections of one of the given classes to the list of rejections potentially coming back from the inner route.” So that’s something that you might look at too.

In any case, it would really help if we could see a reproducer code.

I do have a top level exception handler but I think something else is going on. We generate the internal routes and somehow things are not propagating from the main to the inner routes. For example, I do withRequestTimeout(maxEntityDuration) in the main and extractRequestTimeout() in the generated code and I get InfiniteDuration in the inner function. Am gonna try sending via headers.

Even if there was an exception in entity(as[]) ~ complete(OK), it gets fine to the OK code. I’d expect exception to bubble back up?

I don’t think I have a good enough grasp of this entire system, unfortunately.