http-rpc-client
对以 RESTful 风格设计的 API, 要提供 RPC 调用, 比较明显的缺点就是在 HTTP 请求/响应模式下, 服务器无法主动向客户端推送的尴尬问题.
将推送场景通过订阅模式, 单独提供 WebSocket 服务, 让用户端进行订阅.
而服务之间, 通过消息队列, 来订阅关注的数据流以达成异步的消息处理.
那么大多数的微服务 RPC 调用场景, 都不再强依赖服务器的主动推送功能.
而 grpc 基于 ProtoBuf 的格式定义, 也可以被 openapi.json 取代.
这种情况下, 基于 HTTP 的 RPC 调用, 需要解决的, 基本就是服务直接该如何发现和互相访问.
而基于 Traefik@docker 的微服务架构中, 所有服务注册到 Docker Engine, 通过暴露 Host 和 Path 等规则来提供反向代理.
那么对于 HTTP 客户端而言, 只需要能知晓 traefik 的访问地址, 以及 RPC 资源的 Host 和 Path.
至于params、body 等参数, 在业务代码里进行调用的时候传递即可.
以 HTTPX 的客户端封装为例
from functools import partial
from typing import Any, Optional
from httpx import AsyncClient, Client
HTTP_METHODS = {'get', 'post', 'put', 'delete', 'options', 'head', 'patch'}
class BaseHTTPResource:
SCHEME: str = 'http'
HOST: str = 'localhost'
PORT: Optional[int] = None
SERVICE_NAME: str = ''
PATH: str = ''
def __init__(self, *args, **kwargs):
headers = kwargs.get('headers')
if headers is None:
headers = {}
kwargs['headers'] = headers
headers['host'] = self.SERVICE_NAME if self.SERVICE_NAME else self.HOST
super().__init__(*args, **kwargs)
def __getattribute__(self, name: str) -> Any:
result = super().__getattribute__(name)
if name in HTTP_METHODS:
port = self.PORT
if port is None:
port = 443 if self.SCHEME == 'https' else 80
url = f'{self.SCHEME}://{self.HOST}:{port}{self.PATH}'
result = partial(result, url=url)
return result
class HTTPResource(BaseHTTPResource, Client):
pass
class AsyncHTTPResource(BaseHTTPResource, AsyncClient):
pass
核心思路是在实例化的时候根据SERVICE_NAME 注入 host 请求头, 作为traefik 反向代理的依据.
在实际调用请求的时候, 根据类属性, 注入 url.
那么在定义好表示 HTTP资源的类对象后, 对于实际调用者而言, 仅仅需要关心具体的请求参数和响应结果, 而不需要关心资源所在的目标地址.