From 459f7ee0dc317e53dc669854a4f76dcf87f0b3a2 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Thu, 20 Mar 2025 03:52:25 +0400 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20Add=20yaml=20file=20reader=20to?= =?UTF-8?q?=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 44 ++++++++++++++++----------------- config/file.go | 59 ++++++++++++++++++++++++++++++-------------- go.mod | 1 + go.sum | 2 ++ types/body.go | 22 +++++++++++++++++ types/config_file.go | 17 ++++++++++--- types/cookies.go | 59 +++++++++++++++++++++++++++++++------------- types/duration.go | 21 ++++++++++++++++ types/headers.go | 59 +++++++++++++++++++++++++++++++------------- types/params.go | 59 +++++++++++++++++++++++++++++++------------- types/proxies.go | 30 ++++++++++++++++++++++ types/request_url.go | 15 +++++++++++ 12 files changed, 293 insertions(+), 95 deletions(-) diff --git a/config/config.go b/config/config.go index 192d57e..3c40160 100644 --- a/config/config.go +++ b/config/config.go @@ -27,17 +27,17 @@ const ( var SupportedProxySchemes []string = []string{"http", "socks5", "socks5h"} type RequestConfig struct { - Method string `json:"method"` - URL url.URL `json:"url"` - Timeout time.Duration `json:"timeout"` - DodosCount uint `json:"dodos"` - RequestCount uint `json:"requests"` - Yes bool `json:"yes"` - Params types.Params `json:"params"` - Headers types.Headers `json:"headers"` - Cookies types.Cookies `json:"cookies"` - Body types.Body `json:"body"` - Proxies types.Proxies `json:"proxies"` + Method string + URL url.URL + Timeout time.Duration + DodosCount uint + RequestCount uint + Yes bool + Params types.Params + Headers types.Headers + Cookies types.Cookies + Body types.Body + Proxies types.Proxies } func NewRequestConfig(conf *Config) *RequestConfig { @@ -111,17 +111,17 @@ func (rc *RequestConfig) Print() { } type Config struct { - Method *string `json:"method"` - URL *types.RequestURL `json:"url"` - Timeout *types.Timeout `json:"timeout"` - DodosCount *uint `json:"dodos"` - RequestCount *uint `json:"requests"` - Yes *bool `json:"yes"` - Params types.Params `json:"params"` - Headers types.Headers `json:"headers"` - Cookies types.Cookies `json:"cookies"` - Body types.Body `json:"body"` - Proxies types.Proxies `json:"proxy"` + Method *string `json:"method" yaml:"method"` + URL *types.RequestURL `json:"url" yaml:"url"` + Timeout *types.Timeout `json:"timeout" yaml:"timeout"` + DodosCount *uint `json:"dodos" yaml:"dodos"` + RequestCount *uint `json:"requests" yaml:"requests"` + Yes *bool `json:"yes" yaml:"yes"` + Params types.Params `json:"params" yaml:"params"` + Headers types.Headers `json:"headers" yaml:"headers"` + Cookies types.Cookies `json:"cookies" yaml:"cookies"` + Body types.Body `json:"body" yaml:"body"` + Proxies types.Proxies `json:"proxy" yaml:"proxy"` } func NewConfig() *Config { diff --git a/config/file.go b/config/file.go index 9607390..bfb4571 100644 --- a/config/file.go +++ b/config/file.go @@ -7,40 +7,54 @@ import ( "io" "net/http" "os" + "slices" + "strings" "time" "github.com/aykhans/dodo/types" + "gopkg.in/yaml.v3" ) +var supportedFileTypes = []string{"json", "yaml", "yml"} + func (config *Config) ReadFile(filePath types.ConfigFile) error { var ( data []byte err error ) - if filePath.LocationType() == types.FileLocationTypeRemoteHTTP { - client := &http.Client{ - Timeout: 10 * time.Second, + fileExt := filePath.Extension() + if slices.Contains(supportedFileTypes, fileExt) { + if filePath.LocationType() == types.FileLocationTypeRemoteHTTP { + client := &http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Get(filePath.String()) + if err != nil { + return fmt.Errorf("failed to fetch config file from %s", filePath) + } + defer resp.Body.Close() + + data, err = io.ReadAll(io.Reader(resp.Body)) + if err != nil { + return fmt.Errorf("failed to read config file from %s", filePath) + } + } else { + data, err = os.ReadFile(filePath.String()) + if err != nil { + return errors.New("failed to read config file from " + filePath.String()) + } } - resp, err := client.Get(filePath.String()) - if err != nil { - return fmt.Errorf("failed to fetch config file from %s", filePath) - } - defer resp.Body.Close() - - data, err = io.ReadAll(io.Reader(resp.Body)) - if err != nil { - return fmt.Errorf("failed to read config file from %s", filePath) - } - } else { - data, err = os.ReadFile(filePath.String()) - if err != nil { - return errors.New("failed to read config file from " + filePath.String()) + if fileExt == "json" { + return parseJSONConfig(data, config) + } else if fileExt == "yml" || fileExt == "yaml" { + return parseYAMLConfig(data, config) } } - return parseJSONConfig(data, config) + return fmt.Errorf("unsupported config file type (supported types: %v)", strings.Join(supportedFileTypes, ", ")) } func parseJSONConfig(data []byte, config *Config) error { @@ -58,3 +72,12 @@ func parseJSONConfig(data []byte, config *Config) error { return nil } + +func parseYAMLConfig(data []byte, config *Config) error { + err := yaml.Unmarshal(data, &config) + if err != nil { + return fmt.Errorf("YAML Config file: %s", err.Error()) + } + + return nil +} diff --git a/go.mod b/go.mod index 630fd5b..f364633 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.0 require ( github.com/jedib0t/go-pretty/v6 v6.6.7 github.com/valyala/fasthttp v1.59.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/go.sum b/go.sum index fbc7040..bf32876 100644 --- a/go.sum +++ b/go.sum @@ -29,5 +29,7 @@ golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/types/body.go b/types/body.go index 18ce972..7bb1c24 100644 --- a/types/body.go +++ b/types/body.go @@ -66,6 +66,28 @@ func (body *Body) UnmarshalJSON(b []byte) error { return nil } +func (body *Body) UnmarshalYAML(unmarshal func(interface{}) error) error { + var data any + if err := unmarshal(&data); err != nil { + return err + } + + switch v := data.(type) { + case string: + *body = []string{v} + case []any: + var slice []string + for _, item := range v { + slice = append(slice, fmt.Sprintf("%v", item)) + } + *body = slice + default: + return fmt.Errorf("invalid type for Body: %T (should be string or []string)", v) + } + + return nil +} + func (body *Body) Set(value string) error { *body = append(*body, value) return nil diff --git a/types/config_file.go b/types/config_file.go index 66d8846..f5f2a89 100644 --- a/types/config_file.go +++ b/types/config_file.go @@ -11,13 +11,22 @@ const ( type ConfigFile string -func (config ConfigFile) String() string { - return string(config) +func (configFile ConfigFile) String() string { + return string(configFile) } -func (config ConfigFile) LocationType() FileLocationType { - if strings.HasPrefix(string(config), "http://") || strings.HasPrefix(string(config), "https://") { +func (configFile ConfigFile) LocationType() FileLocationType { + if strings.HasPrefix(string(configFile), "http://") || strings.HasPrefix(string(configFile), "https://") { return FileLocationTypeRemoteHTTP } return FileLocationTypeLocal } + +func (configFile ConfigFile) Extension() string { + i := strings.LastIndex(configFile.String(), ".") + if i == -1 { + return "" + } + + return configFile.String()[i+1:] +} diff --git a/types/cookies.go b/types/cookies.go index 3a1fba8..339e2da 100644 --- a/types/cookies.go +++ b/types/cookies.go @@ -56,6 +56,23 @@ func (cookies Cookies) String() string { return string(buffer.Bytes()) } +func (cookies *Cookies) AppendByKey(key, value string) { + if item := cookies.GetValue(key); item != nil { + *item = append(*item, value) + } else { + *cookies = append(*cookies, KeyValue[string, []string]{Key: key, Value: []string{value}}) + } +} + +func (cookies Cookies) GetValue(key string) *[]string { + for i := range cookies { + if cookies[i].Key == key { + return &cookies[i].Value + } + } + return nil +} + func (cookies *Cookies) UnmarshalJSON(b []byte) error { var data []map[string]any if err := json.Unmarshal(b, &data); err != nil { @@ -82,6 +99,31 @@ func (cookies *Cookies) UnmarshalJSON(b []byte) error { return nil } +func (cookies *Cookies) UnmarshalYAML(unmarshal func(interface{}) error) error { + var raw []map[string]any + if err := unmarshal(&raw); err != nil { + return err + } + + for _, param := range raw { + for key, value := range param { + switch parsed := value.(type) { + case string: + *cookies = append(*cookies, KeyValue[string, []string]{Key: key, Value: []string{parsed}}) + case []any: + var values []string + for _, v := range parsed { + if str, ok := v.(string); ok { + values = append(values, str) + } + } + *cookies = append(*cookies, KeyValue[string, []string]{Key: key, Value: values}) + } + } + } + return nil +} + func (cookies *Cookies) Set(value string) error { parts := strings.SplitN(value, "=", 2) switch len(parts) { @@ -95,20 +137,3 @@ func (cookies *Cookies) Set(value string) error { return nil } - -func (cookies *Cookies) AppendByKey(key, value string) { - if item := cookies.GetValue(key); item != nil { - *item = append(*item, value) - } else { - *cookies = append(*cookies, KeyValue[string, []string]{Key: key, Value: []string{value}}) - } -} - -func (cookies Cookies) GetValue(key string) *[]string { - for i := range cookies { - if cookies[i].Key == key { - return &cookies[i].Value - } - } - return nil -} diff --git a/types/duration.go b/types/duration.go index 64c320e..9d41e33 100644 --- a/types/duration.go +++ b/types/duration.go @@ -34,3 +34,24 @@ func (timeout *Timeout) UnmarshalJSON(b []byte) error { func (timeout Timeout) MarshalJSON() ([]byte, error) { return json.Marshal(timeout.Duration.String()) } + +func (timeout *Timeout) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v any + if err := unmarshal(&v); err != nil { + return err + } + switch value := v.(type) { + case float64: + timeout.Duration = time.Duration(value) + return nil + case string: + var err error + timeout.Duration, err = time.ParseDuration(value) + if err != nil { + return errors.New("Timeout is invalid (e.g. 400ms, 1s, 5m, 1h)") + } + return nil + default: + return errors.New("Timeout is invalid (e.g. 400ms, 1s, 5m, 1h)") + } +} diff --git a/types/headers.go b/types/headers.go index 2968db3..12684ec 100644 --- a/types/headers.go +++ b/types/headers.go @@ -56,6 +56,23 @@ func (headers Headers) String() string { return string(buffer.Bytes()) } +func (headers *Headers) AppendByKey(key, value string) { + if item := headers.GetValue(key); item != nil { + *item = append(*item, value) + } else { + *headers = append(*headers, KeyValue[string, []string]{Key: key, Value: []string{value}}) + } +} + +func (headers Headers) GetValue(key string) *[]string { + for i := range headers { + if headers[i].Key == key { + return &headers[i].Value + } + } + return nil +} + func (headers *Headers) UnmarshalJSON(b []byte) error { var data []map[string]any if err := json.Unmarshal(b, &data); err != nil { @@ -82,6 +99,31 @@ func (headers *Headers) UnmarshalJSON(b []byte) error { return nil } +func (headers *Headers) UnmarshalYAML(unmarshal func(interface{}) error) error { + var raw []map[string]any + if err := unmarshal(&raw); err != nil { + return err + } + + for _, param := range raw { + for key, value := range param { + switch parsed := value.(type) { + case string: + *headers = append(*headers, KeyValue[string, []string]{Key: key, Value: []string{parsed}}) + case []any: + var values []string + for _, v := range parsed { + if str, ok := v.(string); ok { + values = append(values, str) + } + } + *headers = append(*headers, KeyValue[string, []string]{Key: key, Value: values}) + } + } + } + return nil +} + func (headers *Headers) Set(value string) error { parts := strings.SplitN(value, ":", 2) switch len(parts) { @@ -95,20 +137,3 @@ func (headers *Headers) Set(value string) error { return nil } - -func (headers *Headers) AppendByKey(key, value string) { - if item := headers.GetValue(key); item != nil { - *item = append(*item, value) - } else { - *headers = append(*headers, KeyValue[string, []string]{Key: key, Value: []string{value}}) - } -} - -func (headers Headers) GetValue(key string) *[]string { - for i := range headers { - if headers[i].Key == key { - return &headers[i].Value - } - } - return nil -} diff --git a/types/params.go b/types/params.go index fd4b60d..b4db8c1 100644 --- a/types/params.go +++ b/types/params.go @@ -56,6 +56,23 @@ func (params Params) String() string { return string(buffer.Bytes()) } +func (params *Params) AppendByKey(key, value string) { + if item := params.GetValue(key); item != nil { + *item = append(*item, value) + } else { + *params = append(*params, KeyValue[string, []string]{Key: key, Value: []string{value}}) + } +} + +func (params Params) GetValue(key string) *[]string { + for i := range params { + if params[i].Key == key { + return ¶ms[i].Value + } + } + return nil +} + func (params *Params) UnmarshalJSON(b []byte) error { var data []map[string]any if err := json.Unmarshal(b, &data); err != nil { @@ -82,6 +99,31 @@ func (params *Params) UnmarshalJSON(b []byte) error { return nil } +func (params *Params) UnmarshalYAML(unmarshal func(interface{}) error) error { + var raw []map[string]any + if err := unmarshal(&raw); err != nil { + return err + } + + for _, param := range raw { + for key, value := range param { + switch parsed := value.(type) { + case string: + *params = append(*params, KeyValue[string, []string]{Key: key, Value: []string{parsed}}) + case []any: + var values []string + for _, v := range parsed { + if str, ok := v.(string); ok { + values = append(values, str) + } + } + *params = append(*params, KeyValue[string, []string]{Key: key, Value: values}) + } + } + } + return nil +} + func (params *Params) Set(value string) error { parts := strings.SplitN(value, "=", 2) switch len(parts) { @@ -95,20 +137,3 @@ func (params *Params) Set(value string) error { return nil } - -func (params *Params) AppendByKey(key, value string) { - if item := params.GetValue(key); item != nil { - *item = append(*item, value) - } else { - *params = append(*params, KeyValue[string, []string]{Key: key, Value: []string{value}}) - } -} - -func (params Params) GetValue(key string) *[]string { - for i := range params { - if params[i].Key == key { - return ¶ms[i].Value - } - } - return nil -} diff --git a/types/proxies.go b/types/proxies.go index ab9f0f8..6e0f58b 100644 --- a/types/proxies.go +++ b/types/proxies.go @@ -75,6 +75,36 @@ func (proxies *Proxies) UnmarshalJSON(b []byte) error { return nil } +func (proxies *Proxies) UnmarshalYAML(unmarshal func(interface{}) error) error { + var data any + if err := unmarshal(&data); err != nil { + return err + } + + switch v := data.(type) { + case string: + parsed, err := url.Parse(v) + if err != nil { + return err + } + *proxies = []url.URL{*parsed} + case []any: + var urls []url.URL + for _, item := range v { + url, err := url.Parse(item.(string)) + if err != nil { + return err + } + urls = append(urls, *url) + } + *proxies = urls + default: + return fmt.Errorf("invalid type for Body: %T (should be URL or []URL)", v) + } + + return nil +} + func (proxies *Proxies) Set(value string) error { parsedURL, err := url.Parse(value) if err != nil { diff --git a/types/request_url.go b/types/request_url.go index 2fab9d3..e194d7b 100644 --- a/types/request_url.go +++ b/types/request_url.go @@ -25,6 +25,21 @@ func (requestURL *RequestURL) UnmarshalJSON(data []byte) error { return nil } +func (requestURL *RequestURL) UnmarshalYAML(unmarshal func(interface{}) error) error { + var urlStr string + if err := unmarshal(&urlStr); err != nil { + return err + } + + parsedURL, err := url.Parse(urlStr) + if err != nil { + return errors.New("Request URL is invalid") + } + + requestURL.URL = *parsedURL + return nil +} + func (requestURL RequestURL) MarshalJSON() ([]byte, error) { return json.Marshal(requestURL.URL.String()) } From ec80569d5d1ea33d7a92101a71903ee7bc50190d Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Thu, 20 Mar 2025 15:59:09 +0400 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=90=B3=20Remove=20default=20config=20?= =?UTF-8?q?file=20path=20from=20'ENTRYPOINT'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 29ea12c..ccd4ed5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ WORKDIR / COPY --from=builder /src/dodo /dodo COPY --from=builder /src/config.json /config.json -ENTRYPOINT ["./dodo", "-f", "/config.json"] \ No newline at end of file +ENTRYPOINT ["./dodo"] \ No newline at end of file From 56342e49c64c36ff158ced7fa2325ad9e29ca926 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Thu, 20 Mar 2025 15:59:46 +0400 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=9A=20Update=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 127 ++++++++++++++++++++++++++++++++++++++++++++-------- config.yaml | 7 ++- 2 files changed, 112 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index fce8a76..c2e7e2a 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,52 @@ -

Dodo - A Fast and Easy-to-Use HTTP Benchmarking Tool

+# Dodo - A Fast and Easy-to-Use HTTP Benchmarking Tool +

+## Table of Contents + +- [Installation](#installation) + - [Using Docker (Recommended)](#using-docker-recommended) + - [Using Pre-built Binaries](#using-pre-built-binaries) + - [Building from Source](#building-from-source) +- [Usage](#usage) + - [1. CLI Usage](#1-cli-usage) + - [2. Config File Usage](#2-config-file-usage) + - [2.1 JSON Example](#21-json-example) + - [2.2 YAML/YML Example](#22-yamlyml-example) + - [3. CLI & Config File Combination](#3-cli--config-file-combination) +- [Config Parameters Reference](#config-parameters-reference) + ## Installation ### Using Docker (Recommended) -Pull the Dodo image from Docker Hub: +Pull the latest Dodo image from Docker Hub: ```sh docker pull aykhans/dodo:latest ``` -When using Dodo with Docker and a local config file, you must provide the config.json file as a volume to the Docker run command (not as the "-f config.json" argument): +To use Dodo with Docker and a local config file, mount the config file as a volume and pass it as an argument: ```sh -docker run -v /path/to/config.json:/config.json aykhans/dodo +docker run -v /path/to/config.json:/config.json aykhans/dodo -f /config.json ``` -If you're using Dodo with Docker and providing a config file via URL, you don't need to set a volume: +If you're using a remote config file via URL, you don't need to mount a volume: ```sh -docker run aykhans/dodo -f https://raw.githubusercontent.com/aykhans/dodo/main/config.json +docker run aykhans/dodo -f https://raw.githubusercontent.com/aykhans/dodo/main/config.yaml ``` -### Using Binary Files +### Using Pre-built Binaries -You can download pre-built binaries from the [releases](https://github.com/aykhans/dodo/releases) section. +Download the latest binaries from the [releases](https://github.com/aykhans/dodo/releases) section. ### Building from Source -To build Dodo from source, you need to have [Go 1.24+](https://golang.org/dl/) installed. -Follow these steps: +To build Dodo from source, ensure you have [Go 1.24+](https://golang.org/dl/) installed. Then follow these steps: 1. **Clone the repository:** @@ -56,9 +70,9 @@ This will generate an executable named `dodo` in the project directory. ## Usage -You can use Dodo with CLI arguments, a JSON config file, or both. When using both, CLI arguments will override JSON config values if there's a conflict. +Dodo supports CLI arguments, configuration files (JSON/YAML), or a combination of both. If both are used, CLI arguments take precedence. -### 1. CLI +### 1. CLI Usage Send 1000 GET requests to https://example.com with 10 parallel dodos (threads) and a timeout of 2 seconds: @@ -72,7 +86,9 @@ With Docker: docker run --rm -i aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -t 2s ``` -### 2. JSON Config File +### 2. Config File Usage + +#### 2.1 JSON Example Send 1000 GET requests to https://example.com with 10 parallel dodos (threads) and a timeout of 800 milliseconds: @@ -152,21 +168,96 @@ docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo docker run --rm -i aykhans/dodo -f https://example.com/config.json ``` -### 3. Combined (CLI & JSON) +#### 2.2 YAML/YML Example -Override the config file arguments with CLI arguments: +```yaml +method: "GET" +url: "https://example.com" +yes: false +timeout: "800ms" +dodos: 10 +requests: 1000 + +params: + # A random value will be selected from the list for first "key1" param on each request + # And always "value" for second "key1" param on each request + # e.g. "?key1=value2&key1=value" + - key1: ["value1", "value2", "value3", "value4"] + - key1: "value" + + # A random value will be selected from the list for param "key2" on each request + # e.g. "?key2=value2" + - key2: ["value1", "value2"] + +headers: + # A random value will be selected from the list for first "key1" header on each request + # And always "value" for second "key1" header on each request + # e.g. "key1: value3", "key1: value" + - key1: ["value1", "value2", "value3", "value4"] + - key1: "value" + + # A random value will be selected from the list for header "key2" on each request + # e.g. "key2: value2" + - key2: ["value1", "value2"] + +cookies: + # A random value will be selected from the list for first "key1" cookie on each request + # And always "value" for second "key1" cookie on each request + # e.g. "key1=value4; key1=value" + - key1: ["value1", "value2", "value3", "value4"] + - key1: "value" + + # A random value will be selected from the list for cookie "key2" on each request + # e.g. "key2=value1" + - key2: ["value1", "value2"] + +body: "body-text" +# OR +# A random body value will be selected from the list for each request +body: + - "body-text1" + - "body-text2" + - "body-text3" + +proxy: "http://example.com:8080" +# OR +# A random proxy will be selected from the list for each request +proxy: + - "http://example.com:8080" + - "http://username:password@example.com:8080" + - "socks5://example.com:8080" + - "socks5h://example.com:8080" +``` ```sh -dodo -f /path/to/config.json -u https://example.com -m GET -d 10 -r 1000 -t 5s +dodo -f /path/config.yaml +# OR +dodo -f https://example.com/config.yaml ``` With Docker: ```sh -docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -t 5s +docker run --rm -i -v /path/to/config.yaml:/config.yaml aykhans/dodo -f /config.yaml +# OR +docker run --rm -i aykhans/dodo -f https://example.com/config.yaml ``` -## CLI and JSON Config Parameters +### 3. CLI & Config File Combination + +CLI arguments override config file values: + +```sh +dodo -f /path/to/config.yaml -u https://example.com -m GET -d 10 -r 1000 -t 5s +``` + +With Docker: + +```sh +docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo -f /config.json -u https://example.com -m GET -d 10 -r 1000 -t 5s +``` + +## Config Parameters Reference If `Headers`, `Params`, `Cookies`, `Body`, or `Proxy` fields have multiple values, each request will choose a random value from the list. diff --git a/config.yaml b/config.yaml index 54a4492..3273304 100644 --- a/config.yaml +++ b/config.yaml @@ -1,11 +1,8 @@ -# YAML/YML config file option is not implemented yet. -# This file is a example for future implementation. - method: "GET" url: "https://example.com" yes: false timeout: "5s" -dodos: 10 +dodos: 8 requests: 1000 params: @@ -25,6 +22,7 @@ cookies: # body: "body-text" # OR +# A random body value will be selected from the list for each request body: - "body-text1" - "body-text2" @@ -32,6 +30,7 @@ body: # proxy: "http://example.com:8080" # OR +# A random proxy will be selected from the list for each request proxy: - "http://example.com:8080" - "http://username:password@example.com:8080" From 1808865358db5181444d36f28117ae66b2eba3ad Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Thu, 20 Mar 2025 16:01:53 +0400 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=93=9A=20Update=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c2e7e2a..96b66ce 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# Dodo - A Fast and Easy-to-Use HTTP Benchmarking Tool - +

Dodo - A Fast and Easy-to-Use HTTP Benchmarking Tool

From 985fc6200d7c9966909060c3d2f58c36392f6fd5 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Thu, 20 Mar 2025 16:11:43 +0400 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=90=B3=20Update=20Dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ccd4ed5..bd26da1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,13 +7,11 @@ RUN go mod download COPY . . RUN go build -ldflags "-s -w" -o dodo -RUN echo "{}" > config.json FROM gcr.io/distroless/static-debian12:latest WORKDIR / COPY --from=builder /src/dodo /dodo -COPY --from=builder /src/config.json /config.json ENTRYPOINT ["./dodo"] \ No newline at end of file From ed52fff3631b97eee266c4f1d1bd0bd8a57c34ff Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Thu, 20 Mar 2025 16:31:39 +0400 Subject: [PATCH 6/6] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20bump=20version=20to=20?= =?UTF-8?q?0.6.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 3c40160..e92dff4 100644 --- a/config/config.go +++ b/config/config.go @@ -15,7 +15,7 @@ import ( ) const ( - VERSION string = "0.6.0" + VERSION string = "0.6.1" DefaultUserAgent string = "Dodo/" + VERSION DefaultMethod string = "GET" DefaultTimeout time.Duration = time.Second * 10