GithubHelp home page GithubHelp logo

h2non / toxy Goto Github PK

View Code? Open in Web Editor NEW
2.7K 50.0 99.0 308 KB

Hackable HTTP proxy for resiliency testing and simulated network conditions

License: MIT License

JavaScript 98.39% Shell 1.61%
http-proxy proxy reactive failure simulation network resiliency failover testing retry

toxy's Introduction

toxy Build Status Code Climate NPM js-standard-style

Not actively maintained, may not work with latest node.js runtimes. If you are interested in maintaining toxy, please open an issue.

Hackable HTTP proxy to simulate server failure scenarios, systems resiliency testing and unexpected network conditions, built for node.js.

It was mainly designed for failure resistance testing, when toxy becomes particularly useful in order to cover fault tolerance and resiliency capabilities of a system, especially in disruption-tolerant networks and service-oriented architectures, where toxy may act as MitM proxy among services in order to inject failure.

toxy allows you to plug in poisons, optionally filtered by rules, which essentially can intercept and alter the HTTP flow as you need, performing multiple evil actions in the middle of that process, such as limiting the bandwidth, delaying network packets, injecting network jitter latency or replying with a custom error or status code. It primarily operates at L7, although it can simulate L3 network conditions.

toxy can be fluently used programmatically or via HTTP API. It was built on top of rocky, a full-featured middleware-oriented HTTP proxy, and it's also pluggable in connect/express as standard middleware.

Requires node.js +4.

Contents

Features

  • Full-featured HTTP/S proxy (backed by rocky and http-proxy)
  • Hackable and elegant programmatic API (inspired on connect/express)
  • Admin HTTP API for external management and dynamic configuration
  • Featured built-in router with nested configuration
  • Hierarchical and composable poisoning with rule based filtering
  • Hierarchical middleware layer (both global and route scopes)
  • Easily augmentable via middleware (based on connect/express middleware)
  • Supports both incoming and outgoing traffic poisoning
  • Built-in poisons (bandwidth, error, abort, latency, slow read...)
  • Rule-based poisoning (probabilistic, HTTP method, headers, body...)
  • Supports third-party poisons and rules
  • Built-in balancer and traffic interceptor via middleware
  • Inherits API and features from rocky
  • Compatible with connect/express (and most of their middleware)
  • Able to run as standalone HTTP proxy

Introduction

Why toxy?

There're some other similar solutions like toxy in the market, but most of them do not provide a proper programmatic control and usually are not easy to hack, configure or are directly closed to extensibility.

Furthermore, the majority of those solutions only operates at TCP L3 level stack instead of providing high-level abstractions to cover common requirements in the specific domain and nature of the HTTP L7 protocol, like toxy tries to provide

toxy brings a powerful hackable and extensible solution with a convenient abstraction, but without losing a proper low-level interface capabilities to deal with HTTP protocol primitives easily.

toxy was designed based on the rules of composition, simplicity and extensibility. Via its built-in hierarchical domain specific middleware layer you can easily augment toxy features to your own needs.

Concepts

toxy introduces two directives: poisons and rules.

Poisons are the specific logic which infects an incoming or outgoing HTTP transaction (e.g: injecting a latency, replying with an error). One HTTP transaction can be poisoned by one or multiple poisons, and those poisons can be also configured to infect both global or route level traffic.

Rules are a kind of match validation filters that inspects an HTTP request/response in order to determine, given a certain rules, if the HTTP transaction should be poisoned or not (e.g: if headers matches, query params, method, body...). Rules can be reused and applied to both incoming and outgoing traffic flows, including different scopes: global, route or poison level.

How it works

↓  ( 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

Usage

Installation

npm install toxy

Examples

See examples directory for more use cases.

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')

Benchmark

See toxy/benchmark for details.

Poisons

Poisons host specific logic which intercepts and mutates, wraps, modify and/or cancel an HTTP transaction in the proxy server. Poisons can be applied to incoming or outgoing, or even both traffic flows (see poison phases).

Poisons can be composed and reused for different HTTP scenarios. They are executed in FIFO order and asynchronously.

Poisoning scopes

toxy has a hierarchical design based on two different scopes: global and route.

Global scope points to all the incoming HTTP traffic received by the proxy server, regardless of the HTTP method or path.

Route scope points to any incoming traffic which matches with a specific HTTP verb and URI path.

Poisons can be plugged to both scopes, meaning you can operate with better accuracy and restrict the scope of the poisoning, for instance, you might wanna apply a bandwidth limit poisoning only to a certain routes, such as /download or /images.

See routes.js for a featured example.

Poisoning phases

Poisons can be plugged to incoming or outgoing traffic flows, or even both.

Incoming poisoning is applied when the traffic has been received by proxy but it has not been forwarded to the target server yet.

Outgoing poisoning refers to the traffic that has been forwarded to the target server and when proxy receives the response from it, but that response has not been sent to the client yet.

This means, essentially, that you can plug in your poisons to infect the HTTP traffic before or after the request is forwarded to the target HTTP server or sent to the client.

This allows you apply a better and more accurated poisoning based on the request or server response. For instance, given the nature of some poisons, like inject error, you may want to enable it according to the target server response (e.g: some header is present or not).

See poison-phases.js for a featured example.

Built-in poisons

Latency

Namelatency
Poisoning Phaseincoming / outgoing
Reaches the servertrue

Infects the HTTP flow injecting a latency jitter in the response

Arguments:

  • options object
    • jitter number - Jitter value in milliseconds
    • max number - Random jitter maximum value
    • min number - Random jitter minimum value
toxy.poison(toxy.poisons.latency({ jitter: 1000 }))
// Or alternatively using a random value
toxy.poison(toxy.poisons.latency({ max: 1000, min: 100 }))

Inject response

Nameinject
Poisoning Phaseincoming / outgoing
Reaches the serverfalse (only as incoming poison)

Injects a custom response, intercepting the request before sending it to the target server. Useful to inject errors originated in the server.

Arguments:

  • options object
    • code number - Response HTTP status code. Default 500
    • headers object - Optional headers to send
    • body mixed - Optional body data to send. It can be a buffer or string
    • encoding string - Body encoding. Default to utf8
toxy.poison(toxy.poisons.inject({
  code: 503,
  body: '{"error": "toxy injected error"}',
  headers: {'Content-Type': 'application/json'}
}))

Bandwidth

Namebandwidth
Poisoning Phaseincoming / outgoing
Reaches the servertrue

Limits the amount of bytes sent over the network in outgoing HTTP traffic for a specific time frame.

This poison is basically an alias to throttle.

Arguments:

  • options object
    • bytes number - Amount of chunk of bytes to send. Default 1024
    • threshold number - Packets time frame in milliseconds. Default 1000
toxy.poison(toxy.poisons.bandwidth({ bytes: 512 }))

Rate limit

NamerateLimit
Poisoning Phaseincoming / outgoing
Reaches the servertrue

Limits the amount of requests received by the proxy in a specific threshold time frame. Designed to test API limits. Exposes typical X-RateLimit-* headers.

Note that this is very simple rate limit implementation, indeed limits are stored in-memory, therefore are completely volatile. There're a bunch of featured and consistent rate limiter implementations in npm that you can plug in as poison. You might be also interested in token bucket algorithm.

Arguments:

  • options object
    • limit number - Total amount of requests. Default to 10
    • threshold number - Limit time frame in milliseconds. Default to 1000
    • message string - Optional error message when limit is reached.
    • code number - HTTP status code when limit is reached. Default to 429.
toxy.poison(toxy.poisons.rateLimit({ limit: 5, threshold: 10 * 1000 }))

Slow read

NameslowRead
Poisoning Phaseincoming
Reaches the servertrue

Reads incoming payload data packets slowly. Only valid for non-GET request.

Arguments:

  • options object
    • chunk number - Packet chunk size in bytes. Default to 1024
    • threshold number - Limit threshold time frame in milliseconds. Default to 1000
toxy.poison(toxy.poisons.slowRead({ chunk: 2048, threshold: 1000 }))

Slow open

Name: slowOpen

NameslowOpen
Poisoning Phaseincoming
Reaches the servertrue

Delays the HTTP connection ready state.

Arguments:

  • options object
    • delay number - Delay connection in milliseconds. Default to 1000
toxy.poison(toxy.poisons.slowOpen({ delay: 2000 }))

Slow close

NameslowClose
Poisoning Phaseincoming / outgoing
Reaches the servertrue

Delays the HTTP connection close signal (EOF).

Arguments:

  • options object
    • delay number - Delay time in milliseconds. Default to 1000
toxy.poison(toxy.poisons.slowClose({ delay: 2000 }))

Throttle

Namethrottle
Poisoning Phaseincoming / outgoing
Reaches the servertrue

Restricts the amount of packets sent over the network in a specific threshold time frame.

Arguments:

  • options object
    • chunk number - Packet chunk size in bytes. Default to 1024
    • delay object - Data chunk delay time frame in milliseconds. Default to 100
toxy.poison(toxy.poisons.throttle({ chunk: 2048, threshold: 1000 }))

Abort connection

Nameabort
Poisoning Phaseincoming / outgoing
Reaches the serverfalse (only as incoming poison)

Aborts the TCP connection. From the low-level perspective, this will destroy the socket on the server, operating only at TCP level without sending any specific HTTP application level data.

Arguments:

  • options object
    • delay number - Aborts TCP connection after waiting the given milliseconds. Default to 0
    • next boolean - If true, the connection will be aborted if the target server takes more than the delay param time to reply. Default to false
    • error Error - Custom internal node.js error to use when destroying the socket. Default to null
// 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 }))

Timeout

Nametimeout
Poisoning Phaseincoming / outgoing
Reaches the servertrue

Defines a response timeout. Useful when forward to potentially slow servers.

Arguments:

  • milliseconds number - Timeout limit in milliseconds
toxy.poison(toxy.poisons.timeout(5000))

How to write poisons

Poisons are implemented as standard middleware function with the same interface as connect/express middleware.

Some poisons are not trivial to implement so you've to be familiar with node.js http module and its API.

Here's a simple example of a server latency poison:

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))

You can optionally extend the build-in poisons with your own poisons:

toxy.addPoison(customLatency)

// Then you can use it as a built-in poison
proxy
  .get('/foo')
  .poison(toxy.poisons.customLatency)

For featured real example, take a look to the built-in poisons implementation.

Rules

Rules are simple validation filters which inspects an incoming or outgoing HTTP traffic in order to determine, given a certain rules (e.g: matches the method, headers, query params, body...), if the current HTTP transaction should be poisoned or not, based on the resolution value of the rule.

Rules are useful to compose, decouple and reuse logic among different scenarios of poisoning. Rules can be applied to global, route or even poison scope, and it also applies to both phases of poisoning.

Rules are executed in FIFO order. Their evaluation logic is equivalent to Array#every() in JavaScript: all the rules must pass in order to proceed with the poisoning.

Built-in rules

Probability

Nameprobability
Poison Phaseincoming / outgoing

Enables the rule by a random probabilistic. Useful for random poisoning.

Arguments:

  • percentage number - Percentage of filtering. Default 50
var rule = toxy.rules.probability(85)
toxy.rule(rule)

Time threshold

NametimeThreshold
Poison Phaseincoming / outgoing

Simple rule to enable poisons based on a specific time threshold and duration. For instance, you can enable a certain poisons during a specific amount of time (e.g: 1 second) within a time threshold (e.g: 1 minute).

Arguments:

  • options object
    • duration number - Enable time interval in milliseconds. Default to 1000
    • threshold number - Time threshold in milliseconds to wait before re-enable the poisoning. Default to 10000
// 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 }))

Method

Namemethod
Poison Phaseincoming / outgoing

Filters by HTTP method.

Arguments:

  • method string|array - Method or methods to filter.
var method = toxy.rules.method(['GET', 'POST'])
toxy.rule(method)

Content Type

Filters by content type header. It should be present

Arguments:

  • value string|regexp - Header value to match.
var rule = toxy.rules.contentType('application/json')
toxy.rule(rule)

Headers

Nameheaders
Poison Phaseincoming / outgoing

Filter by request headers.

Arguments:

  • headers object - Headers to match by key-value pair. value can be a string, regexp, boolean or function(headerValue, headerName) => boolean
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)

Response headers

NameresponseHeaders
Poison Phaseoutgoing

Filter by response headers from target server. Same as headers rule, but evaluating the outgoing request.

Arguments:

  • headers object - Headers to match by key-value pair. value can be a string, regexp, boolean or function(headerValue, headerName) => boolean
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

Namebody
Poison Phaseincoming / outgoing

Match incoming body payload by a given string, regexp or custom filter function.

This rule is pretty simple, so for complex body matching (e.g: validating against a JSON schema) you should probably write your own rule.

Arguments:

  • match string|regexp|function - Body content to match
  • limit string - Optional. Body limit in human size. E.g: 5mb
  • encoding string - Body encoding. Default to utf8
  • length number - Body length. Default taken from Content-Length header
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)

Response body

NameresponseBody
Poison Phaseoutgoing

Match outgoing body payload by a given string, regexp or custom filter function.

Arguments:

  • match string|regexp|function - Body content to match
  • encoding string - Body encoding. Default to utf8
  • length number - Body length. Default taken from Content-Length header
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)

Response status

NameresponseStatus
Poison Phaseoutgoing

Evaluates the response status from the target server. Only applicable to outgoing poisons.

Arguments:

  • range array - Pair of status code range to match. Default [200, 300].
  • lower number - Compare status as lower than operation. Default to null.
  • higher number - Compare status as higher than operation. Default to null.
  • value number - Status code to match using a strict equality comparison. Default null.
  • include array - Unordered list of status codes to match. Useful to specify custom status. Default null
// 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] }))

Third-party rules

List of available third-party rules provided by the community. PR are welcome.

  • IP - Enable/disable poisons based on the client IP address (supports CIDR, subnets, ranges...).

How to write rules

Rules are simple middleware functions that resolve asynchronously with a boolean value to determine if a given HTTP transaction should be ignored when poisoning.

Your rule must resolve with a boolean param calling the next(err, shouldIgnore) function in the middleware, passing a true value if the rule has not matches and should not apply the poisoning, and therefore continuing with the next middleware stack.

Here's an example of a simple rule matching the HTTP method to determine if:

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(/* ... */)

You can optionally extend the build-in rules with your own rules:

toxy.addRule(customMethodRule)

// Then you can use it as a built-in poison
proxy
  .get('/foo')
  .rules(toxy.rules.customMethodRule)

For featured real examples, take a look to the built-in rules implementation

Programmatic API

toxy API is completely built on top the rocky API. In other words, you can use any of the methods, features and middleware layer natively provided by rocky.

toxy([ options ])

Create a new toxy proxy.

For supported options, please see rocky documentation

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... ])

Return: ToxyRoute

Register a new route for GET method.

toxy#post(path, [ middleware... ])

Return: ToxyRoute

Register a new route for POST method.

toxy#put(path, [ middleware... ])

Return: ToxyRoute

Register a new route for PUT method.

toxy#patch(path, [ middleware... ])

Return: ToxyRoute

toxy#delete(path, [ middleware... ])

Return: ToxyRoute

Register a new route for DELETE method.

toxy#head(path, [ middleware... ])

Return: ToxyRoute

Register a new route for HEAD method.

toxy#all(path, [ middleware... ])

Return: ToxyRoute

Register a new route for any method.

toxy#poisons => Object

Exposes a map with the built-in poisons. Prototype alias to toxy.poisons

toxy#rules => Object

Exposes a map with the built-in poisons. Prototype alias to toxy.rules

toxy#forward(url)

Define a URL to forward the incoming traffic received by the proxy.

toxy#balance(urls)

Forward to multiple servers balancing among them.

For more information, see the rocky docs

toxy#replay(url)

Define a new replay server. You can call this method multiple times to define multiple replay servers.

For more information, see the rocky docs

toxy#use(middleware)

Plug in a custom middleware.

For more information, see the rocky docs.

toxy#useResponse(middleware)

Plug in a response outgoing traffic middleware.

For more information, see the rocky docs.

toxy#useReplay(middleware)

Plug in a replay traffic middleware.

For more information, see the rocky docs

toxy#requestBody(middleware)

Intercept incoming request body. Useful to modify it on the fly.

For more information, see the rocky docs

toxy#responseBody(middleware)

Intercept outgoing response body. Useful to modify it on the fly.

For more information, see the rocky docs

toxy#middleware()

Return a standard middleware to use with connect/express.

toxy#host(host)

Overwrite the Host header with a custom value. Similar to forwardHost option.

toxy#redirect(url)

Redirect traffic to the given URL.

toxy#findRoute(routeIdOrPath, [ method ])

Find a route by ID or path and method.

toxy#listen(port)

Starts the built-in HTTP server, listening on a specific TCP port.

toxy#close([ callback ])

Closes the HTTP server.

toxy#poison(poison)

Alias: usePoison, useIncomingPoison

Register a new poison to infect incoming traffic.

toxy#outgoingPoison(poison)

Alias: useOutgoingPoison, responsePoison

Register a new poison to infect outgoing traffic.

toxy#rule(rule)

Alias: useRule

Register a new rule.

toxy#withRule(rule)

Aliases: ifRule, whenRule, poisonRule, poisonFilter

Apply a new rule for the latest registered poison.

toxy#enable(poison)

Enable a poison by name identifier

toxy#disable(poison)

Disable a poison by name identifier

toxy#remove(poison)

Return: boolean

Remove an incoming traffic poison by name identifier or object reference.

toxy#removeOutgoing(poison)

Return: boolean

Remove an outgoing traffic poison by name identifier or object reference.

toxy#isEnabled(poison)

Return: boolean

Checks if a poison is enabled by name identifier.

toxy#disableAll()

Alias: disablePoisons

Disable all the registered poisons.

toxy#getPoison(name)

Return: Directive|null

Searches and retrieves a registered poison in the stack by name identifier.

toxy#getIncomingPoison(name)

Return: Directive|null

Searches and retrieves a registered incoming poison in the stack by name identifier.

toxy#getOutgoingPoison(name)

Return: Directive|null

Searches and retrieves a registered outgoing poison in the stack by name identifier.

toxy#getPoisons()

Return: array<Directive>

Return an array of registered poisons.

toxy#getIncomingPoisons()

Return: array<Directive>

Return an array of registered incoming poisons.

toxy#getOutgoingPoisons()

Return: array<Directive>

Return an array of registered outgoing poisons.

toxy#flush()

Alias: flushPoisons

Remove all the registered poisons for both incoming and outgoing traffic flows.

toxy#enableRule(rule)

Enable a rule by name identifier.

toxy#disableRule(rule)

Disable a rule by name identifier.

toxy#removeRule(rule)

Return: boolean

Remove a rule by name identifier.

toxy#disableRules()

Disable all the registered rules.

toxy#isRuleEnabled(rule)

Return: boolean

Checks if the given rule is enabled by name identifier.

toxy#getRule(rule)

Return: Directive|null

Searches and retrieves a registered rule in the stack by name identifier.

toxy#getRules()

Return: array<Directive>

Returns and array with the registered rules wrapped as Directive.

toxy#flushRules()

Remove all the rules.

toxy.addPoison(name, fn)

Extend built-in poisons.

toxy.addRule(name, fn)

Extend built-in rules.

toxy.poisons => Object

Exposes a map with the built-in poisons.

toxy.rules => Object

Exposes a map with the built-in rules.

toxy.VERSION => String

Current toxy semantic version.

ToxyRoute

ToxyRoute exposes the same interface as Toxy global interface, it just adds some route level additional methods.

Further actions you perform against the ToxyRoute API will only be applicable at route-level (nested). In other words: you already know the API.

This example will probably clarify possible doubts:

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)

A convenient wrapper internally used for poisons and rules.

Normally you don't need to know this interface, but for hacking purposes or more low-level actions might be useful.

Directive#enable()

Return: boolean

Directive#disable()

Return: boolean

Directive#isEnabled()

Return: boolean

Directive#rule(rule)

Alias: filter

Directive#handler()

Return: function(req, res, next)

HTTP API

The toxy HTTP API follows the JSON API conventions, including resource based hypermedia linking.

Usage

For a featured use case, see the admin server example.

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)

For more details about the admin programmatic API, see below.

Authorization

The HTTP API can be protected to unauthorized clients. Authorized clients must define the API key token via API-Key or Authorization HTTP headers.

To enable it, you should simple pass the following options to toxy admin server:

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

Hierarchy:

  • Servers - Managed toxy instances
    • Rules - Globally applied rules
    • Poisons - Globally applied poisons
      • Rules - Poison-specific rules
    • Routes - List of configured routes
      • Route - Object for each specific route
        • Rules - Route-level registered rules
        • Poisons - Route-level registered poisons
          • Rules - Route-level poison-specific rules

GET /

Servers

GET /servers

GET /servers/:id

Rules

GET /servers/:id/rules

POST /servers/:id/rules

Accepts: application/json

Example payload:

{
  "name": "method",
  "options": "GET"
}

DELETE /servers/:id/rules

GET /servers/:id/rules/:id

DELETE /servers/:id/rules/:id

Poisons

GET /servers/:id/poison

POST /servers/:id/poisons

Accepts: application/json

Example payload:

{
  "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

Accepts: application/json

Example payload:

{
  "name": "method",
  "options": "GET"
}

DELETE /servers/:id/poisons/:id/rules

GET /servers/:id/poisons/:id/rules/:id

DELETE /servers/:id/poisons/:id/rules/:id

Routes

GET /servers/:id/routes

POST /servers/:id/routes

Accepts: application/json

Example payload:

{
  "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

Route rules

GET /servers/:id/routes/:id/rules

POST /servers/:id/routes/:id/rules

Accepts: application/json

Example payload:

{
  "name": "method",
  "options": "GET"
}

DELETE /servers/:id/routes/:id/rules

GET /servers/:id/routes/:id/rules/:id

DELETE /servers/:id/routes/:id/rules/:id

Route poisons

GET /servers/:id/routes/:id/poisons

POST /servers/:id/routes/:id/poisons

Accepts: application/json

Example payload:

{
  "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

Accepts: application/json

Example payload:

{
  "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

Programmatic API

The built-in HTTP admin server also provides a simple interface open to extensibility and hacking purposes. For instance, you can plug in additional middleware to the admin server, or register new routes.

toxy.admin([ opts ])

Returns: Admin

Supported options:

  • apiKey string - Optional API key to protect the server
  • port number - Optional. TCP port to listen
  • cors boolean - Enable CORS for web browser access
  • middleware array<function> - Plug in additional middleware
  • ssl object - Node.js HTTPS server TLS options.
Admin#listen([ port, host ])

Start listening on the network.

Admin#manage(toxy)

Manage a toxy server instance.

Admin#find(toxy)

Find a toxy instance. Accepts toxy server ID or toxy instance.

Admin#remove(toxy)

Stop managing a toxy instance.

Admin#use(...middleware)

Register a middleware.

Admin#param(...middleware)

Register a param middleware.

Admin#get(path, [ ...middleware ])

Register a GET route.

Admin#post(path, [ ...middleware ])

Register a POST route.

Admin#put(path, [ ...middleware ])

Register a PUT route.

Admin#delete(path, [ ...middleware ])

Register a DELETE route.

Admin#patch(path, [ ...middleware ])

Register a PATCH route.

Admin#all(path, [ ...middleware ])

Register a route accepting any HTTP method.

Admin#middleware(req, res, next)

Middleware to plug in with connect/express.

Admin#close(cb)

Stop the server.

License

MIT - Tomas Aparicio

views

toxy's People

Contributors

0xflotus avatar benhartley avatar h2non avatar jesstelford avatar juhovuori avatar marinacodes avatar nwwells avatar prayagverma avatar tonygaetani avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

toxy's Issues

How to use behind a corporate proxy?

We want to use toxy behind a corporate proxy for resilience testing of our client for a REST service provided by another institution. We can only access the service through the proxy server of our institute. I could not find any configuration options to achieve that. Have I overlooked something?

I tried to use global-agent it fails because the http-proxy dependency used by toxy always creates a new instance of the agent for every request and misses the parameters required by global-agent (because it is and likely cannot be aware of it). Before I raise an issue for http-proxy, I wanted to make sure that I am not overlooking a mechanism of toxy to be run behind a corporate proxy.

Just as a remark the issue with http-proxy is that it does not pass the agent option correctly to the http client API of nodejs. It converts null to false which has a different semantic. Therefore, the following two configuration lines for toxy are passed as false to http.request by http-proxy.

proxy.opts['agent'] = null;
proxy.opts['agent'] = false;

HTTP API is unable to handle CORS preflight requests

Summary: Toxy's HTTP API doesn't accept well formed CORS preflight requests. It encounters an error unless both a Content-Type header and body are present, neither of which should be in a CORS preflight request. This essentially makes CORS support non-functional.

Additionally, if authorization is enabled, it attempts to validate the Authorization or API-Key headers on the preflight request.


I have a Toxy server set up as such:

var toxy = require('toxy');

var admin = toxy.admin({ cors: true, apiKey: 'secret' });
var proxy = toxy();

proxy.forward('http://api.preview.mydomain.com');
proxy.all('/*');
proxy.listen(80);

admin.manage(proxy);
admin.listen(3000);

When I try to send an XHR to the HTTP API from my browser, it sends this preflight:

OPTIONS http://toxy.preview.mydomain.com:3000/ HTTP/1.1
Host: toxy.preview.mydomain.com:3000
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:27614
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
Access-Control-Request-Headers: API-Key
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

And I receive this response:

HTTP/1.1 415 Unsupported Media Type
Date: Wed, 29 Nov 2017 22:51:01 GMT
Server: toxy (admin)
Content-Length: 0
Connection: keep-alive

If I copy the preflight request and send it with an additional Content-Type: application/json header and receive this error:

HTTP/1.1 500 Internal Server Error
Date: Wed, 29 Nov 2017 22:52:32 GMT
Server: toxy (admin)
Content-Length: 35
Connection: keep-alive

{"error":"Unexpected end of input"}

I change the preflight request again, to contain a body of {}, and receive yet another error:

HTTP/1.1 401 Unauthorized
Date: Wed, 29 Nov 2017 22:52:51 GMT
Server: toxy (admin)
Content-Length: 0
Connection: keep-alive

Finally, I add the API-Key: secret header, and it finally accepts the preflight request (which, at this point, is very malformed):

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: localhost
Date: Wed, 29 Nov 2017 22:53:42 GMT
Server: toxy (admin)
Content-Length: 0
Connection: keep-alive

Can toxy be used as a web browser proxy?

Can toxy be used as a web browser proxy (to test some page loading behaviour with low connection speed)? Or is this package not suitable for such use case?

Thank you!

How to allow the entire network traffic via proxy port by not filtering via certain urls?

Is there a way I can channel my entire network traffic via toxy, just like the way we do it with tools like charles? Currently, I'm able to forward traffic from certain URL's via toxy and apply poisons to it, but don't see the whole network traffic going through the proxy server that I've opened in port 3000 despite adding "proxy.all('/*')". If I have an android device connected (with manual proxy in the Wi-Fi settings) to the proxy server running in my computer on port 3000, the network connection is not established and no sites are accessible.

Routing problems, can't get a simple proxy to work without any rules even

Greetings

I am very interested about using toxy but I ran into usage issues right from the start.

I cannot seems to get a correct response from a multiple servers, cause some direct IP access and many many sites are refusing to forward, I have tried it at multiple AP home/work/cafferooms etc

I run proxy like this

~$ node name.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
    .all('/*')
    .forward('http://4chan.org/')

proxy.listen(3000)

console.log('Server listening on port:', 3000)

And 4chan kindly responds with Direct "IP access not allowed"

I have no firewall set on my debian machine.

more demos: https://youtu.be/dvarUf868S8

Thank you for the time.

Incoming / outgoing traffic poisoning

Poisons are currently executed in the incoming traffic phase. It would be great to attach poisons that will be executed only when the proxy receives the response from the target server, allowing the poisons to determine when should be executed or not based on the server response.

Actually this feature can be achieve currently via the useResponse() middleware, but a convenient wrapper around it, adding a DSL for toxy, would be better and simple for the API consumers.

The API may look like this:

proxy.outgoingPoison(poison)
proxy.useOutgoingPoison(poison)
  .withRule(rule)

Tasks

  • Add featured test
  • Support filter via HTTP API outgoing and incoming poisons
  • Rename poisons ID in HTTP API to include the poison phase
  • Remove rules by phase
  • Add proper documentation
  • Add poisons phases explanation with proper graph
  • Add API docs
  • Add examples

Filters design

  • Hierarchical support (global, route and poison level)
  • Embed or not to embed as part of the poison feature

Support for HTTPS

Ideally, supporting HTTPS on both sides of the connection, providing a certificate which must be trusted, similar to Fiddler/Charles Proxy.

feature request: more flexible inject poison

Right now, when using the inject poison, you need to inject all three status, body and headers. I'd love to be able to only inject one or two of them.

Furthermore, it would be very useful if the inject poison would merge injected headers with the original response headers. Right now, these headers are disregarded, resulting in e.g. missing CORS headers. Any change this could be added?

HTTP API

Useful for remote configuration control via toxy clients

Mid-request poison udpates

I have implemented a custom throttling solution with node, but I am interested in using toxy. One of the features I need is the ability to begin throttling a request mid stream. In other words, if I am streaming a large video, I need to be able to start throttling the bandwidth some amount of time (say 10 seconds) into the stream to simulate poor network conditions. I see that I could create a poison to throttle the request and use the toxy API to asynchronously activate the poison, but my question is: will the poison immediately take effect (mid request) or will it not take effect until future requests arrive?

lib/helpers/each-series.js:2 TypeError: Cannot read property 'slice' of null

Just happened after using Toxy (installed via current npm) on a random website:

Server listening on port: 3000
/node_modules/toxy/lib/helpers/each-series.js:2
var stack = arr.slice()
^
TypeError: Cannot read property 'slice' of null
at Object.eachSeries (/node_modules/toxy/lib/helpers/each-series.js:2:18)
at ServerResponse.res.end (/node_modules/toxy/lib/poisons/throttle.js:33:15)
at end [as _onTimeout] (/node_modules/toxy/lib/poisons/slow-close.js:40:17)
at Timer.listOnTimeout (timers.js:119:15)

Session based poisons

This is related to my other question: #36

I need to be able to poison requests conditionally on the session (or similar identifier). For example I want to be able to poison user A's request to /some-end-point without poisoning user B's request to /some-end-point. Specifically, I want user A to be able to hit the troxy admin API and specify a new poison with a specific bandwidth and have the poison only applied to user A's requests. In my custom throttling implementation, I do this using sessions. I am sure this can be accomplished with toxy, but I want to know: what would be the most straight forward mechanism for implementing this with toxy?

Question about abort poisons

I'd like to implement a poison on a resource that will disrupt the connection after some time. It looks like the abort poison is what I want, but I was not getting the functionality I wanted with the following configuration:

const toxy = require('..')
const poisons = toxy.poisons

const proxy = toxy()
const serverhost = 'mysite.de'
const timeout = 1000

proxy
  .all('/*')
  .forward('http://' + serverhost)

proxy
  .all('/myresource/*')
  .poison(toxy.poisons.abort(1000))
  .withRule(rules.method('GET'))
  .outgoingPoison(toxy.poisons.abort(1000))
  .withRule(rules.method('GET'))
  .forward('http://' + serverhost)

const port = 4567
proxy.listen(port)
console.log('Server listening on port:', port)

My landscape is:

[client] => [nginx] => [toxy] => [webapp]

Is this the right approach, or am I misunderstanding something?

Odd self signed certificate JSON payload

I followed the HTTPS sample with the SSL option. When I query the toxy proxy, I'm getting this odd JSON
{"message":"self signed certificate"}

Yes I am using a self signed cert. Is there a work around to rejectUnauthorized: false?

MaxListenersExceededWarning when trying to update poison on the proxy

Hi,

I am using the proxy for video player testing, switching network conditions within the playback session. I do this every time I want to change the network:

if (poison) {
    proxy.remove(poison);
    poison = null;
}

poison = poisons.throttle({ chunk: chunkSize, threshold: chunkDelay});
proxy.poison(poison);

And then I get this warning:

(node:19805) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added. Use emitter.setMaxListeners() to increase limit

I did a separate test program and the warning did not appear when just switching poisons but rather it appeared when the proxy was also requests on the same time.

Is there a better way to change the network bandwidth in the same session?

Thanks

Relative forward

I would like to use single toxy instance to throttle many different APIs.
I am trying to set up following:

proxy.all("/papyrus")
    .forward("https://server1")
    .poison(poisons.latency({ min: 50, max: 2000 }))

proxy.all("/gql/*")
    .forward("https://server2")

Problem is, part of the url in all is also being forwarded

Any way to forward excluding the base url in all?

Forward all

Hi,

is it posible proxy requests to all hosts and only poison specific routes?

proxy
  .forward('*')

proxy
  .forward('http://myhost')
  .get('/test')
  .poison(poisons.abort())

Having issues with Node 4.5.0 after updating from 0.12.5

I'm using toxy to inject problems talking to the backend APIs into my single page web application.

So far so good, I had a working toxy proxy.

But when trying to bootstrap the design team with the proxy, they downloaded the latest node, which then caused the proxy to hang. I know its getting to transformRequestBody, because I see the log messages, but the browser thinks all the requests are pending until they time out.

Here's my config:

var toxy = require('toxy');
var express = require('express');
var poisons = toxy.poisons;
var rules = toxy.rules;

// Create a new toxy proxy
var proxy = toxy();
//var Array = require('Array');

var host = process.env.HOST || 'uicommon-lab-sc.lab.skyportsystems.com'


var app = express(),
port = process.env.PORT || 8080;

var cspheader = "default-src 'self'; script-src 'self' www.google-analytics.com" +
                " ajax.googleapis.com ; style-src 'unsafe-inline' 'self' data:; " +
                "img-src 'self' data:; media-src 'none'; font-src 'self' data:; " +
                "connect-src 'self'; report-uri https://" +
                host+"/api/v0/csp";

var cspheadernames=["Content-Security-Policy", "X-Content-Security-Policy", "X-WebKit-CSP"];

function setCSPHeaders(res, path, stat)
{
    for (var ix=0; ix < cspheadernames.length; ix++)
    {
        res.append(cspheadernames[ix],cspheader);
    }

}

function processRequest(req,res,next) {
        console.log(">>>>"+req.url);

        next();
}

function processResponse(req,res,next) {
        console.log("<<<<<"+req.url);
    var keys = Object.keys(res._headers),
        len = keys.length,
        prop,
        value;
        for (i=0; i < len;i++) 
        {
            prop = keys[i];
            value = res._headers[prop];
            //console.log("replacing",prop,value);
            if (Array.isArray(value))
            {
                for (jx=0; jx < value.length; jx++)
                {
                    v2 = value[jx];
                    value[jx] = v2.replace(/Secure/g,'');
                }
            }
            else
            {
                value.replace(/Secure/g,'');
                res._headers[prop]=value;
            }
        }

        next()
}

// Default server to forward incoming traffic
proxy
  .forward('https://'+host)

var roots = ['/api','/templates','/cdn_images','/session'];

for (var ix=0;ix< roots.length;ix++)
{
var root = roots[ix]+'/*';
    console.log('proxying',root);
    proxy
      .all(root)
      .withRule(rules.method(['POST', 'PUT', 'DELETE','GET']))
      .options({ secure: false })
    .transformRequest(processRequest)
    .transformResponse(processResponse)
      .host(host)
}



app.use(express.static(__dirname + '/webroot', {setHeaders: setCSPHeaders}));
app.use(proxy.middleware())
app.listen(port);


console.log('Server listening on port:', port)
console.log('Test it:', 'http://localhost:'+port+'/html/home.html')

Admin HTTP API

A nice-to-have feature, probably as embedded feature in toxy.

Implement a built-in server listening on a different port exposing a simple HTTP API for admin purposes and dynamic configuration.

API

var toxy = require('toxy')

var proxy = toxy()
var admin = toxy.admin()

admin.manage(proxy)

proxy.listen(3000)
admin.listen(9000)

Tasks

  • Read
  • Update
  • Create
  • Remove
  • Hypermedia (HAL)

HTTP spec

GET /servers

GET /servers/:id

GET /servers/:id/routes

GET /servers/:id/routes/:id

POST /servers/:id/routes/:id

DELETE /servers/:id/routes/:id

GET /servers/:id/routes/:id/poisons

GET /servers/:id/routes/:id/poisons/:id

POST /servers/:id/routes/:id/poison

DELETE /servers/:id/routes/:id/poisons/:id

Strange spikes in latency with toxy

Created a simple pass through toxy script to just forward traffic and I'm seeing large ~20 sec spikes fairly regularly. I'm sending 60 requests per second through toxy. I've got some client side metrics enabled and the majority of the time is spent receiving the response from toxy. Is there some default poison applied that could be causing this? Can toxy not handle that rate of requests (I assume it can per some benchmarks on the rocky README)? When I run the same code without toxy (directly hitting the endpoint) I'm not getting any spikes. Hitting toxy with http and toxy is forwarding to an https endpoint.

var toxy = require('toxy')
var fs = require('fs')

var poisons = toxy.poisons
var rules = toxy.rules
var proxy = toxy()

// Configure and enable proxy
proxy.all("*").forward("https://dynamodb.us-west-2.amazonaws.com")
proxy.listen(6241)

Strict Forwarding and Websockets

Why write forward address every time for every website, how to enable it just like straight proxy without any forwarding address strictly typed for concrete website?
And when i connected to website, which uses websockets in background, proxy doesn't hold it, i've websocket connection error, because it has ip address which diffs from main website, another strict forward problem i think? Thanks.

Unable to delete outgoing poisons via HTTP API

I'm experiencing that I'm unable to delete outgoing poisons after adding them via the HTTP API.

I added an outgoing latency poison to a route via the HTTP API, resulting in the call to GET /servers/882/routes/20e/poisons looking like this:

[
   {
      "name":"latency",
      "enabled":true,
      "phase":"outgoing",
      "rules":[

      ],
      "links":{
         "self":{
            "href":"/servers/882/routes/20e/poisons/latency:outgoing"
         },
         "parent":{
            "href":"/servers/882/routes/20e/poisons"
         }
      }
   }
]

I then attempt to delete the poison via DELETE /servers/882/routes/20e/poisons and get back a 204 No Content, but the poison is still present.

I also experience the same problem when I try to delete the specific poison, both via DELETE /servers/882/routes/20e/poisons/latency and DELETE /servers/882/routes/20e/poisons/latency:outgoing; I get a 204 No Content, but the poison is still there.

Note that I can delete any incoming poisons; the problem is exclusive to outgoing ones.

slowRead problems when submitting files larger then 30KB

Hi guys, first of all thank you for building this cool tool!

I've been making a small, simple POC for myself using toxy to simulate bad network behaviour and I stumble upon a problem.

Using the poison slowRead like so:

proxy
    .post('/upload')
    .poison(poisons.slowRead({ chunk: 102400, threshold: 500 }))

The browser becomes unable to submit files larger then 30KB, 70KB files sometimes get submitted 100KB for instance, doesn't.

You can see for yourselves: https://github.com/goncalvesjoao/up-your-ass-ets
the POC is fairly simple and quick to look at, am I missing something or am I configuring toxy in a wrong way?

Thanks,
John

Time window rule

Enable/disable poisoning based on a specific time threshold. Useful for testing failures in specific recurrent periods of time.

proxy.rule(toxy.rules.timeThreshold({ period: 1000 * 60 * 60, duration: 500 }))

Or using a random value within a given range:

proxy.rule(toxy.rules.timeThreshold({ period: 1000 * 60 * 60, duration: [100, 1000] }))

Relative forward?

I would like to use single toxy instance to throttle many different APIs.
I am trying to set up following:

proxy.all("/papyrus")
    .forward("https://server1")
    .poison(poisons.latency({ min: 50, max: 2000 }))

proxy.all("/gql/*")
    .forward("https://server2")

Problem is, part of the url in all is also being forwarded

Any way to forward excluding the base url in all?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.