Skip to content

Commit 5bb644d

Browse files
authored
Merge pull request #116 from aykhans/feat/value-generator
Add value generators
2 parents 924bd81 + 9152eef commit 5bb644d

File tree

10 files changed

+845
-142
lines changed

10 files changed

+845
-142
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ COPY go.mod go.sum ./
66
RUN go mod download
77
COPY . .
88

9-
RUN go build -ldflags "-s -w" -o dodo
9+
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o dodo
1010

1111
FROM gcr.io/distroless/static-debian12:latest
1212

README.md

Lines changed: 133 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
- [Usage](#usage)
1313
- [1. CLI Usage](#1-cli-usage)
1414
- [2. Config File Usage](#2-config-file-usage)
15-
- [2.1 JSON Example](#21-json-example)
16-
- [2.2 YAML/YML Example](#22-yamlyml-example)
15+
- [2.1 YAML/YML Example](#21-yamlyml-example)
16+
- [2.2 JSON Example](#22-json-example)
1717
- [3. CLI & Config File Combination](#3-cli--config-file-combination)
1818
- [Config Parameters Reference](#config-parameters-reference)
19+
- [Template Functions](#template-functions)
1920

2021
## Installation
2122

@@ -73,7 +74,84 @@ docker run --rm -i aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -o 1
7374

7475
Send 1000 GET requests to https://example.com with 10 parallel dodos (threads), each with a timeout of 800 milliseconds, within a maximum duration of 250 seconds:
7576

76-
#### 2.1 JSON Example
77+
#### 2.1 YAML/YML Example
78+
79+
```yaml
80+
method: "GET"
81+
url: "https://example.com"
82+
yes: false
83+
timeout: "800ms"
84+
dodos: 10
85+
requests: 1000
86+
duration: "250s"
87+
skip_verify: false
88+
89+
params:
90+
# A random value will be selected from the list for first "key1" param on each request
91+
# And always "value" for second "key1" param on each request
92+
# e.g. "?key1=value2&key1=value"
93+
- key1: ["value1", "value2", "value3", "value4"]
94+
- key1: "value"
95+
96+
# A random value will be selected from the list for param "key2" on each request
97+
# e.g. "?key2=value2"
98+
- key2: ["value1", "value2"]
99+
100+
headers:
101+
# A random value will be selected from the list for first "key1" header on each request
102+
# And always "value" for second "key1" header on each request
103+
# e.g. "key1: value3", "key1: value"
104+
- key1: ["value1", "value2", "value3", "value4"]
105+
- key1: "value"
106+
107+
# A random value will be selected from the list for header "key2" on each request
108+
# e.g. "key2: value2"
109+
- key2: ["value1", "value2"]
110+
111+
cookies:
112+
# A random value will be selected from the list for first "key1" cookie on each request
113+
# And always "value" for second "key1" cookie on each request
114+
# e.g. "key1=value4; key1=value"
115+
- key1: ["value1", "value2", "value3", "value4"]
116+
- key1: "value"
117+
118+
# A random value will be selected from the list for cookie "key2" on each request
119+
# e.g. "key2=value1"
120+
- key2: ["value1", "value2"]
121+
122+
body: "body-text"
123+
# OR
124+
# A random body value will be selected from the list for each request
125+
body:
126+
- "body-text1"
127+
- "body-text2"
128+
- "body-text3"
129+
130+
proxy: "http://example.com:8080"
131+
# OR
132+
# A random proxy will be selected from the list for each request
133+
proxy:
134+
- "http://example.com:8080"
135+
- "http://username:[email protected]:8080"
136+
- "socks5://example.com:8080"
137+
- "socks5h://example.com:8080"
138+
```
139+
140+
```sh
141+
dodo -f /path/config.yaml
142+
# OR
143+
dodo -f https://example.com/config.yaml
144+
```
145+
146+
With Docker:
147+
148+
```sh
149+
docker run --rm -i -v /path/to/config.yaml:/config.yaml aykhans/dodo -f /config.yaml
150+
# OR
151+
docker run --rm -i aykhans/dodo -f https://example.com/config.yaml
152+
```
153+
154+
#### 2.2 JSON Example
77155

78156
```jsonc
79157
{
@@ -153,83 +231,6 @@ docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo
153231
docker run --rm -i aykhans/dodo -f https://example.com/config.json
154232
```
155233

156-
#### 2.2 YAML/YML Example
157-
158-
```yaml
159-
method: "GET"
160-
url: "https://example.com"
161-
yes: false
162-
timeout: "800ms"
163-
dodos: 10
164-
requests: 1000
165-
duration: "250s"
166-
skip_verify: false
167-
168-
params:
169-
# A random value will be selected from the list for first "key1" param on each request
170-
# And always "value" for second "key1" param on each request
171-
# e.g. "?key1=value2&key1=value"
172-
- key1: ["value1", "value2", "value3", "value4"]
173-
- key1: "value"
174-
175-
# A random value will be selected from the list for param "key2" on each request
176-
# e.g. "?key2=value2"
177-
- key2: ["value1", "value2"]
178-
179-
headers:
180-
# A random value will be selected from the list for first "key1" header on each request
181-
# And always "value" for second "key1" header on each request
182-
# e.g. "key1: value3", "key1: value"
183-
- key1: ["value1", "value2", "value3", "value4"]
184-
- key1: "value"
185-
186-
# A random value will be selected from the list for header "key2" on each request
187-
# e.g. "key2: value2"
188-
- key2: ["value1", "value2"]
189-
190-
cookies:
191-
# A random value will be selected from the list for first "key1" cookie on each request
192-
# And always "value" for second "key1" cookie on each request
193-
# e.g. "key1=value4; key1=value"
194-
- key1: ["value1", "value2", "value3", "value4"]
195-
- key1: "value"
196-
197-
# A random value will be selected from the list for cookie "key2" on each request
198-
# e.g. "key2=value1"
199-
- key2: ["value1", "value2"]
200-
201-
body: "body-text"
202-
# OR
203-
# A random body value will be selected from the list for each request
204-
body:
205-
- "body-text1"
206-
- "body-text2"
207-
- "body-text3"
208-
209-
proxy: "http://example.com:8080"
210-
# OR
211-
# A random proxy will be selected from the list for each request
212-
proxy:
213-
- "http://example.com:8080"
214-
- "http://username:[email protected]:8080"
215-
- "socks5://example.com:8080"
216-
- "socks5h://example.com:8080"
217-
```
218-
219-
```sh
220-
dodo -f /path/config.yaml
221-
# OR
222-
dodo -f https://example.com/config.yaml
223-
```
224-
225-
With Docker:
226-
227-
```sh
228-
docker run --rm -i -v /path/to/config.yaml:/config.yaml aykhans/dodo -f /config.yaml
229-
# OR
230-
docker run --rm -i aykhans/dodo -f https://example.com/config.yaml
231-
```
232-
233234
### 3. CLI & Config File Combination
234235

235236
CLI arguments override config file values:
@@ -264,3 +265,55 @@ If `Headers`, `Params`, `Cookies`, `Body`, or `Proxy` fields have multiple value
264265
| Body | body | -body | -b | String OR [String] | Request body or list of request bodies | - |
265266
| Proxy | proxies | -proxy | -x | String OR [String] | Proxy URL or list of proxy URLs | - |
266267
| Skip Verify | skip_verify | -skip-verify | | Boolean | Skip SSL/TLS certificate verification | false |
268+
269+
## Template Functions
270+
271+
Dodo supports template functions in `Headers`, `Params`, `Cookies`, and `Body` fields. These functions allow you to generate dynamic values for each request.
272+
273+
You can use Go template syntax to include dynamic values in your requests. Here's how to use template functions:
274+
275+
In CLI config:
276+
277+
```sh
278+
dodo -u https://example.com -r 1 \
279+
-header "User-Agent:{{ fakeit_UserAgent }}" \ # e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
280+
-param "username={{ strings_ToUpper fakeit_Username }}" \ # e.g. "username=JOHN BOB"
281+
-cookie "token={{ fakeit_Password true true true true true 10 }}" \ # e.g. token=1234567890abcdef1234567890abcdef
282+
-body '{"email":"{{ fakeit_Email }}", "password":"{{ fakeit_Password true true true true true 10 }}"}' # e.g. {"email":"[email protected]", "password":"12rw4d-78d"}
283+
```
284+
285+
In YAML/YML config:
286+
287+
```yaml
288+
headers:
289+
- User-Agent: "{{ fakeit_UserAgent }}" # e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
290+
- "Random-Header-{{fakeit_FirstName}}": "static_value" # e.g. "Random-Header-John: static_value"
291+
292+
cookies:
293+
- token: "Bearer {{ fakeit_UUID }}" # e.g. "token=Bearer 1234567890abcdef1234567890abcdef"
294+
295+
params:
296+
- id: "{{ fakeit_Uint }}" # e.g. "id=1234567890"
297+
- username: "{{ fakeit_Username }}" # e.g. "username=John Doe"
298+
299+
body:
300+
- '{ "username": "{{ fakeit_Username }}", "password": "{{ fakeit_Password }}" }' # e.g. { "username": "john.doe", "password": "password123" }
301+
- '{ "email": "{{ fakeit_Email }}", "phone": "{{ fakeit_Phone }}" }' # e.g. { "email": "[email protected]", "phone": "1234567890" }
302+
- '{{ body_FormData (dict_Str "username" fakeit_Username "password" "secret123") }}' # Creates multipart form data for form submissions, automatically sets the appropriate Content-Type header.
303+
```
304+
305+
In JSON config:
306+
307+
```jsonc
308+
{
309+
"headers": [
310+
{ "User-Agent": "{{ fakeit_UserAgent }}" }, // e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
311+
],
312+
"body": [
313+
"{ \"username\": \"{{ strings_RemoveSpaces fakeit_Username }}\", \"password\": \"{{ fakeit_Password }}\" }", // e.g. { "username": "johndoe", "password": "password123" }
314+
"{{ body_FormData (dict_Str \"username\" fakeit_Username \"password\" \"12345\") }}", // Creates multipart form data for form submissions, automatically sets the appropriate Content-Type header.
315+
],
316+
}
317+
```
318+
319+
For the full list of template functions over 200 functions, refer to the `NewFuncMap` function in `utils/templates.go`.

Taskfile.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ tasks:
3232

3333
lint: golangci-lint run
3434

35-
build: go build -ldflags "-s -w" -o "dodo"
35+
build: CGO_ENABLED=0 go build -ldflags "-s -w" -o "dodo"
3636

3737
build-all:
3838
silent: true

config/config.go

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package config
22

33
import (
4+
"bytes"
45
"errors"
56
"fmt"
7+
"math/rand"
68
"net/url"
79
"os"
810
"slices"
911
"strings"
12+
"text/template"
1013
"time"
1114

1215
"github.com/aykhans/dodo/types"
@@ -15,7 +18,7 @@ import (
1518
)
1619

1720
const (
18-
VERSION string = "0.6.5"
21+
VERSION string = "0.7.0"
1922
DefaultUserAgent string = "Dodo/" + VERSION
2023
DefaultMethod string = "GET"
2124
DefaultTimeout time.Duration = time.Second * 10
@@ -201,6 +204,98 @@ func (config *Config) Validate() []error {
201204
}
202205
}
203206

207+
funcMap := *utils.NewFuncMapGenerator(
208+
rand.New(
209+
rand.NewSource(
210+
time.Now().UnixNano(),
211+
),
212+
),
213+
).GetFuncMap()
214+
215+
for _, header := range config.Headers {
216+
t, err := template.New("default").Funcs(funcMap).Parse(header.Key)
217+
if err != nil {
218+
errs = append(errs, fmt.Errorf("header key (%s) parse error: %v", header.Key, err))
219+
} else {
220+
var buf bytes.Buffer
221+
if err = t.Execute(&buf, nil); err != nil {
222+
errs = append(errs, fmt.Errorf("header key (%s) parse error: %v", header.Key, err))
223+
}
224+
}
225+
226+
for _, value := range header.Value {
227+
t, err := template.New("default").Funcs(funcMap).Parse(value)
228+
if err != nil {
229+
errs = append(errs, fmt.Errorf("header value (%s) parse error: %v", value, err))
230+
} else {
231+
var buf bytes.Buffer
232+
if err = t.Execute(&buf, nil); err != nil {
233+
errs = append(errs, fmt.Errorf("header value (%s) parse error: %v", value, err))
234+
}
235+
}
236+
}
237+
}
238+
239+
for _, cookie := range config.Cookies {
240+
t, err := template.New("default").Funcs(funcMap).Parse(cookie.Key)
241+
if err != nil {
242+
errs = append(errs, fmt.Errorf("cookie key (%s) parse error: %v", cookie.Key, err))
243+
} else {
244+
var buf bytes.Buffer
245+
if err = t.Execute(&buf, nil); err != nil {
246+
errs = append(errs, fmt.Errorf("cookie key (%s) parse error: %v", cookie.Key, err))
247+
}
248+
}
249+
250+
for _, value := range cookie.Value {
251+
t, err := template.New("default").Funcs(funcMap).Parse(value)
252+
if err != nil {
253+
errs = append(errs, fmt.Errorf("cookie value (%s) parse error: %v", value, err))
254+
} else {
255+
var buf bytes.Buffer
256+
if err = t.Execute(&buf, nil); err != nil {
257+
errs = append(errs, fmt.Errorf("cookie value (%s) parse error: %v", value, err))
258+
}
259+
}
260+
}
261+
}
262+
263+
for _, param := range config.Params {
264+
t, err := template.New("default").Funcs(funcMap).Parse(param.Key)
265+
if err != nil {
266+
errs = append(errs, fmt.Errorf("param key (%s) parse error: %v", param.Key, err))
267+
} else {
268+
var buf bytes.Buffer
269+
if err = t.Execute(&buf, nil); err != nil {
270+
errs = append(errs, fmt.Errorf("param key (%s) parse error: %v", param.Key, err))
271+
}
272+
}
273+
274+
for _, value := range param.Value {
275+
t, err := template.New("default").Funcs(funcMap).Parse(value)
276+
if err != nil {
277+
errs = append(errs, fmt.Errorf("param value (%s) parse error: %v", value, err))
278+
} else {
279+
var buf bytes.Buffer
280+
if err = t.Execute(&buf, nil); err != nil {
281+
errs = append(errs, fmt.Errorf("param value (%s) parse error: %v", value, err))
282+
}
283+
}
284+
}
285+
}
286+
287+
for _, body := range config.Body {
288+
t, err := template.New("default").Funcs(funcMap).Parse(body)
289+
if err != nil {
290+
errs = append(errs, fmt.Errorf("body (%s) parse error: %v", body, err))
291+
} else {
292+
var buf bytes.Buffer
293+
if err = t.Execute(&buf, nil); err != nil {
294+
errs = append(errs, fmt.Errorf("body (%s) parse error: %v", body, err))
295+
}
296+
}
297+
}
298+
204299
return errs
205300
}
206301

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/aykhans/dodo
33
go 1.24.2
44

55
require (
6+
github.com/brianvoe/gofakeit/v7 v7.2.1
67
github.com/jedib0t/go-pretty/v6 v6.6.7
78
github.com/valyala/fasthttp v1.62.0
89
gopkg.in/yaml.v3 v3.0.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
22
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
3+
github.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI=
4+
github.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
35
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
46
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=

0 commit comments

Comments
 (0)