Handler builds an http.Handler that translates `/v1/*` REST calls into gRPC calls against the supplied in-process *grpc.Server. Returns the handler and a cleanup function the caller invokes at shutdown.
(ctx context.Context, grpcSrv *grpc.Server)
| 37 | // gRPC calls against the supplied in-process *grpc.Server. Returns the |
| 38 | // handler and a cleanup function the caller invokes at shutdown. |
| 39 | func Handler(ctx context.Context, grpcSrv *grpc.Server) (http.Handler, func(), error) { |
| 40 | lis := bufconn.Listen(bufSize) |
| 41 | |
| 42 | // Serve gRPC over the bufconn in a goroutine; the existing TCP |
| 43 | // listener (started by grpcsrv.Server.Run) is the public entry point — |
| 44 | // this listener only carries in-process gateway traffic. |
| 45 | go func() { |
| 46 | _ = grpcSrv.Serve(lis) |
| 47 | }() |
| 48 | |
| 49 | conn, err := grpc.NewClient( |
| 50 | "passthrough:///bufconn", |
| 51 | grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { return lis.Dial() }), |
| 52 | grpc.WithTransportCredentials(insecure.NewCredentials()), |
| 53 | ) |
| 54 | if err != nil { |
| 55 | _ = lis.Close() |
| 56 | return nil, nil, err |
| 57 | } |
| 58 | |
| 59 | mux := runtime.NewServeMux( |
| 60 | // Forward the original request's authorizer host URL to the gRPC |
| 61 | // layer. The in-process bufconn call carries `:authority=bufconn`, |
| 62 | // so without this the service layer would resolve the host as |
| 63 | // "http://bufconn" and JWT issuer validation would reject every |
| 64 | // token minted via the HTTP surface. parsers.GetHostFromRequest is |
| 65 | // the same spoof-hardened resolution the gin path uses; |
| 66 | // transport.MetaFromGRPC reads `x-authorizer-url` first. |
| 67 | runtime.WithMetadata(func(_ context.Context, r *http.Request) metadata.MD { |
| 68 | // x-authorizer-transport=rest lets transport.MetaFromGRPC tag the |
| 69 | // request protocol as REST (vs a direct gRPC call) for audit logs |
| 70 | // and the api-operations metric. |
| 71 | return metadata.Pairs( |
| 72 | "x-authorizer-url", parsers.GetHostFromRequest(r), |
| 73 | "x-authorizer-transport", constants.ProtocolREST, |
| 74 | ) |
| 75 | }), |
| 76 | // Forward the custom admin-secret header to the gRPC layer. The default |
| 77 | // matcher only forwards permanent headers (Authorization, Cookie), but |
| 78 | // admin header-auth also accepts x-authorizer-admin-secret; without this |
| 79 | // REST callers could only authenticate via the admin cookie. All other |
| 80 | // headers fall through to the default matcher so existing behaviour is |
| 81 | // unchanged. |
| 82 | runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { |
| 83 | if strings.EqualFold(key, "x-authorizer-admin-secret") { |
| 84 | return key, true |
| 85 | } |
| 86 | return runtime.DefaultHeaderMatcher(key) |
| 87 | }), |
| 88 | // Promote the service layer's session/MFA cookies to real Set-Cookie |
| 89 | // response headers. Handlers emit them via transport.ApplyToGRPC -> |
| 90 | // grpc.SendHeader as `set-cookie` server metadata; grpc-gateway's |
| 91 | // default outgoing matcher would rename that to `Grpc-Metadata-Set-Cookie` |
| 92 | // (which browsers ignore), so login/signup/session/verify_* would set no |
| 93 | // cookie over REST. Returning false for every other key also stops |
| 94 | // internal gRPC metadata leaking as Grpc-Metadata-* response headers. |
| 95 | runtime.WithOutgoingHeaderMatcher(func(key string) (string, bool) { |
| 96 | if strings.EqualFold(key, "set-cookie") { |