# toxy [](https://travis-ci.org/h2non/toxy) [](https://codeclimate.com/github/h2non/toxy) [](https://www.npmjs.org/package/toxy) [](http://standardjs.com)
> **当前未积极维护,可能与最新的 Node.js 运行时不兼容**。如果你有兴趣维护 toxy,请提交 issue。
**可入侵的 HTTP 代理**,用于**模拟**服务器**故障场景**、**系统弹性测试**和**意外网络状况**,专为 [node.js](http://nodejs.org) 构建。
<img align="right" height="180" src="http://s8.postimg.org/ikc9jxllh/toxic.jpg" />
它主要用于抵抗故障测试,在覆盖容错和弹性能力时特别有用,尤其是在[容忍延迟网络](https://en.wikipedia.org/wiki/Delay-tolerant_networking)和[面向服务](http://microservices.io/patterns/index.html)架构中,toxy 可以作为服务间的中间人代理来注入故障。
toxy 允许你插入[毒药](#poisons),可选择通过[规则](#rules)过滤,这些规则可以拦截并按需更改 HTTP 流,在此过程中执行多种恶意操作,例如限制带宽、延迟网络数据包、注入网络抖动延迟或回复自定义错误或状态码。
它主要在 L7 层操作,尽管可以模拟 L3 层网络条件。
toxy 可以通过[编程方式](#programmatic-api)或 [HTTP API](#http-api) 流畅使用。
它基于 [rocky](https://github.com/h2non/rocky) 构建,一个全功能的面向中间件的 HTTP 代理,并且也可以作为标准中间件[插件式](https://github.com/h2non/toxy/blob/master/examples/express.js)地用于 [connect](https://github.com/senchalabs/connect)/[express](http://expressjs.com)。
需要 node.js +4。
## 目录
- [功能特性](#features)
- [介绍](#introduction)
- [为什么使用 toxy?](#why-toxy)
- [概念](#concepts)
- [工作原理](#how-it-works)
- [使用](#usage)
- [安装](#installation)
- [示例](#examples)
- [基准测试](#benchmark)
- [毒药](#poisons)
- [中毒范围](#poisoning-scopes)
- [中毒阶段](#poisoning-phases)
- [内置毒药](#built-in-poisons)
- [延迟](#latency)
- [注入响应](#inject-response)
- [带宽](#bandwidth)
- [速率限制](#rate-limit)
- [慢读](#slow-read)
- [慢打开](#slow-open)
- [慢关闭](#slow-close)
- [节流](#throttle)
- [中止连接](#abort-connection)
- [超时](#timeout)
- [如何编写毒药](#how-to-write-poisons)
- [规则](#rules)
- [内置规则](#built-in-rules)
- [概率](#probability)
- [时间阈值](#time-threshold)
- [方法](#method)
- [内容类型](#content-type)
- [请求头](#headers)
- [响应头](#response-headers)
- [请求体](#body)
- [响应体](#response-body)
- [响应状态](#response-status)
- [第三方规则](#third-party-rules)
- [如何编写规则](#how-to-write-rules)
- [编程 API](#programmatic-api)
- [HTTP API](#http-api)
- [使用](#usage)
- [授权](#authorization)
- [API](#api)
- [编程 API](#programmatic-api-1)
- [许可证](#license)
## 功能特性
- 全功能的 HTTP/S 代理(基于 [rocky](https://github.com/h2non/rocky) 和 [http-proxy](https://github.com/nodejitsu/node-http-proxy))
- 可入侵且优雅的编程 API(受 connect/express 启发)
- 用于外部管理和动态配置的管理 HTTP API
- 内置特色路由器,支持嵌套配置
- 分层且可组合的中毒机制,支持基于规则的过滤
- 分层中间件层(全局和路由范围)
- 可通过中间件轻松扩展(基于 connect/express 中间件)
- 支持入站和出站流量的中毒
- 内置毒药(带宽、错误、中止、延迟、慢读等)
- 基于规则的中毒(概率、HTTP 方法、请求头、请求体等)
- 支持第三方毒药和规则
- 通过中间件内置的负载均衡器和流量拦截器
- 继承自 [rocky](https://github.com/h2non/rocky) 的 API 和功能
- 与 connect/express(及其大部分中间件)兼容
- 可作为独立的 HTTP 代理运行
## 介绍
### 为什么使用 toxy?
市场上有一些其他类似 `toxy` 的解决方案,但大多数都没有提供合适的程序化控制,通常不易于入侵、配置,或者直接封闭于扩展性。
此外,这些解决方案中的大多数只在 TCP L3 层栈上操作,而不是提供高级抽象来覆盖 HTTP L7 协议特定领域和性质的常见需求,而 toxy 试图提供这一点。
toxy 带来了一个强大、可入侵且可扩展的解决方案,具有方便的抽象,同时没有失去适当的低级接口能力,以便轻松处理 HTTP 协议原语。
toxy 是基于组合、简单性和可扩展性原则设计的。通过其内置的分层领域特定中间件层,你可以轻松地根据自己需求扩展 toxy 的功能。
### 概念
`toxy` 引入了两个指令:毒药(poisons)和规则(rules)。
**毒药(Poisons)** 是感染入站或出站 HTTP 事务的特定逻辑(例如:注入延迟、回复错误)。一个 HTTP 事务可以被一个或多个毒药感染,并且这些毒药也可以配置为感染全局或路由级别的流量。
**规则(Rules)** 是一种匹配验证过滤器,它检查 HTTP 请求/响应,以确定给定某些规则时,HTTP 事务是否应被中毒(例如:如果请求头匹配、查询参数、方法、请求体等)。规则可以被重用并应用于入站和出站流量流,包括不同的范围:全局、路由或毒药级别。
### 工作原理```
↓ ( Incoming request ) ↓
↓ ||| ↓
↓ +-------------+ ↓
↓ | Toxy Router | ↓ -> Match the incoming request
↓ +-------------+ ↓
↓ ||| ↓
↓ +--------------------+ ↓
↓ | Incoming phase | ↓ -> The proxy receives the request from the client
↓ |~~~~~~~~~~~~~~~~~~~~| ↓
↓ | ---------------- | ↓
↓ | | Exec Rules | | ↓ -> Apply configured rules for the incoming request
↓ | ---------------- | ↓
↓ | ||| | ↓
↓ | ---------------- | ↓
↓ | | Exec Poisons | | ↓ -> If all rules passed, then poison the HTTP flow
↓ | ---------------- | ↓
↓ +~~~~~~~~~~~~~~~~~~~~+ ↓
↓ / \ ↓
↓ \ / ↓
↓ +--------------------+ ↓
↓ | HTTP dispatcher | ↓ -> Forward the HTTP traffic to the target server, either poisoned or not
↓ +--------------------+ ↓
↓ / \ ↓
↓ \ / ↓
↓ +--------------------+ ↓
↓ | Outgoing phase | ↓ -> Receives response from target server
↓ |~~~~~~~~~~~~~~~~~~~~| ↓
↓ | ---------------- | ↓
↓ | | Exec Rules | | ↓ -> Apply configured rules for the outgoing request
↓ | ---------------- | ↓
↓ | ||| | ↓
↓ | ---------------- | ↓
↓ | | Exec Poisons | | ↓ -> If all rules passed, then poison the HTTP flow before send it to the client
↓ | ---------------- | ↓
↓ +~~~~~~~~~~~~~~~~~~~~+ ↓
↓ ||| ↓
↓ ( Send to the client ) ↓ -> Finally, send the request to the client, either poisoned or not
```
## 用法
### 安装```
npm install toxy
```
### 示例
更多用例请参见 [examples](https://github.com/h2non/toxy/tree/master/examples) 目录。```js
var toxy = require('toxy')
var poisons = toxy.poisons
var rules = toxy.rules
// Create a new toxy proxy
var proxy = toxy()
// Default server to forward incoming traffic
proxy
.forward('http://httpbin.org')
// Register global poisons and rules
proxy
.poison(poisons.latency({ jitter: 500 }))
.rule(rules.probability(25))
// Register multiple routes
proxy
.get('/download/*')
.forward('http://files.myserver.net')
.poison(poisons.bandwidth({ bps: 1024 }))
.withRule(rules.headers({'Authorization': /^Bearer (.*)$/i }))
// Infect outgoing traffic only (after the server replied properly)
proxy
.get('/image/*')
.outgoingPoison(poisons.bandwidth({ bps: 512 }))
.withRule(rules.method('GET'))
.withRule(rules.timeThreshold({ duration: 1000, threshold: 1000 * 10 }))
.withRule(rules.responseStatus({ range: [ 200, 400 ] }))
proxy
.all('/api/*')
.poison(poisons.rateLimit({ limit: 10, threshold: 1000 }))
.withRule(rules.method(['POST', 'PUT', 'DELETE']))
// And use a different more permissive poison for GET requests
.poison(poisons.rateLimit({ limit: 50, threshold: 1000 }))
.withRule(rules.method('GET'))
// Handle the rest of the traffic
proxy
.all('/*')
.poison(poisons.slowClose({ delay: 1000 }))
.poison(poisons.slowRead({ bps: 128 }))
.withRule(rules.probability(50))
proxy.listen(3000)
console.log('Server listening on port:', 3000)
console.log('Test it:', 'http://localhost:3000/image/jpeg')
```
## 基准测试
详情请参见 [toxy/benchmark](https://github.com/h2non/toxy/tree/master/benchmark)。
## 毒药
毒药包含特定逻辑,用于在代理服务器中拦截、变异、包装、修改和/或取消HTTP事务。
毒药可以应用于传入或传出流量,甚至两者(参见[毒化阶段](#poisoning-phases))。
毒药可以组合并复用于不同的HTTP场景。
它们按FIFO顺序异步执行。
### 毒化范围
`toxy` 具有层次化设计,基于两个不同的作用域:`global` 和 `route`。
**全局**作用域指向代理服务器接收的所有传入HTTP流量,无论HTTP方法或路径如何。
**路由**作用域指向与特定HTTP动词和URI路径匹配的任何传入流量。
毒药可以插入到两种作用域中,这意味着您可以更精确地操作并限制毒化范围,例如,您可能只想对某些路由(如 `/download` 或 `/images`)应用带宽限制毒药。
参见 [routes.js](https://github.com/h2non/toxy/blob/master/examples/routes.js) 获取示例。
### 毒化阶段
毒药可以插入到传入或传出流量中,甚至两者。
**传入**毒化在代理已接收流量但尚未转发给目标服务器时应用。
**传出**毒化指的是已转发给目标服务器的流量,并且代理收到其响应,但该响应尚未发送给客户端。
这本质上意味着,您可以在请求转发到目标HTTP服务器之前或之后,或者在发送给客户端之前或之后,插入毒药来感染HTTP流量。
这允许您根据请求或服务器响应应用更好更精确的毒化。
例如,鉴于某些毒药(如 `inject error`)的性质,您可能希望根据目标服务器响应(例如,某个标头是否存在)来启用它。
参见 [poison-phases.js](https://github.com/h2non/toxy/blob/master/examples/poison-phases.js) 获取示例。
### 内置毒药
#### Latency
<table>
<tr>
<td><b>名称</b></td><td>latency</td>
</tr>
<tr>
<td><b>毒化阶段</b></td><td>incoming / outgoing</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>true</td>
</tr>
</table>
在响应中注入延迟抖动来感染HTTP流量。
**参数**:
- **options** `object`
- **jitter** `number` - 抖动值(毫秒)
- **max** `number` - 随机抖动最大值
- **min** `number` - 随机抖动最小值```js
toxy.poison(toxy.poisons.latency({ jitter: 1000 }))
// Or alternatively using a random value
toxy.poison(toxy.poisons.latency({ max: 1000, min: 100 }))
```
#### 注入响应
<table>
<tr>
<td><b>名称</b></td><td>inject</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入 / 传出</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>否(仅作为传入毒药)</td>
</tr>
</table>
在将请求发送到目标服务器之前,拦截请求并注入自定义响应。
用于注入服务器端产生的错误。
**参数**:
- **options** `object`
- **code** `number` - 响应 HTTP 状态码。默认值 `500`
- **headers** `object` - 可选的要发送的头部
- **body** `mixed` - 可选的要发送的正文数据。可以是 `buffer` 或 `string`
- **encoding** `string` - 正文编码。默认为 `utf8````js
toxy.poison(toxy.poisons.inject({
code: 503,
body: '{"error": "toxy injected error"}',
headers: {'Content-Type': 'application/json'}
}))
```
#### 带宽
<table>
<tr>
<td><b>名称</b></td><td>bandwidth</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入 / 传出</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>是</td>
</tr>
</table>
限制在特定时间范围内,通过传出 HTTP 流量在网络中发送的字节数。
此毒剂基本上是 [throttle](#throttle) 的别名。
**参数**:
- **options** `object`
- **bytes** `number` - 要发送的字节块大小。默认值 `1024`
- **threshold** `number` - 数据包时间范围(毫秒)。默认值 `1000````js
toxy.poison(toxy.poisons.bandwidth({ bytes: 512 }))
```
#### 速率限制
<table>
<tr>
<td><b>名称</b></td><td>rateLimit</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入 / 传出</td>
</tr>
<tr>
<td><b>是否到达服务器</b></td><td>true</td>
</tr>
</table>
限制代理在特定时间窗口内接收的请求数量。旨在测试 API 限流。会暴露典型的 `X-RateLimit-*` 头。
请注意,这是一个非常简单的速率限制实现,限制存储在内存中,因此完全是易失性的。在 [npm](https://www.npmjs.com/search?q=rate+limit) 上有许多功能丰富且一致的限流器实现,你可以作为毒药插入。你可能还会对[令牌桶算法](http://en.wikipedia.org/wiki/Token_bucket)感兴趣。
**参数**:
- **options** `object`
- **limit** `number` - 请求总数。默认为 `10`
- **threshold** `number` - 限制时间窗口(毫秒)。默认为 `1000`
- **message** `string` - 当达到限制时的可选错误消息。
- **code** `number` - 达到限制时的 HTTP 状态码。默认为 `429`。```js
toxy.poison(toxy.poisons.rateLimit({ limit: 5, threshold: 10 * 1000 }))
```
#### 慢速读取
<table>
<tr>
<td><b>名称</b></td><td>slowRead</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>incoming</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>true</td>
</tr>
</table>
缓慢读取传入的有效载荷数据包。仅适用于非GET请求。
**参数**:
- **options** `object`
- **chunk** `number` - 数据包块大小(字节)。默认为 `1024`
- **threshold** `number` - 限制阈值时间范围(毫秒)。默认为 `1000````js
toxy.poison(toxy.poisons.slowRead({ chunk: 2048, threshold: 1000 }))
```
#### Slow open
名称:`slowOpen`
<table>
<tr>
<td><b>名称</b></td><td>slowOpen</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>是</td>
</tr>
</table>
延迟HTTP连接就绪状态。
**参数**:
- **options** `object`
- **delay** `number` - 延迟连接的时间,以毫秒为单位。默认为 `1000````js
toxy.poison(toxy.poisons.slowOpen({ delay: 2000 }))
```
#### 缓慢关闭
<table>
<tr>
<td><b>名称</b></td><td>slowClose</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>入站 / 出站</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>true</td>
</tr>
</table>
延迟HTTP连接关闭信号(EOF)。
**参数**:
- **options** `object`
- **delay** `number` - 延迟时间(毫秒)。默认为 `1000````js
toxy.poison(toxy.poisons.slowClose({ delay: 2000 }))
```
#### Throttle
<table>
<tr>
<td><b>名称</b></td><td>throttle</td>
</tr>
<tr>
<td><b>中毒阶段</b></td><td>传入/传出</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>是</td>
</tr>
</table>
限制在特定阈值时间帧内通过网络发送的数据包数量。
**参数**:
- **options** `object`
- **chunk** `number` - 数据包块大小(字节)。默认为 `1024`
- **delay** `object` - 数据块延迟时间帧(毫秒)。默认为 `100````js
toxy.poison(toxy.poisons.throttle({ chunk: 2048, threshold: 1000 }))
```
#### 中止连接
<table>
<tr>
<td><b>名称</b></td><td>abort</td>
</tr>
<tr>
<td><b>中毒阶段</b></td><td>传入/传出</td>
</tr>
<tr>
<td><b>是否到达服务器</b></td><td>false(仅作为传入中毒)</td>
</tr>
</table>
中止 TCP 连接。从底层视角来看,这将销毁服务器上的套接字,仅工作在 TCP 层面,不发送任何特定的 HTTP 应用层数据。
**参数**:
- **options** `object`
- **delay** `number` - 在等待指定的毫秒后中止 TCP 连接。默认值为 `0`
- **next** `boolean` - 如果为 `true`,当目标服务器响应时间超过 `delay` 参数时,连接将被中止。默认值为 `false`
- **error** `Error` - 销毁套接字时使用的自定义 Node.js 内部错误。默认值为 `null````js
// Basic connection abort
toxy.poison(toxy.poisons.abort())
// Abort after a delay
toxy.poison(toxy.poisons.abort(1000))
// In this case, the socket will be closed if
// the target server takes more than
// 2 seconds to respond
toxy.poison(toxy.poisons.abort({ delay: 2000, next: true }))
```
#### 超时
<table>
<tr>
<td><b>名称</b></td><td>timeout</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>入站 / 出站</td>
</tr>
<tr>
<td><b>到达服务器</b></td><td>true</td>
</tr>
</table>
定义响应超时。当转发到可能较慢的服务器时很有用。
**参数**:
- **milliseconds** `number` - 超时时间(毫秒)```js
toxy.poison(toxy.poisons.timeout(5000))
```
### 如何编写毒药
毒药作为标准中间件函数实现,具有与 connect/express 中间件相同的接口。
有些毒药实现起来并非易事,因此你需要熟悉 node.js [http](https://nodejs.org/api/http.html) 模块及其 API。
以下是一个简单的服务器延迟毒药示例:```js
var toxy = require('toxy')
function customLatencyPoison (delay) {
// We name the function since toxy uses it as identifier to get/disable/remove it in the future
return function customLatency (req, res, next) {
var timeout = setTimeout(process, delay)
req.once('close', onClose)
function onClose () {
clearTimeout(timeout)
next('client connection closed')
}
function process () {
req.removeListener('close', onClose)
next()
}
}
}
var proxy = toxy()
// Register and enable the poison
proxy
.get('/foo')
.poison(customLatencyPoison(2000))
```
你可以选择使用自己的恶意载荷来扩展内置的恶意载荷:```js
toxy.addPoison(customLatency)
// Then you can use it as a built-in poison
proxy
.get('/foo')
.poison(toxy.poisons.customLatency)
```
关于具体的真实例子,请参阅[内置投毒](https://github.com/h2non/toxy/tree/master/lib/poisons)的实现。
## 规则
规则是简单的验证过滤器,它检查传入或传出的HTTP流量,以根据规则的解析值确定当前HTTP事务是否应该被投毒。
规则有助于在不同投毒场景中组合、解耦和重用逻辑。
规则可以应用于全局、路由甚至投毒作用域,并且也适用于两个[投毒阶段](#poisoning-phases)。
规则按先进先出顺序执行。它们的评估逻辑等价于 JavaScript 中的 `Array#every()`:必须通过所有规则才能继续投毒。
### 内置规则
#### 概率
<table>
<tr>
<td><b>名称</b></td><td>概率</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入/传出</td>
</tr>
</table>
通过随机概率启用规则。用于随机投毒。
**参数**:
- **percentage** `number` - 过滤百分比。默认 `50````js
var rule = toxy.rules.probability(85)
toxy.rule(rule)
```
#### 时间阈值
<table>
<tr>
<td><b>名称</b></td><td>timeThreshold</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>incoming / outgoing</td>
</tr>
</table>
基于特定时间阈值和持续时间启用投毒的简单规则。
例如,你可以在一个时间阈值(如1分钟)内,在特定时间长度(如1秒)内启用某些投毒。
**参数**:
- **options** `object`
- **duration** `number` - 启用时间间隔(毫秒)。默认为 `1000`
- **threshold** `number` - 重新启用投毒前等待的时间阈值(毫秒)。默认为 `10000````js
// Enable the poisoning only 100 milliseconds per each 10 seconds
proxy.rule(toxy.rules.timeThreshold(100))
// Enable poisoning during 1 second every minute
proxy.rule(toxy.rules.timeThreshold({ duration: 1000, period: 1000 * 60 }))
```
#### 方法
<table>
<tr>
<td><b>名称</b></td><td>method</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入 / 传出</td>
</tr>
</table>
按 HTTP 方法过滤。
**参数**:
- **method** `string|array` - 要过滤的方法或方法列表。```js
var method = toxy.rules.method(['GET', 'POST'])
toxy.rule(method)
```
#### 内容类型
按内容类型标头过滤。它应该存在。
**参数**:
- **value** `string|regexp` - 要匹配的标头值。```js
var rule = toxy.rules.contentType('application/json')
toxy.rule(rule)
```
#### 请求头
<table>
<tr>
<td><b>名称</b></td><td>请求头</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>入站 / 出站</td>
</tr>
</table>
按请求头进行过滤。
**参数**:
- **headers** `object` - 通过键值对匹配的请求头。`value` 可以是字符串、正则表达式、`boolean` 或 `function(headerValue, headerName) => boolean````js
var matchHeaders = {
'content-type': /^application/\json/i,
'server': true, // meaning it should be present,
'accept': function (value, key) {
return value.indexOf('text') !== -1
}
}
var rule = toxy.rules.headers(matchHeaders)
toxy.rule(rule)
```
#### 响应头
<table>
<tr>
<td><b>名称</b></td><td>responseHeaders</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>outgoing</td>
</tr>
</table>
按目标服务器的响应头进行过滤。与 `headers` 规则相同,但评估的是出站请求。
**参数**:
- **headers** `object` - 通过键值对匹配的头部。`value` 可以是 `string`、`regexp`、`boolean` 或 `function(headerValue, headerName) => boolean````js
var matchHeaders = {
'content-type': /^application/\json/i,
'server': true, // meaning it should be present,
'accept': function (value, key) {
return value.indexOf('text') !== -1
}
}
var rule = toxy.rules.responseHeaders(matchHeaders)
toxy.rule(rule)
```
#### Body
<table>
<tr>
<td><b>名称</b></td><td>body</td>
</tr>
<tr>
<td><b>投毒阶段</b></td><td>传入 / 传出</td>
</tr>
</table>
通过给定的 `string`、`regexp` 或自定义过滤器 `function` 匹配传入的消息体。
此规则非常简单,对于复杂的消息体匹配(例如:根据 JSON 模式验证),您可能需要编写自己的规则。
**参数**:
- **match** `string|regexp|function` - 要匹配的消息体内容
- **limit** `string` - 可选。消息体大小限制,使用人类可读格式。例如:`5mb`
- **encoding** `string` - 消息体编码。默认值为 `utf8`
- **length** `number` - 消息体长度。默认从 `Content-Length` 头部获取```js
var rule = toxy.rules.body('"hello":"world"')
toxy.rule(rule)
// Or using a filter function returning a boolean
var rule = toxy.rules.body(function contains(body) {
return body.indexOf('hello') !== -1
})
toxy.rule(rule)
```
#### 响应体
<table>
<tr>
<td><b>名称</b></td><td>responseBody</td>
</tr>
<tr>
<td><b>毒化阶段</b></td><td>出站</td>
</tr>
</table>
通过给定的`string`、`regexp`或自定义过滤器`function`匹配出站正文载荷。
**参数**:
- **match** `string|regexp|function` - 要匹配的正文内容
- **encoding** `string` - 正文编码。默认为`utf8`
- **length** `number` - 正文长度。默认取自`Content-Length`头部```js
var rule = toxy.rules.responseBody('"hello":"world"')
toxy.rule(rule)
// Or using a filter function returning a boolean
var rule = toxy.rules.responseBody(function contains(body) {
return body.indexOf('hello') !== -1
})
toxy.rule(rule)
```
#### 响应状态
<table>
<tr>
<td><b>名称</b></td><td>responseStatus</td>
</tr>
<tr>
<td><b>毒化阶段</b></td><td>outgoing</td>
</tr>
</table>
评估来自目标服务器的响应状态。
仅适用于出站毒化。
**参数**:
- **range** `array` - 要匹配的状态码范围对。默认为 `[200, 300]`。
- **lower** `number` - 将状态比较为“小于”操作。默认为 `null`。
- **higher** `number` - 将状态比较为“大于”操作。默认为 `null`。
- **value** `number` - 使用严格相等比较来匹配的状态码。默认为 `null`。
- **include** `array` - 要匹配的状态码无序列表。用于指定自定义状态。默认为 `null`。```js
// Strict evaluation of the status code
toxy.rule(toxy.rules.responseBody(200))
// Using a range of valid status
toxy.rule(toxy.rules.responseBody([200, 204]))
// Using relational comparison
toxy.rule(toxy.rules.responseBody({ higher: 199, lower: 400 }))
// Custom unordered status code to match
toxy.rule(toxy.rules.responseBody({ include: [200, 204, 400, 404] }))
```
### 第三方规则
社区提供的可用第三方规则列表。欢迎提交 PR。
- [IP](https://github.com/h2non/toxy-ip) - 根据客户端 IP 地址启用/禁用毒药(支持 CIDR、子网、范围等)。
### 如何编写规则
规则是简单的中间件函数,它们异步解析出一个 `boolean` 值,用于判断在投毒时是否应忽略某个 HTTP 事务。
您的规则必须通过中间件中的 `next(err, shouldIgnore)` 函数解析出一个 `boolean` 参数,如果规则不匹配且不应施加毒药,则传递 `true` 值,从而继续执行下一个中间件堆栈。
以下是一个简单规则的示例,它匹配 HTTP 方法以判断是否:```js
var toxy = require('toxy')
function customMethodRule(matchMethod) {
/**
* We name the function since it's used by toxy to identify the rule to get/disable/remove it in the future
*/
return function customMethodRule(req, res, next) {
var shouldIgnore = req.method !== matchMethod
next(null, shouldIgnore)
}
}
var proxy = toxy()
// Register and enable the rule
proxy
.get('/foo')
.rule(customMethodRule('GET'))
.poison(/* ... */)
```
您可以选择用您自己的规则来扩展内置规则:```js
toxy.addRule(customMethodRule)
// Then you can use it as a built-in poison
proxy
.get('/foo')
.rules(toxy.rules.customMethodRule)
```
有关特色真实示例,请查看内置规则 [实现](https://github.com/h2non/toxy/tree/master/lib/rules)
## 程序化 API
`toxy` API 完全构建在 [rocky API](https://github.com/h2non/rocky#programmatic-api) 之上。换句话说,您可以使用 `rocky` 原生提供的任何方法、功能和中间件层。
### toxy([ options ])
创建一个新的 `toxy` 代理。
有关支持的 `options`,请参阅 rocky [文档](https://github.com/h2non/rocky#configuration)```js
var toxy = require('toxy')
toxy({ forward: 'http://server.net', timeout: 30000 })
toxy
.get('/foo')
.poison(toxy.poisons.latency(1000))
.withRule(toxy.rules.contentType('json'))
.forward('http://foo.server')
toxy
.post('/bar')
.poison(toxy.poisons.bandwidth({ bps: 1024 }))
.withRule(toxy.rules.probability(50))
.forward('http://bar.server')
toxy
.post('/boo')
.outgoingPoison(toxy.poisons.bandwidth({ bps: 1024 }))
.withRule(toxy.rules.method('GET'))
.forward('http://boo.server')
toxy.all('/*')
toxy.listen(3000)
```
#### toxy#get(path, [ middleware... ])
返回:`ToxyRoute`
为 `GET` 方法注册一个新路由。
#### toxy#post(path, [ middleware... ])
返回:`ToxyRoute`
为 `POST` 方法注册一个新路由。
#### toxy#put(path, [ middleware... ])
返回:`ToxyRoute`
为 `PUT` 方法注册一个新路由。
#### toxy#patch(path, [ middleware... ])
返回:`ToxyRoute`
#### toxy#delete(path, [ middleware... ])
返回:`ToxyRoute`
为 `DELETE` 方法注册一个新路由。
#### toxy#head(path, [ middleware... ])
返回:`ToxyRoute`
为 `HEAD` 方法注册一个新路由。
#### toxy#all(path, [ middleware... ])
返回:`ToxyRoute`
为任意方法注册一个新路由。
#### toxy#poisons `=>` Object
暴露一个包含内置毒药(poisons)的映射。原型别名为 `toxy.poisons`
#### toxy#rules `=>` Object
暴露一个包含内置毒药的映射。原型别名为 `toxy.rules`
#### toxy#forward(url)
定义一个 URL,用于转发代理接收到的传入流量。
#### toxy#balance(urls)
转发到多个服务器并在它们之间进行负载均衡。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#programmatic-api)
#### toxy#replay(url)
定义一个新的重放服务器。
你可以多次调用此方法以定义多个重放服务器。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#programmatic-api)
#### toxy#use(middleware)
插入一个自定义中间件。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#middleware-layer).
#### toxy#useResponse(middleware)
插入一个响应传出流量中间件。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#middleware-layer).
#### toxy#useReplay(middleware)
插入一个重放流量中间件。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#middleware-layer)
#### toxy#requestBody(middleware)
拦截传入请求体。可用于即时修改请求体。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#programmatic-api)
#### toxy#responseBody(middleware)
拦截传出响应体。可用于即时修改响应体。
更多信息,请参阅 [rocky 文档](https://github.com/h2non/rocky#programmatic-api)
#### toxy#middleware()
返回一个标准中间件,用于与 connect/express 配合使用。
#### toxy#host(host)
用自定义值覆盖 `Host` 头。类似 `forwardHost` 选项。
#### toxy#redirect(url)
将流量重定向到给定的 URL。
#### toxy#findRoute(routeIdOrPath, [ method ])
通过路由 ID 或路径及方法查找路由。
#### toxy#listen(port)
启动内置 HTTP 服务器,监听特定 TCP 端口。
#### toxy#close([ callback ])
关闭 HTTP 服务器。
#### toxy#poison(poison)
别名:`usePoison`, `useIncomingPoison`
注册一个新的毒药,用于感染 [传入](#poisoning-phases) 流量。
#### toxy#outgoingPoison(poison)
别名:`useOutgoingPoison`, `responsePoison`
注册一个新的毒药,用于感染 [传出](#poisoning-phases) 流量。
#### toxy#rule(rule)
别名:`useRule`
注册一个新规则。
#### toxy#withRule(rule)
别名:`ifRule`, `whenRule`, `poisonRule`, `poisonFilter`
为最近注册的毒药应用一个新规则。
#### toxy#enable(poison)
通过名称标识符启用一个毒药。
#### toxy#disable(poison)
通过名称标识符禁用一个毒药。
#### toxy#remove(poison)
返回:`boolean`
通过名称标识符或对象引用移除一个传入流量毒药。
#### toxy#removeOutgoing(poison)
返回:`boolean`
通过名称标识符或对象引用移除一个传出流量毒药。
#### toxy#isEnabled(poison)
返回:`boolean`
通过名称标识符检查毒药是否已启用。
#### toxy#disableAll()
别名:`disablePoisons`
禁用所有已注册的毒药。
#### toxy#getPoison(name)
返回:`Directive|null`
在栈中通过名称标识符搜索并获取已注册的毒药。
#### toxy#getIncomingPoison(name)
返回:`Directive|null`
在栈中通过名称标识符搜索并获取已注册的 `incoming` 毒药。
#### toxy#getOutgoingPoison(name)
返回:`Directive|null`
在栈中通过名称标识符搜索并获取已注册的 `outgoing` 毒药。
#### toxy#getPoisons()
返回:`array<Directive>`
返回已注册毒药的数组。
#### toxy#getIncomingPoisons()
返回:`array<Directive>`
返回已注册 `incoming` 毒药的数组。
#### toxy#getOutgoingPoisons()
返回:`array<Directive>`
返回已注册 `outgoing` 毒药的数组。
#### toxy#flush()
别名:`flushPoisons`
移除所有已注册的传入和传出流量毒药。
#### toxy#enableRule(rule)
通过名称标识符启用一个规则。
#### toxy#disableRule(rule)
通过名称标识符禁用一个规则。
#### toxy#removeRule(rule)
返回:`boolean`
通过名称标识符移除一个规则。
#### toxy#disableRules()
禁用所有已注册的规则。
#### toxy#isRuleEnabled(rule)
返回:`boolean`
通过名称标识符检查给定规则是否已启用。
#### toxy#getRule(rule)
返回:`Directive|null`
在栈中通过名称标识符搜索并获取已注册的规则。
#### toxy#getRules()
返回:`array<Directive>`
返回以 `Directive` 包装的已注册规则的数组。
#### toxy#flushRules()
移除所有规则。
### toxy.addPoison(name, fn)
扩展内置毒药。
### toxy.addRule(name, fn)
扩展内置规则。
### toxy.poisons `=>` Object
暴露一个包含内置毒药的映射。
### toxy.rules `=>` Object
暴露一个包含内置规则的映射。
### toxy.VERSION `=>` String
当前 toxy 的语义化版本。
### ToxyRoute
`ToxyRoute` 暴露与 `Toxy` 全局接口相同的接口,只是额外添加了一些路由级别的 [附加方法](https://github.com/h2non/rocky#routepath)。
你对 `ToxyRoute` API 执行的后续操作仅适用于路由级别(嵌套)。换句话说:你已经了解该 API。
这个例子可能会澄清可能的疑问:```js
var toxy = require('toxy')
var proxy = toxy()
// Now using the global API
proxy
.forward('http://server.net')
.poison(toxy.poisons.bandwidth({ bps: 1024 }))
.rule(toxy.rules.method('GET'))
// Now create a route
var route = proxy
.get('/foo')
.toPath('/bar') // Route-level API method
.host('server.net') // Route-level API method
.forward('http://new.server.net')
// Now using the ToxyRoute interface
route
.poison(toxy.poisons.bandwidth({ bps: 512 }))
.rule(toxy.rules.contentType('json'))
```
### Directive(middlewareFn)
一个便捷的包装器,内部用于毒药(poisons)和规则(rules)。
通常情况下你不需要了解这个接口,但对于黑客目的或更底层的操作可能很有用。
#### Directive#enable()
返回值:`boolean`
#### Directive#disable()
返回值:`boolean`
#### Directive#isEnabled()
返回值:`boolean`
#### Directive#rule(rule)
别名:`filter`
#### Directive#handler()
返回值:`function(req, res, next)`
## HTTP API
`toxy` 的 HTTP API 遵循 [JSON API](http://jsonapi.org) 约定,包括基于资源的超媒体链接。
### 用法
关于特色用例,请参阅 [admin server](https://github.com/h2non/toxy/blob/master/examples/admin.js) 示例。```js
const toxy = require('toxy')
// Create the toxy admin server
var admin = toxy.admin({ cors: true })
admin.listen(9000)
// Create the toxy proxy
var proxy = toxy()
proxy.listen(3000)
// Add the toxy instance to be managed by the admin server
admin.manage(proxy)
// Then configure the proxy
proxy
.forward('http://my.target.net')
proxy
.get('/slow')
.poison(toxy.poisons.bandwidth({ bps: 1024 }))
// Handle the rest of the traffic
proxy
.all('/*')
.poison(toxy.poisons.bandwidth({ bps: 1024 * 5 }))
console.log('toxy proxy listening on port:', 3000)
console.log('toxy admin server listening on port:', 9000)
```
有关管理程序化API的更多详情,请参见[下文](#programmatic-api-1)。
### 授权
HTTP API 可防范未授权客户端的访问。
授权客户端必须通过 `API-Key` 或 `Authorization` HTTP 标头定义 API 密钥令牌。
要启用此功能,您只需将以下选项传递给 `toxy` 管理服务器:```js
const toxy = require('toxy')
const opts = { apiKey: 's3cr3t' }
var admin = toxy.admin(opts)
admin.listen(9000)
console.log('protected toxy admin server listening on port:', 9000)
```
### API
**层次结构**:
- **服务器** - 托管的 `toxy` 实例
- **规则** - 全局应用的规则
- **毒药** - 全局应用的毒药
- **规则** - 毒药特定的规则
- **路由** - 已配置路由的列表
- **路由** - 每个特定路由的对象
- **规则** - 路由级别注册的规则
- **毒药** - 路由级别注册的毒药
- **规则** - 路由级别毒药特定的规则
#### GET /
### 服务器
#### GET /servers
#### GET /servers/:id
### 规则
#### GET /servers/:id/rules
#### POST /servers/:id/rules
**接受**:`application/json`
示例载荷:```js
{
"name": "method",
"options": "GET"
}
```
#### DELETE /servers/:id/rules
#### GET /servers/:id/rules/:id
#### DELETE /servers/:id/rules/:id
### 毒化
#### GET /servers/:id/poison
#### POST /servers/:id/poisons
接受:`application/json`
示例负载:```js
{
"name": "latency",
"phase": "outgoing",
"options": { "jitter": 1000 }
}
```
#### DELETE /servers/:id/poisons
#### GET /servers/:id/poisons/:id
#### DELETE /servers/:id/poisons/:id
#### GET /servers/:id/poisons/:id/rules
#### POST /servers/:id/poisons/:id/rules
接受: `application/json`
示例负载:```js
{
"name": "method",
"options": "GET"
}
```
#### 删除 /servers/:id/poisons/:id/rules
#### 获取 /servers/:id/poisons/:id/rules/:id
#### 删除 /servers/:id/poisons/:id/rules/:id
### 路由
#### 获取 /servers/:id/routes
#### 新增 /servers/:id/routes
接受: `application/json`
示例负载:```js
{
"path": "/foo", // Required
"method": "GET", // use ALL for all the methods
"forward": "http://my.server", // Optional custom forward server URL
}
```
#### DELETE /servers/:id/routes
#### GET /servers/:id/routes/:id
#### DELETE /servers/:id/routes/:id
### 路由规则
#### GET /servers/:id/routes/:id/rules
#### POST /servers/:id/routes/:id/rules
接受:`application/json`
示例负载:```js
{
"name": "method",
"options": "GET"
}
```
#### DELETE /servers/:id/routes/:id/rules
#### GET /servers/:id/routes/:id/rules/:id
#### DELETE /servers/:id/routes/:id/rules/:id
### 路由毒化
#### GET /servers/:id/routes/:id/poisons
#### POST /servers/:id/routes/:id/poisons
接受:`application/json`
示例负载:```js
{
"name": "latency",
"phase": "outgoing",
"options": { "jitter": 1000 }
}
```
#### DELETE /servers/:id/routes/:id/poisons
#### GET /servers/:id/routes/:id/poisons/:id
#### DELETE /servers/:id/routes/:id/poisons/:id
#### GET /servers/:id/routes/:id/poisons/:id/rules
#### POST /servers/:id/routes/:id/poisons/:id/rules
接受: `application/json`
示例载荷:```js
{
"name": "method",
"options": "GET"
}
```
#### DELETE /servers/:id/routes/:id/poisons/:id/rules
#### GET /servers/:id/routes/:id/poisons/:id/rules/:id
#### DELETE /servers/:id/routes/:id/poisons/:id/rules/:id
### 编程接口
内置的HTTP管理服务器还提供了一个简单的接口,用于扩展和定制。例如,你可以向管理服务器添加额外的中间件,或注册新的路由。
#### toxy.admin([ opts ])
返回:`Admin`
**支持的选项**:
- **apiKey** `string` - 保护服务器的可选API密钥
- **port** `number` - 可选。监听的TCP端口
- **cors** `boolean` - 启用CORS以便浏览器访问
- **middleware** `array<function>` - 插入额外的中间件
- **ssl** `object` - Node.js HTTPS服务器的[TLS选项](https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener)
##### Admin#listen([ port, host ])
开始在网络中监听。
##### Admin#manage(toxy)
管理一个`toxy`服务器实例。
##### Admin#find(toxy)
查找一个toxy实例。接受toxy服务器ID或toxy实例。
##### Admin#remove(toxy)
停止管理一个toxy实例。
##### Admin#use(...middleware)
注册一个中间件。
##### Admin#param(...middleware)
注册一个参数中间件。
##### Admin#get(path, [ ...middleware ])
注册一个GET路由。
##### Admin#post(path, [ ...middleware ])
注册一个POST路由。
##### Admin#put(path, [ ...middleware ])
注册一个PUT路由。
##### Admin#delete(path, [ ...middleware ])
注册一个DELETE路由。
##### Admin#patch(path, [ ...middleware ])
注册一个PATCH路由。
##### Admin#all(path, [ ...middleware ])
注册一个接受任何HTTP方法的路由。
##### Admin#middleware(req, res, next)
与connect/express一起使用的中间件。
##### Admin#close(cb)
停止服务器。
## 许可证
MIT - Tomas Aparicio
[](https://sourcegraph.com/github.com/h2non/toxy)