Skip to content

Commit b08f2d9

Browse files
author
Jacob Wirth
committed
Add bidirectional toxic info to the docs
1 parent fb86b43 commit b08f2d9

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 2.1.0 (Unreleased)
22

3+
* Add bidirectional toxics #132
34
* Add `/populate` endpoint to server #111
45
* Change error responses from `title` to `error`
56
* Allow hostname to be specified in CLI #129

CREATING_TOXICS.md

+84-3
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,18 @@ and how much memory you are comfortable with using.
118118
## Stateful toxics
119119

120120
If a toxic needs to store extra information for a connection such as the number of bytes
121-
transferred (See `limit_data` toxic), a state object can be created by implementing the
122-
`StatefulToxic` interface. This interface defines the `NewState()` function that can create
123-
a new state object with default values set.
121+
transferred (See the [limit_data toxic](https://github.com/Shopify/toxiproxy/blob/master/toxics/limit_data.go)),
122+
a state object can be created by implementing the `StatefulToxic` interface. This interface
123+
defines the `NewState()` function that can create a new state object with default values set.
124+
125+
```go
126+
func (t *ExampleToxic) NewState() interface{} {
127+
return &ExampleToxicState{
128+
BytesRemaining: t.BytesAllowed,
129+
SomeOtherState: true,
130+
}
131+
}
132+
```
124133

125134
When a stateful toxic is created, the state object will be stored on the `ToxicStub` and
126135
can be accessed from `toxic.Pipe()`:
@@ -133,6 +142,78 @@ If necessary, some global state can be stored in the toxic struct, which will no
133142
instanced per-connection. These fields cannot have a custom default value set and will
134143
not be thread-safe, so proper locking or atomic operations will need to be used.
135144

145+
## Bidirectional toxics
146+
147+
Regular toxics are limited to data flowing in a single direction, so they can't make decisions
148+
for the `downstream` based on a request in the `upstream`. For things like protocol aware toxics
149+
this is a problem.
150+
151+
Bidirectional toxics allow state to be shared for the `upstream` and `downstream` pipes in a single
152+
toxic implementation. They also ensure direction-specific code is always run on the correct pipe
153+
(a toxic that only works on the `upstream` can't be added to the `downstream`).
154+
155+
Creating a bidirectional toxic is done by implementing a second `Pipe()` function called `PipeRequest()`.
156+
The implementation is same as a regular toxic, and can be paired with other types such as a stateful toxic.
157+
158+
One use case of a bidirectional toxic is to mock out the backend server entirely, which is shown below:
159+
160+
```go
161+
type EchoToxic struct {}
162+
163+
type EchoToxicState struct {
164+
Request chan *stream.StreamChunk
165+
}
166+
167+
// PipeRequest handles the upstream direction
168+
func (t *EchoToxic) PipeRequest(stub *toxics.ToxicStub) {
169+
state := stub.State.(*EchoToxicState)
170+
171+
for {
172+
select {
173+
case <-stub.Interrupt:
174+
return
175+
case c := <-stub.Input:
176+
if c == nil {
177+
// Close the downstream when the client closes
178+
close(state.Request)
179+
stub.Close()
180+
return
181+
}
182+
// Send the data to the downstream through the state object
183+
state.Request <- c
184+
}
185+
}
186+
}
187+
188+
// Pipe() will only handle the downstream on a bidirectional toxic
189+
func (t *EchoToxic) Pipe(stub *toxics.ToxicStub) {
190+
state := stub.State.(*EchoToxicState)
191+
192+
for {
193+
select {
194+
case <-stub.Interrupt:
195+
return
196+
case c := <-state.Request: // Read from the upstream instead of the server
197+
if c == nil {
198+
stub.Close()
199+
return
200+
}
201+
stub.Output <- c
202+
}
203+
}
204+
}
205+
206+
func (t *EchoToxic) NewState() interface{} {
207+
return &EchoToxicState{
208+
Request: make(chan *stream.StreamChunk),
209+
}
210+
}
211+
```
212+
213+
This example will loop back all data send to the server back to the client. Another use case seen
214+
within toxiproxy is to filter http response modifications based on the request URL (See the
215+
[http toxic](https://github.com/Shopify/toxiproxy/tree/master/toxics/http.go)).
216+
136217
## Using `io.Reader` and `io.Writer`
137218

138219
If your toxic involves modifying the data going through a proxy, you can use the `ChanReader`

0 commit comments

Comments
 (0)