packages feed

grpc-spec-1.0.0: src/Network/GRPC/Spec/Headers/PseudoHeaders.hs

-- | Part of the gRPC spec that maps to HTTP2 pseudo-headers
--
-- Intended for unqualified import.
module Network.GRPC.Spec.Headers.PseudoHeaders (
    -- * Definition
    ServerHeaders(..)
  , ResourceHeaders(..)
  , PseudoHeaders(..)
    -- ** Individual headers
  , Method(..)
  , Scheme(..)
  , Address(..)
  , Path(..)
  , rpcPath
  ) where

import Data.ByteString qualified as Strict (ByteString)
import Data.Hashable
import Data.Proxy
import Network.Socket (HostName, PortNumber)

import Network.GRPC.Spec.RPC

{-------------------------------------------------------------------------------
  Definition

  This is not intended to be a general definition of pseudo-headers in HTTP2,
  but rather a reflection of how these pseudo-headers are used in gRPC.
-------------------------------------------------------------------------------}

-- | Partial pseudo headers: identify the server, but not a specific resource
data ServerHeaders = ServerHeaders {
      serverScheme  :: Scheme
    , serverAddress :: Address
    }
  deriving stock (Show)

-- | Request pseudo-methods
--
-- <https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3>
data ResourceHeaders = ResourceHeaders {
      resourceMethod :: Method
    , resourcePath   :: Path
    }
  deriving stock (Show)

-- | All pseudo-headers
data PseudoHeaders = PseudoHeaders {
      serverHeaders   :: ServerHeaders
    , resourceHeaders :: ResourceHeaders
    }
  deriving stock (Show)

-- | Method
--
-- The only method supported by gRPC is @POST@.
--
-- See also <https://datatracker.ietf.org/doc/html/rfc7231#section-4>.
data Method = Post
  deriving stock (Show)

-- | Scheme
--
-- See <https://datatracker.ietf.org/doc/html/rfc3986#section-3.1>.
data Scheme = Http | Https
  deriving stock (Show)

-- | Address
--
-- The address of a server to connect to. This is not standard gRPC
-- nomenclature, but follows convention such as adopted by
-- [grpcurl](https://github.com/fullstorydev/grpcurl) and
-- [grpc-client-cli](https://github.com/vadimi/grpc-client-cli), which
-- distinguish between the /address/ of a server to connect to (hostname and
-- port), and the (optional) HTTP /authority/, which is an (optional) string to
-- be included as the HTTP2
-- [:authority](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2)
-- [pseudo-header](https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3).
data Address = Address {
      -- | Hostname
      addressHost :: HostName

      -- | TCP port
    , addressPort :: PortNumber

      -- | Authority
      --
      -- When the authority is not specified, it defaults to @addressHost@.
      --
      -- This is used both for the HTTP2 @:authority@ pseudo-header as well
      -- as for TLS SNI (if using a secure connection).
      --
      -- Although the HTTP(2) specification allows the authority to include a
      -- port number, and many servers can accept this, this will /not/ work
      -- with TLS, and it is therefore recommended not to include a port number.
      -- Note that the HTTP2 spec explicitly /disallows/ the authority to
      -- include @userinfo@@.
    , addressAuthority :: Maybe String
    }
  deriving stock (Show)

-- | Path
--
-- The gRPC spec specifies:
--
-- > Path → ":path" "/" Service-Name "/" {method name} # But see note below.
--
-- Moreover, it says:
--
-- > Path is case-sensitive. Some gRPC implementations may allow the Path format
-- > shown above to be overridden, but this functionality is strongly
-- > discouraged. gRPC does not go out of its way to break users that are using
-- > this kind of override, but we do not actively support it, and some
-- > functionality (e.g., service config support) will not work when the path is
-- > not of the form shown above.
--
-- We don't support these non-standard paths at all.
data Path = Path {
      pathService :: Strict.ByteString
    , pathMethod  :: Strict.ByteString
    }
  deriving stock (Show, Eq)

instance Hashable Path where
  hashWithSalt salt Path{pathService, pathMethod} =
      hashWithSalt salt (pathService, pathMethod)

-- | Construct path
rpcPath :: IsRPC rpc => Proxy rpc -> Path
rpcPath proxy = Path (rpcServiceName proxy) (rpcMethodName proxy)