一、Transport 是什么?
在官方 JavaScript 客户端里,Transport 是所有请求的“总管”——
它负责把你的调用发给 Elasticsearch,并在此过程中完成错误处理与**嗅探(sniffing)**等通用逻辑。
你可以通过自定义 Transport
类来“钩住”请求管道,在不破坏现有客户端行为的前提下,插入业务所需的最小代码片段。
二、自定义一个 Transport
const { Client } = require('@elastic/elasticsearch')
const { Transport } = require('@elastic/transport')
class MyTransport extends Transport {
// 覆写请求入口
request (params, options) {
// 在这里注入:统一 Header / 链路追踪 / 限流 / 审计 / 指标…
// 例如:给所有请求加上 X-Opaque-Id
options = options || {}
options.opaqueId = options.opaqueId || `app-${Date.now()}`
// 交回给内置实现,保留原有重试、错误处理、嗅探等能力
return super.request(params, options)
}
}
const client = new Client({
Transport: MyTransport
})
2.1只想“插一刀”?用 super.method
回到默认逻辑
有时只需前后各做一点点(如打点、限流),主逻辑仍沿用官方实现:
class MyTransport extends Transport {
request (params, options) {
const start = process.hrtime.bigint()
return super.request(params, options).finally(() => {
const tookMs = Number(process.hrtime.bigint() - start) / 1e6
// 在这里上报时延/状态码/索引名等指标
})
}
}
小贴士:尽量调用
super.request
,这样不会破坏客户端既有的重试、错误包装、嗅探与连接池配合。
三、你可以在 Transport 里做什么?
-
统一注入与规范化
- 统一的请求头(租户、区域、链路 ID)
- 统一的查询参数/超时策略
-
安全与合规
- 请求级审计日志(谁、何时、查了什么)
- 与公司内网网关/零信任代理对接
-
可观测性
- 记录耗时、状态码、目标索引、重试次数
- 对慢查询/错误进行分级上报
-
保护策略
- 轻量限流、熔断(在调用
super.request
前拦截)
- 轻量限流、熔断(在调用
四、响应内容类型与返回值类型
Transport 会根据响应的 Content-Type
返回不同的 JavaScript 类型:
Content-Type | JavaScript 类型 |
---|---|
application/json | object |
text/plain | string |
application/vnd.elasticsearch+json | object |
application/vnd.mapbox-vector-tile | Buffer |
application/vnd.apache.arrow.stream | Buffer |
application/vnd.elasticsearch+arrow+stream | Buffer |
application/smile | Buffer |
application/vnd.elasticsearch+smile | Buffer |
application/cbor | Buffer |
application/vnd.elasticsearch+cbor | Buffer |
实战含义:如果你在自定义
Transport
里对响应做处理,不要破坏这些映射规则;需要进一步解析(如 Arrow/CBOR),请在你的业务层基于Buffer
再做二次解码。
五、常见增强范式
1) 统一链路追踪(X-Opaque-Id)
class TraceTransport extends Transport {
request (params, options = {}) {
options.opaqueId = options.opaqueId || `svc:${process.pid}:${Date.now()}`
return super.request(params, options)
}
}
2) 简单限流/熔断
class GuardTransport extends Transport {
constructor (opts) {
super(opts)
this.inflight = 0
this.limit = 100 // 并发上限
}
async request (params, options) {
if (this.inflight >= this.limit) {
throw new Error('ES requests throttled')
}
this.inflight++
try {
return await super.request(params, options)
} finally {
this.inflight--
}
}
}
3) 审计日志(最小代价)
class AuditTransport extends Transport {
async request (params, options) {
const t0 = Date.now()
try {
const res = await super.request(params, options)
console.info('ES OK', { path: params.path, method: params.method, took: Date.now()-t0 })
return res
} catch (e) {
console.warn('ES ERR', { path: params.path, method: params.method, took: Date.now()-t0, err: e.name })
throw e
}
}
}
六、与客户端其他扩展的关系
- 你可以同时自定义
ConnectionPool / Connection / Serializer
。 Transport
处于最外层请求管道,适合做横切关注点(AOP 风格):可观测、治理、审计、安全。- 如果你需要替换 HTTP 栈或做底层网络接入,优先在
Connection
覆写request
;需要改序列化协议/NDJSON 细节,使用Serializer
。
七、使用建议与踩坑
- ✅ 尽量复用官方行为:覆写后调用
super.request(params, options)
。 - ✅ 只做“细针脚”:小段逻辑 + 明确边界,复杂改造放到底层
Connection/Serializer
。 - ✅ 关注返回类型:根据
Content-Type
决定是对象、字符串还是Buffer
。 - ⚠️ 不要吞掉错误:记得把异常继续抛出,保留客户端的错误类型封装(如
ResponseError
)。 - ⚠️ 避免全局可变副作用:不要随意修改传入的
params
对象(必要时浅拷贝)。 - ⚠️ 慎重做重试:客户端已有重试策略,二次重试需考虑幂等性(读操作优先)。
八、最小可用模板(TypeScript 友好)
import { Client } from '@elastic/elasticsearch'
import { Transport, TransportRequestParams, TransportRequestOptions } from '@elastic/transport'
class MyTransport extends Transport {
request<TResponse = any, TContext = unknown> (
params: TransportRequestParams,
options?: TransportRequestOptions
) {
// …注入你的逻辑…
return super.request<TResponse, TContext>(params, options)
}
}
export const es = new Client({ Transport: MyTransport })
九、总结
- Transport = 请求总管:负责调度请求、处理错误与嗅探。
- 可插拔、可“只插一刀”:覆写
request
并调用super
,即可在不破坏默认行为下完成统一注入。 - 注意类型映射:根据内容类型返回对象/字符串/Buffer。
- 结合
Connection / Serializer
的分层职责,你可以为生产环境构建一条可观测、可治理、安全合规的 ES 请求链路。