@@ -118,9 +118,18 @@ and how much memory you are comfortable with using.
118
118
## Stateful toxics
119
119
120
120
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
+ ```
124
133
125
134
When a stateful toxic is created, the state object will be stored on the ` ToxicStub ` and
126
135
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
133
142
instanced per-connection. These fields cannot have a custom default value set and will
134
143
not be thread-safe, so proper locking or atomic operations will need to be used.
135
144
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
+
136
217
## Using ` io.Reader ` and ` io.Writer `
137
218
138
219
If your toxic involves modifying the data going through a proxy, you can use the ` ChanReader `
0 commit comments