spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.5k stars 3.31k forks source link

how to build grpc response while spring-cloud-gateway handle exception #2562

Open MnameHZJ opened 2 years ago

MnameHZJ commented 2 years ago

This is a duplicate problem, but I can't find the solution.Anybody know?

When the grpc client requests to the gateway, the gateway occur an exception or check header illegal and then needs to return the message about to the grpc client. So how to build a ExceptionHandler similar to controlleradvice . in my case, If no additional processing is done, the grpc client will get a protocol-related exception

moores commented 1 year ago

When will 4.0.5 have a publicly available milestone build available? I have not tried pulling milestone builds in before, but i can't find anything on the repository beyond 4.0.0-RCxx. I'm eager to pull this fix in an test it out.

Albertoimpl commented 1 year ago

@moores https://github.com/spring-cloud/spring-cloud-gateway/releases/tag/v4.0.5 was released and should contain the fix. Let us know how it went. Thanks!

moores commented 1 year ago

hi @Albertoimpl I'm trying to verify a request via BloomRPC client - using v4.0.5 of Spring Cloud Gateway. Requests work fine if there is no error. But if i intentionally throw an exception within a filter or configure the route endpoint to a non-existent DNS entry, the bloom client just hangs when the exception is propagated. It's acting like the connection is never closed. Should I assume that with the gRPC H2C configuration that a particular error status should be set and connection released/closed? Should I expect the GRPCResponseHeadersFilter to be executed when a system error happens? Where is the exception handler for gRPC?

Albertoimpl commented 1 year ago

Thanks for the feedback @moores, would you mind providing a minimal reproducible example so I can take a better look at what headers are coming back when called through the gateway and compare the differences? Not sure if it is a problem with that specific client or if we are still not returning an expected header.

moores commented 1 year ago

Yes @Albertoimpl I'll do that. Can you also let me know where in spring cloud we are handling system exceptions for gRPC responses?

Albertoimpl commented 1 year ago

We do not have a custom exception handler for gRPC @moores, what I would expect is that the server sends a response with different statuses and headers if anything went wrong, and the gateway passes them through. gRPC itself does not have the concept of exception, it is all propagated through statuses and headers.

moores commented 1 year ago

Thanks, that makes sense. In the request/response attached below, I am throwing an exception in the application due to a DNS error on the route endpoint URL. The response looks like the same JSON response I would expect when running with HTTP/REST:

{
    "timestamp": "2023-05-05T17:03:29.550+00:00",
    "path": "/com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails",
    "status": 500,
    "error": "Internal Server Error",
    "requestId": "79db2fec-1"
}

So should spring have a default exception handler for this when the protocol is gRPC? The content-type in the response is set to application/json.. ?

Error transaction packet capture ``` No. Time Source Destination Protocol Length Info 1 0.000000 ::1 ::1 TCP 88 57177 → 8083 [SYN] Seq=0 Win=65535 Len=0 MSS=16324 WS=64 TSval=1852520561 TSecr=0 SACK_PERM Frame 1: 88 bytes on wire (704 bits), 88 bytes captured (704 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 57177, Dst Port: 8083, Seq: 0, Len: 0 No. Time Source Destination Protocol Length Info 2 0.001105 ::1 ::1 TCP 88 8083 → 57177 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=16324 WS=64 TSval=499619003 TSecr=1852520561 SACK_PERM Frame 2: 88 bytes on wire (704 bits), 88 bytes captured (704 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 57177, Seq: 0, Ack: 1, Len: 0 No. Time Source Destination Protocol Length Info 3 0.001125 ::1 ::1 TCP 76 57177 → 8083 [ACK] Seq=1 Ack=1 Win=407744 Len=0 TSval=1852520563 TSecr=499619003 Frame 3: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 57177, Dst Port: 8083, Seq: 1, Ack: 1, Len: 0 No. Time Source Destination Protocol Length Info 4 0.001137 ::1 ::1 TCP 76 [TCP Window Update] 8083 → 57177 [ACK] Seq=1 Ack=1 Win=407744 Len=0 TSval=499619005 TSecr=1852520563 Frame 4: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 57177, Seq: 1, Ack: 1, Len: 0 No. Time Source Destination Protocol Length Info 5 0.001275 ::1 ::1 HTTP2 175 Magic, SETTINGS[0], WINDOW_UPDATE[0], PING[0] Frame 5: 175 bytes on wire (1400 bits), 175 bytes captured (1400 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 57177, Dst Port: 8083, Seq: 1, Ack: 1, Len: 99 HyperText Transfer Protocol 2 Stream: Magic Magic: PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n HyperText Transfer Protocol 2 Stream: SETTINGS, Stream ID: 0, Length 36 Length: 36 Type: SETTINGS (4) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Settings - Enable PUSH : 0 Settings - Max concurrent streams : 0 Settings - Initial Windows size : 4194304 Settings - Max frame size : 4194304 Settings - Max header list size : 8192 Settings - Unknown (65027) : 1 HyperText Transfer Protocol 2 Stream: WINDOW_UPDATE, Stream ID: 0, Length 4 Length: 4 Type: WINDOW_UPDATE (8) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0011 1111 0000 0000 0000 0001 = Window Size Increment: 4128769 HyperText Transfer Protocol 2 Stream: PING, Stream ID: 0, Length 8 Length: 8 Type: PING (6) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Ping: 0000000000000000 No. Time Source Destination Protocol Length Info 6 0.001309 ::1 ::1 TCP 76 8083 → 57177 [ACK] Seq=1 Ack=100 Win=407680 Len=0 TSval=499619005 TSecr=1852520563 Frame 6: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 57177, Seq: 1, Ack: 100, Len: 0 No. Time Source Destination Protocol Length Info 7 0.001378 ::1 ::1 HTTP2 479 HEADERS[1]: POST /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails, WINDOW_UPDATE[1], DATA[1], WINDOW_UPDATE[0] Frame 7: 479 bytes on wire (3832 bits), 479 bytes captured (3832 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 57177, Dst Port: 8083, Seq: 100, Ack: 1, Len: 403 HyperText Transfer Protocol 2 Stream: HEADERS, Stream ID: 1, Length 337, POST /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails Length: 337 Type: HEADERS (1) Flags: 0x04, End Headers 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Header Block Fragment: 8683400a3a617574686f726974790e6c6f63616c686f73743a3830383340053a70617468… [Header Length: 413] [Header Count: 10] Header: :scheme: http Header: :method: POST Header: :authority: localhost:8083 Header: :path: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails Header: client-id: 2 Header: te: trailers Header: content-type: application/grpc Header: user-agent: grpc-node/1.24.7 grpc-c/8.0.0 (osx; chttp2; ganges) Header: grpc-accept-encoding: identity,deflate,gzip Header: accept-encoding: identity,gzip [Full request URI: http://localhost:8083/com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails] HyperText Transfer Protocol 2 Stream: WINDOW_UPDATE, Stream ID: 1, Length 4 Length: 4 Type: WINDOW_UPDATE (8) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0101 = Window Size Increment: 5 HyperText Transfer Protocol 2 Stream: DATA, Stream ID: 1, Length 22 Length: 22 Type: DATA (0) Flags: 0x01, End Stream 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Data: 000000001108bd21120cf403d4de01d5de01019f8d06 HyperText Transfer Protocol 2 Stream: WINDOW_UPDATE, Stream ID: 0, Length 4 Length: 4 Type: WINDOW_UPDATE (8) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0101 = Window Size Increment: 5 No. Time Source Destination Protocol Length Info 8 0.001401 ::1 ::1 TCP 76 8083 → 57177 [ACK] Seq=1 Ack=503 Win=407296 Len=0 TSval=499619005 TSecr=1852520563 Frame 8: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 57177, Seq: 1, Ack: 503, Len: 0 No. Time Source Destination Protocol Length Info 9 0.346373 ::1 ::1 HTTP2 117 SETTINGS[0], SETTINGS[0], PING[0] Frame 9: 117 bytes on wire (936 bits), 117 bytes captured (936 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 57177, Seq: 1, Ack: 503, Len: 41 HyperText Transfer Protocol 2 Stream: SETTINGS, Stream ID: 0, Length 6 Length: 6 Type: SETTINGS (4) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Settings - Max header list size : 8192 HyperText Transfer Protocol 2 Stream: SETTINGS, Stream ID: 0, Length 0 Length: 0 Type: SETTINGS (4) Flags: 0x01, ACK 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 HyperText Transfer Protocol 2 Stream: PING, Stream ID: 0, Length 8 Length: 8 Type: PING (6) Flags: 0x01, ACK 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Pong: 0000000000000000 No. Time Source Destination Protocol Length Info 10 0.346441 ::1 ::1 TCP 76 57177 → 8083 [ACK] Seq=503 Ack=42 Win=407744 Len=0 TSval=1852520908 TSecr=499619350 Frame 10: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 57177, Dst Port: 8083, Seq: 503, Ack: 42, Len: 0 No. Time Source Destination Protocol Length Info 11 10.118059 ::1 ::1 HTTP2/JSON 331 HEADERS[1]: 500 Internal Server Error, DATA[1], JavaScript Object Notation (application/json) Frame 11: 331 bytes on wire (2648 bits), 331 bytes captured (2648 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 57177, Seq: 42, Ack: 503, Len: 255 HyperText Transfer Protocol 2 Stream: HEADERS, Stream ID: 1, Length 30, 500 Internal Server Error Length: 30 Type: HEADERS (1) Flags: 0x04, End Headers 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Header Block Fragment: 8e5f106170706c69636174696f6e2f6a736f6e5a04677a69705c03323037 [Header Length: 107] [Header Count: 4] Header: :status: 500 Internal Server Error Header: content-type: application/json Header: content-encoding: gzip Header: content-length: 207 HyperText Transfer Protocol 2 Stream: DATA, Stream ID: 1, Length 207 Length: 207 Type: DATA (0) Flags: 0x01, End Stream 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Content-encoded entity body (gzip): 207 bytes -> 231 bytes JavaScript Object Notation: application/json Object Member: timestamp [Path with value: /timestamp:2023-05-05T16:52:21.829+00:00] [Member with value: timestamp:2023-05-05T16:52:21.829+00:00] String value: 2023-05-05T16:52:21.829+00:00 Key: timestamp [Path: /timestamp] Member: path [Path with value: /path:/com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails] [Member with value: path:/com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails] String value: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails Key: path [Path: /path] Member: status [Path with value: /status:500] [Member with value: status:500] Number value: 500 Key: status [Path: /status] Member: error [Path with value: /error:Internal Server Error] [Member with value: error:Internal Server Error] String value: Internal Server Error Key: error [Path: /error] Member: requestId [Path with value: /requestId:a6dceb8d/1-1] [Member with value: requestId:a6dceb8d/1-1] String value: a6dceb8d/1-1 Key: requestId [Path: /requestId] No. Time Source Destination Protocol Length Info 12 10.118123 ::1 ::1 TCP 76 57177 → 8083 [ACK] Seq=503 Ack=297 Win=407488 Len=0 TSval=1852530679 TSecr=499629121 Frame 12: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 57177, Dst Port: 8083, Seq: 503, Ack: 297, Len: 0 ```
Success transaction packet capture ``` No. Time Source Destination Protocol Length Info 1 0.000000 ::1 ::1 TCP 88 56809 → 8083 [SYN] Seq=0 Win=65535 Len=0 MSS=16324 WS=64 TSval=1134208300 TSecr=0 SACK_PERM Frame 1: 88 bytes on wire (704 bits), 88 bytes captured (704 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 0, Len: 0 No. Time Source Destination Protocol Length Info 2 0.001192 ::1 ::1 TCP 88 8083 → 56809 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=16324 WS=64 TSval=1572985310 TSecr=1134208300 SACK_PERM Frame 2: 88 bytes on wire (704 bits), 88 bytes captured (704 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 0, Ack: 1, Len: 0 No. Time Source Destination Protocol Length Info 3 0.001215 ::1 ::1 TCP 76 56809 → 8083 [ACK] Seq=1 Ack=1 Win=407744 Len=0 TSval=1134208301 TSecr=1572985310 Frame 3: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 1, Ack: 1, Len: 0 No. Time Source Destination Protocol Length Info 4 0.001228 ::1 ::1 TCP 76 [TCP Window Update] 8083 → 56809 [ACK] Seq=1 Ack=1 Win=407744 Len=0 TSval=1572985311 TSecr=1134208301 Frame 4: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 1, Ack: 1, Len: 0 No. Time Source Destination Protocol Length Info 5 0.001335 ::1 ::1 HTTP2 175 Magic, SETTINGS[0], WINDOW_UPDATE[0], PING[0] Frame 5: 175 bytes on wire (1400 bits), 175 bytes captured (1400 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 1, Ack: 1, Len: 99 HyperText Transfer Protocol 2 Stream: Magic Magic: PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n HyperText Transfer Protocol 2 Stream: SETTINGS, Stream ID: 0, Length 36 Length: 36 Type: SETTINGS (4) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Settings - Enable PUSH : 0 Settings - Max concurrent streams : 0 Settings - Initial Windows size : 4194304 Settings - Max frame size : 4194304 Settings - Max header list size : 8192 Settings - Unknown (65027) : 1 HyperText Transfer Protocol 2 Stream: WINDOW_UPDATE, Stream ID: 0, Length 4 Length: 4 Type: WINDOW_UPDATE (8) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0011 1111 0000 0000 0000 0001 = Window Size Increment: 4128769 HyperText Transfer Protocol 2 Stream: PING, Stream ID: 0, Length 8 Length: 8 Type: PING (6) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Ping: 0000000000000000 No. Time Source Destination Protocol Length Info 6 0.001359 ::1 ::1 TCP 76 8083 → 56809 [ACK] Seq=1 Ack=100 Win=407680 Len=0 TSval=1572985311 TSecr=1134208301 Frame 6: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 1, Ack: 100, Len: 0 No. Time Source Destination Protocol Length Info 7 0.001445 ::1 ::1 GRPCHTTP2 479 HEADERS[1]: POST /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails, WINDOW_UPDATE[1], DATA[1] (GRPC) (PROTOBUF) com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanDetailsGetRequest, WINDOW_UPDATE[0] Frame 7: 479 bytes on wire (3832 bits), 479 bytes captured (3832 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 100, Ack: 1, Len: 403 HyperText Transfer Protocol 2 Stream: HEADERS, Stream ID: 1, Length 337, POST /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails Length: 337 Type: HEADERS (1) Flags: 0x04, End Headers 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Header Block Fragment: 8683400a3a617574686f726974790e6c6f63616c686f73743a3830383340053a70617468… [Header Length: 413] [Header Count: 10] Header: :scheme: http Header: :method: POST Header: :authority: localhost:8083 Header: :path: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails Header: client-id: 2 Header: te: trailers Header: content-type: application/grpc Header: user-agent: grpc-node/1.24.7 grpc-c/8.0.0 (osx; chttp2; ganges) Header: grpc-accept-encoding: identity,deflate,gzip Header: accept-encoding: identity,gzip [Full request URI: http://localhost:8083/com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails] HyperText Transfer Protocol 2 Stream: WINDOW_UPDATE, Stream ID: 1, Length 4 Length: 4 Type: WINDOW_UPDATE (8) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0101 = Window Size Increment: 5 HyperText Transfer Protocol 2 Stream: DATA, Stream ID: 1, Length 22 Length: 22 Type: DATA (0) Flags: 0x01, End Stream 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] DATA payload (22 bytes) GRPC Message: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails, Request Protocol Buffers: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails,request Message: com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanDetailsGetRequest [Message Name: com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanDetailsGetRequest] Field(1): propertyId = 4285 (int32) Field(2): ratePlanIds = [ 500 (int32), 28500 (int32), 28501 (int32), 1 (int32), 99999 (int32)] HyperText Transfer Protocol 2 Stream: WINDOW_UPDATE, Stream ID: 0, Length 4 Length: 4 Type: WINDOW_UPDATE (8) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0101 = Window Size Increment: 5 No. Time Source Destination Protocol Length Info 8 0.001465 ::1 ::1 TCP 76 8083 → 56809 [ACK] Seq=1 Ack=503 Win=407296 Len=0 TSval=1572985311 TSecr=1134208301 Frame 8: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 1, Ack: 503, Len: 0 No. Time Source Destination Protocol Length Info 9 0.010000 ::1 ::1 HTTP2 117 SETTINGS[0], SETTINGS[0], PING[0] Frame 9: 117 bytes on wire (936 bits), 117 bytes captured (936 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 1, Ack: 503, Len: 41 HyperText Transfer Protocol 2 Stream: SETTINGS, Stream ID: 0, Length 6 Length: 6 Type: SETTINGS (4) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Settings - Max header list size : 8192 HyperText Transfer Protocol 2 Stream: SETTINGS, Stream ID: 0, Length 0 Length: 0 Type: SETTINGS (4) Flags: 0x01, ACK 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 HyperText Transfer Protocol 2 Stream: PING, Stream ID: 0, Length 8 Length: 8 Type: PING (6) Flags: 0x01, ACK 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0 Pong: 0000000000000000 No. Time Source Destination Protocol Length Info 10 0.010039 ::1 ::1 TCP 76 56809 → 8083 [ACK] Seq=503 Ack=42 Win=407744 Len=0 TSval=1134208310 TSecr=1572985320 Frame 10: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 503, Ack: 42, Len: 0 No. Time Source Destination Protocol Length Info 11 0.368745 ::1 ::1 HTTP2 189 HEADERS[1]: 200 OK Frame 11: 189 bytes on wire (1512 bits), 189 bytes captured (1512 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 42, Ack: 503, Len: 113 HyperText Transfer Protocol 2 Stream: HEADERS, Stream ID: 1, Length 104, 200 OK Length: 104 Type: HEADERS (1) Flags: 0x04, End Headers 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Header Block Fragment: 884007747261696c657218677270632d7374617475732c677270632d6d6573736167655f… [Header Length: 154] [Header Count: 5] Header: :status: 200 OK Header: trailer: grpc-status,grpc-message Header: content-type: application/grpc Header: grpc-encoding: identity Header: grpc-accept-encoding: gzip No. Time Source Destination Protocol Length Info 12 0.368810 ::1 ::1 TCP 76 56809 → 8083 [ACK] Seq=503 Ack=155 Win=407616 Len=0 TSval=1134208668 TSecr=1572985678 Frame 12: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 503, Ack: 155, Len: 0 No. Time Source Destination Protocol Length Info 13 0.369344 ::1 ::1 GRPC 160 DATA[1] (GRPC) (PROTOBUF) com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanDetailsGetResponse Frame 13: 160 bytes on wire (1280 bits), 160 bytes captured (1280 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 155, Ack: 503, Len: 84 HyperText Transfer Protocol 2 Stream: DATA, Stream ID: 1, Length 75 Length: 75 Type: DATA (0) Flags: 0x00 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] DATA payload (75 bytes) GRPC Message: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails, Response Protocol Buffers: /com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanService/getRoomRatePlanDetails,response Message: com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanDetailsGetResponse [Message Name: com.expedia.lodging.lips.contracts.generated.service.dds.RoomTypeRatePlanDetailsGetResponse] Field(1): roomRateDetail (message) Field(1): roomRateDetail (message) Field(1): roomRateDetail (message) No. Time Source Destination Protocol Length Info 14 0.369378 ::1 ::1 TCP 76 56809 → 8083 [ACK] Seq=503 Ack=239 Win=407552 Len=0 TSval=1134208668 TSecr=1572985678 Frame 14: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 503, Ack: 239, Len: 0 No. Time Source Destination Protocol Length Info 15 0.374777 ::1 ::1 HTTP2 115 HEADERS[1] Frame 15: 115 bytes on wire (920 bits), 115 bytes captured (920 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 239, Ack: 503, Len: 39 HyperText Transfer Protocol 2 Stream: HEADERS, Stream ID: 1, Length 30 Length: 30 Type: HEADERS (1) Flags: 0x05, End Headers, End Stream 0... .... .... .... .... .... .... .... = Reserved: 0x0 .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1 [Pad Length: 0] Header Block Fragment: 400b677270632d7374617475730130400c677270632d6d65737361676500 [Header Length: 40] [Header Count: 2] Header: grpc-status: 0 Header: grpc-message: No. Time Source Destination Protocol Length Info 16 0.374843 ::1 ::1 TCP 76 56809 → 8083 [ACK] Seq=503 Ack=278 Win=407488 Len=0 TSval=1134208675 TSecr=1572985685 Frame 16: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 503, Ack: 278, Len: 0 No. Time Source Destination Protocol Length Info 17 0.404089 ::1 ::1 TCP 76 56809 → 8083 [FIN, ACK] Seq=503 Ack=278 Win=407488 Len=0 TSval=1134208704 TSecr=1572985685 Frame 17: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 503, Ack: 278, Len: 0 No. Time Source Destination Protocol Length Info 18 0.404161 ::1 ::1 TCP 76 8083 → 56809 [ACK] Seq=278 Ack=504 Win=407296 Len=0 TSval=1572985714 TSecr=1134208704 Frame 18: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 278, Ack: 504, Len: 0 No. Time Source Destination Protocol Length Info 19 0.404429 ::1 ::1 TCP 76 8083 → 56809 [FIN, ACK] Seq=278 Ack=504 Win=407296 Len=0 TSval=1572985714 TSecr=1134208704 Frame 19: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 8083, Dst Port: 56809, Seq: 278, Ack: 504, Len: 0 No. Time Source Destination Protocol Length Info 20 0.404504 ::1 ::1 TCP 76 56809 → 8083 [ACK] Seq=504 Ack=279 Win=407488 Len=0 TSval=1134208704 TSecr=1572985714 Frame 20: 76 bytes on wire (608 bits), 76 bytes captured (608 bits) on interface lo0, id 0 Null/Loopback Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 56809, Dst Port: 8083, Seq: 504, Ack: 279, Len: 0 ```
moores commented 1 year ago

I think the spring-cloud-gateway default exception handler should, by default, set the grpc-status and grpc-message headers to some value when an error occurs; doing this if the content-type is application/grpc.
I extended DefaultErrorAttributes and added code to add this response header if processing a grpc request (content-type=application/grpc). But that's suboptimal, as this class should probably not be mutating things. Overriding or extending DefaultErrorWebExceptionHandler seems really complicated - i don't see any support guidance in documentation on this.

moores commented 1 year ago

@Albertoimpl - Should I create a new issue regarding the concerns mentioned above?

Albertoimpl commented 1 year ago

Hi @moores, I think this issue covers well the problem, no need to create a new one. The packets were very useful and I can see that there is something wrong. But I am still not sure why an exception gets propagated when we are properly handling bad responses from a server, see: https://github.com/spring-cloud/spring-cloud-gateway/blob/81203031217b7e73e0fd4e5fa798f08d14ada054/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplicationTests.java#L75-L87 Would it be possible for you to provide a minimal reproducible sample so I can verify this for your case? Thanks!

moores commented 1 year ago

I'll try to do that this week. The test above is returning a FAILED_PRECONDITION from the test stream observer - this works fine.
In my case, I'm trying to handle an internal exception- one that occurs before the request is actually routed to it's destination. For example, if the netty client can't connect to the route destination because of a connect timeout or a DNS resolution failure.

Another difference is that I am not using TLS/SSL- configured like this: (there's a Kotlin extension function isGrpcRequest added to the ServerWebExchange - it's just checks for Content-Type application/grpc)

@Component
class H2CAwareNettyRoutingFilter(
    httpClient: HttpClient,
    headersFiltersProvider: ObjectProvider<List<HttpHeadersFilter>>,
    properties: HttpClientProperties
) : NettyRoutingFilter(httpClient, headersFiltersProvider, properties) {

    /**
     * Overrides [getHttpClient] to return an H2C capable client if
     * the request indicates it's a gRPC request.
     */
    override fun getHttpClient(route: Route, exchange: ServerWebExchange): HttpClient =
        super.getHttpClient(route, exchange).let { httpClient ->
            if (exchange.isGrpcRequest()) {
                // https://projectreactor.io/docs/netty/release/reference/index.html#_http2_2
                httpClient.protocol(HttpProtocol.H2C)
            } else {
                httpClient
            }
        }
    override fun getOrder() = super.getOrder() - 1
}

I did verify an IO error seems to be processed OK on main branch:

@Test
    public void gRPCUnaryCallShouldHandleInternalException() throws SSLException {
        ManagedChannel channel = createSecuredChannel(9999);

        try {
            HelloServiceGrpc.newBlockingStub(channel)
                    .hello(HelloRequest.newBuilder().setFirstName("Mickey").build());
        }
        catch (StatusRuntimeException e) {
            Assertions.assertThat(UNAVAILABLE.getCode()).isEqualTo(e.getStatus().getCode());
            Assertions.assertThat("io exception").isEqualTo(e.getStatus().getDescription());
        }
    }
spencergibb commented 1 year ago

@Albertoimpl @martamedio is this fixed by #2938 #2914?

Albertoimpl commented 1 year ago

@spencergibb If the error comes from the upstream grpc-server, it propagates well and everything works. However, when there is an exception within the gateway-server, we use the default exception handler and it may cause some problems in the grpc-client.

Asked @moores for a reproducer so I can take a deeper look.

moores commented 1 year ago

Sorry I have not worked on this issue for a while- I have been on vacation. I'll try to solve this - this week. Seems to me that the spring org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler knows nothing about gRPC, and it should. It should understand that we are using the gRPC protocol and return a grpc response header.

I extended DefaultErrorWebExceptionHandler and added an override on renderErrorResponse that sets proper gRPC response headers. Something like this: (i have some kotlin extension functions on the exchange, etc)


override fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> =
        if (request.exchange().isGrpcRequest()) {
            val error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL))
            ResponseErrorUtil.grpcServerResponseForError(
                "${error["error"]?.toString() ?: ""} transactionId=${request.exchange().transactionId()}",
                HttpStatus.valueOf(getHttpStatus(error))
            )
        } else {
            super.renderErrorResponse(request)
        }

fun grpcServerResponseForError(
        errorMessage: String,
        status: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR
    ): Mono<ServerResponse> {
        return ServerResponse.status(status)
            .header(HttpHeaders.TRAILER, TrailerHeaderValue)
            .header(GrpcStatusHeader, grpcResponseCodeFor(status))
            .header(GrpcMessageHeader, errorMessage)
            .header(ContentTypeHeader, ContentTypeGrpc)
            .build()
    }

   private fun grpcResponseCodeFor(status: HttpStatus): String =
        when (status) {
            HttpStatus.OK -> GrpcStatusOkCode
            HttpStatus.UNAUTHORIZED -> GrpcStatusUnauthorizedRequestErrorCode
            HttpStatus.BAD_REQUEST -> GrpcStatusInvalidRequestErrorCode
            HttpStatus.NOT_FOUND -> GrpcStatusNotFound
            HttpStatus.GATEWAY_TIMEOUT -> GrpcStatusDeadlineExceeded
            HttpStatus.SERVICE_UNAVAILABLE -> GrpcStatusServiceUnavailableErrorCode
            HttpStatus.INTERNAL_SERVER_ERROR -> GrpcStatusUnknownErrorCode
            else -> GrpcStatusUnknownErrorCode
        }

    // See https://grpc.github.io/grpc/core/md_doc_statuscodes.html
    // See https://chromium.googlesource.com/external/github.com/grpc/grpc/+/HEAD/doc/PROTOCOL-HTTP2.md
    private const val GrpcStatusOkCode = "0"
    private const val GrpcStatusUnknownErrorCode = "2"
    private const val GrpcStatusInvalidRequestErrorCode = "3"
    private const val GrpcStatusDeadlineExceeded = "4"
    private const val GrpcStatusNotFound = "5"
    private const val GrpcStatusUnauthorizedRequestErrorCode = "7"
    private const val GrpcStatusServiceUnavailableErrorCode = "14"

    private const val GrpcStatusHeader = "grpc-status"
    private const val GrpcMessageHeader = "grpc-message"
    private const val TrailerHeaderValue = "$GrpcStatusHeader,$GrpcMessageHeader"