_client_shared.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. from __future__ import annotations
  2. import base64
  3. from typing import TYPE_CHECKING, TypeVar
  4. from pyqwest import Headers as HTTPHeaders
  5. from pyqwest import StreamError, StreamErrorCode
  6. from ._protocol_connect import CONNECT_PROTOCOL_VERSION
  7. from .code import Code
  8. from .errors import ConnectError
  9. if TYPE_CHECKING:
  10. from ._codec import Codec
  11. from .request import RequestContext
  12. REQ = TypeVar("REQ")
  13. RES = TypeVar("RES")
  14. def prepare_get_params(
  15. codec: Codec, request_data: bytes, headers: HTTPHeaders
  16. ) -> dict[str, str]:
  17. # Follow order in spec: https://connectrpc.com/docs/protocol/#unary-get-request
  18. params: dict[str, str] = {"connect": f"v{CONNECT_PROTOCOL_VERSION}", "base64": "1"}
  19. if "content-encoding" in headers:
  20. params["compression"] = headers.pop("content-encoding")
  21. params["encoding"] = codec.name()
  22. params["message"] = base64.urlsafe_b64encode(request_data).decode("ascii")
  23. return params
  24. # https://github.com/connectrpc/connect-go/blob/59cc6973156cd9164d6bea493b1d106ed894f2df/error.go#L393
  25. def maybe_map_stream_reset(
  26. e: Exception, ctx: RequestContext[REQ, RES]
  27. ) -> ConnectError | None:
  28. if not isinstance(e, StreamError):
  29. return None
  30. msg = str(e)
  31. match e.code:
  32. case (
  33. StreamErrorCode.NO_ERROR
  34. | StreamErrorCode.PROTOCOL_ERROR
  35. | StreamErrorCode.INTERNAL_ERROR
  36. | StreamErrorCode.FLOW_CONTROL_ERROR
  37. | StreamErrorCode.SETTINGS_TIMEOUT
  38. | StreamErrorCode.FRAME_SIZE_ERROR
  39. | StreamErrorCode.COMPRESSION_ERROR
  40. | StreamErrorCode.CONNECT_ERROR
  41. ):
  42. return ConnectError(Code.INTERNAL, msg)
  43. case StreamErrorCode.REFUSED_STREAM:
  44. return ConnectError(Code.UNAVAILABLE, msg)
  45. case StreamErrorCode.CANCEL:
  46. # Some servers use CANCEL when deadline expires. We can't differentiate
  47. # that from normal cancel without checking our own deadline.
  48. if (t := ctx.timeout_ms) is not None and t <= 0:
  49. return ConnectError(Code.DEADLINE_EXCEEDED, msg)
  50. return ConnectError(Code.CANCELED, msg)
  51. case StreamErrorCode.ENHANCE_YOUR_CALM:
  52. return ConnectError(Code.RESOURCE_EXHAUSTED, f"Bandwidth exhausted: {msg}")
  53. case StreamErrorCode.INADEQUATE_SECURITY:
  54. return ConnectError(
  55. Code.PERMISSION_DENIED, f"Transport protocol insecure: {msg}"
  56. )
  57. return None