caddy: cannot reverse proxy in front of grpc + grpc-web multiplexer
I have a go server that does multiplexing of grpc and grpc.web based on content-type, this means I accept both http/1.1 and http2 with TLS. Unfortunately it seems impossible to have all version in the http transport directive of reverse_proxy, either I support plain text grpc-web or http2 binary grpc.
The go server expect the TLS cert and key (wich I generated with caddy)
This is my Caddyfile
https://api.mydomain.com
log {
level INFO
}
reverse_proxy {
to h2c://api:8000
transport http {
versions h2c 2
}
}
This is what it logs caddy when calling /Service/Method with a grpc client
{
"level": "error",
"ts": 1611876892.228595,
"logger": "http.log.error.log0",
"msg": "write tcp 172.30.0.3:53400->172.30.0.2:8000: write: broken pipe",
"request": {
"remote_addr": "5.90.3.181:36177",
"proto": "HTTP/2.0",
"method": "POST",
"host": "api.mydomain.com",
"uri": "/Service/Method",
"headers": {
"User-Agent": [
"grpc-node/1.24.2 grpc-c/8.0.0 (linux; chttp2; ganges)"
],
"Grpc-Accept-Encoding": [
"identity,deflate,gzip"
],
"Accept-Encoding": [
"identity,gzip"
],
"Te": [
"trailers"
],
"Content-Type": [
"application/grpc"
]
},
"tls": {
"resumed": false,
"version": 771,
"cipher_suite": 49196,
"proto": "h2",
"proto_mutual": true,
"server_name": "api.mydomain.com"
}
},
"duration": 0.005654583,
"status": 502,
"err_id": "n9kacgca2",
"err_trace": "reverseproxy.statusError (reverseproxy.go:783)"
}
{
"level": "error",
"ts": 1611876892.2293694,
"logger": "http.log.access.log0",
"msg": "handled request",
"request": {
"remote_addr": "5.90.3.181:36177",
"proto": "HTTP/2.0",
"method": "POST",
"host": "api.mydomain.com",
"uri": "/Service/Method",
"headers": {
"Content-Type": [
"application/grpc"
],
"User-Agent": [
"grpc-node/1.24.2 grpc-c/8.0.0 (linux; chttp2; ganges)"
],
"Grpc-Accept-Encoding": [
"identity,deflate,gzip"
],
"Accept-Encoding": [
"identity,gzip"
],
"Te": [
"trailers"
]
},
"tls": {
"resumed": false,
"version": 771,
"cipher_suite": 49196,
"proto": "h2",
"proto_mutual": true,
"server_name": "api.mydomain.com"
}
},
"common_log": "5.90.3.181 - - [28/Jan/2021:23:34:52 +0000] \"POST /Service/Method HTTP/2.0\" 502 0",
"duration": 0.005654583,
"size": 0,
"status": 502,
"resp_headers": {
"Server": [
"Caddy"
]
}
}
My docker compose
version: "3.7"
services:
api:
container_name: "my_container"
image: my_api:latest
restart: unless-stopped
environment:
- SSL_CERT=./cert.pem
- SSL_KEY=./key.pem
ports:
- "8000:8000"
volumes:
- path/to/cert.crt:/cert.pem
- path/to/key.key:/key.pem
caddy:
container_name: "caddy"
image: caddy:latest
restart: unless-stopped
depends_on:
- api
ports:
- "80:80"
- "443:443"
volumes:
- $HOME/Caddyfile:/etc/caddy/Caddyfile
- $HOME/caddy-data:/data
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 41 (20 by maintainers)
Commits related to this issue
- reverseproxy: Support grpc-web -> grpc bridge Related to #3997 — committed to caddyserver/caddy by mholt 2 years ago
The proxy still needs to do the work to translate gRPC-Web bytes into gRPC bytes, which is the work done by Envoy and the gRPC-Web proxy provided by Improbable. This is what Johan says in his blog post. Currently, Caddy does not have this translation layer between the 2 protocols natively. It may be developed as a module.
That said, and this comes with a disclaimer of “I have not tested this myself”, you can have Caddy sit between the client and Envoy and/or between Envoy and the backend; but you cannot expect Caddy to translate gRPC-Web to gRPC. At least not now. Unless you want to turn this into a feature request, which probably should go into a new ticket for discussion.
@mohammed90 True, although I wonder if he means across different programs, i.e. not just Caddy. It’s true other programs can use those certs too, but they have to know to look for them there.
@mholt The caddy reverse proxy configuration is practically the same except I had set
flush_intervalto-1.I think the main issue stems from the fact that
grpc-webrequests are actually incompatible with a grpc server because the protocols are slightly different. The general solution is to have a reverse proxy like Envoy sit between the client and the grpc server and I’m not sure Caddy supports translating the requests like Envoy. My solution for this was to use grpcweb to create agrpc-webcompatible server and use a connection multiplexer to host bothgrpc-webandgrpcon the same port. This is where I ran into issues, and I suspect this is also where @tiero had problems.Caddy was only sending
h2crequests to my webserver so while mygrpchandler could understand it, my grpc-web handler could not and I was gettingread: connection reset by peererrors. At first I thought this was a Caddy issue, but it looks to be an issue with my Go server. Since the default http server in Go does not handleh2cI had to modify the multiplexer toapplication/grpcheaders with the grpc serverapplication/grpc-webheaders with a http2 serverapplication/grpc-webheaders with the default http serverTLDR: This really an issue with Caddy but an issue with how we wrote our gRPC servers. Caddy does not translate
grpc-webto grpc so our Go servers need to handle grpc-web requests. Additionally, we have to explicitly use the h2 packagegrpc-webrequests since the default go http server does not serve h2 by default.You can use a shared storage backend besides the default of filesystem to share the certs. For instance, there are two module to utilize Consul as the certificate storage, from which you can grab the certs to be shared across instances.
Yeah, all this is redundant and can be removed:
The dial timeout as of v2.5.0 (iirc) is 3s, and using
h2c://sets those versions automatically@mholt I’m not sure I understood your question. Here’s what I have
grpcurlplaintext without TLSEnvoyandCaddyas a reverse proxy forgrpc-webrequests2-4 can communicate with 1 without issues. Both TLS and plaintext. All three are using GRPC native transport, which is HTTP 2.0 based.
5 is THE issue. The problem is that ALL web browsers are still HTTP 1.1. Thus there’s a need in a BRIDGE, which converts HTTP1.1 to HTTP2. Reverse proxy is that bridge.
My system works with
Envoyas a reverse proxy. It takes HTTP 1.1 input from agrpc-weband converts into native HTTP 2.0, which isgrpc.Remember, there’s no
grpc-webprotocol. Those are just two projects from here and here, which solve the above stated issue - convert HTTP 1.1 into HTTP2.0 transportSo, bottom line. How do I config
Caddyto deliver that HTTP 1.1 to HTTP 2.0 transport bridging?Envoydoes the job, here is an example@jbrown-stripe might be a longshot, but could you try building from the latest from
master?I think there’s a chance https://github.com/caddyserver/caddy/commit/e6c29ce081673d85e527d59f3afb7ace034573df has fixed this issue, but I may not be fully understanding the problem. Just a sanity check to make sure this is still an issue.
Edit: You can try v2.4.5 at this point, which has the aforementioned fixes.
@tiero It looks like both grpc-web and grpc request get sent to the grpc listener which won’t work. Instead only grpc requests should be routed to the grpc listener and then grpc-web should be routed to http1.1 and h2c listener.
@tiero I ended up getting this to work a few days later. I never needed http2 with tls, just h2c. Regardless, caddy is sending h2c requests when proxying
grpc-web. However your http listener does not match it and cannot handle the request. It also doesn’t like h2c request can be handled by the default http server ingo. My solution was toflush_interval=1in caddy. This seemed to fix the stream errors I was running into.@tiero Running into the exact same issue, any luck figuring it out?