diff options
| author | Shulhan <ms@kilabit.info> | 2024-02-16 00:24:47 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2024-02-16 01:38:51 +0700 |
| commit | 2b6dc0730aa3c30787e440c1090b8041ff6b4ca8 (patch) | |
| tree | 588439eafdb7cdae7d5d469e7f688e6fd206c00a | |
| parent | 372d10a3e1ea01f8d03e44e6ab8be673d05c0773 (diff) | |
| download | gorankusu-2b6dc0730aa3c30787e440c1090b8041ff6b4ca8.tar.xz | |
all: set default HTTPTarget Attack if its not set
Previously, the function for Attack need to be coded manually.
This changes introduce new function DefaultHTTPAttack that generate
HTTPAttackHandler based on the HTTPTarget method, request type, and
Params; if AllowAttack is true and Attack is nil.
Implements: https://todo.sr.ht/~shulhan/gorankusu/4
| -rw-r--r-- | errors.go | 8 | ||||
| -rw-r--r-- | example.go | 89 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | gorankusu.go | 3 | ||||
| -rw-r--r-- | http_attack_handler.go | 82 | ||||
| -rw-r--r-- | http_target.go | 15 |
7 files changed, 99 insertions, 104 deletions
@@ -35,14 +35,6 @@ func errAttackNotAllowed() error { return res } -var errAttackHandlerNotSet = libhttp.EndpointResponse{ - E: liberrors.E{ - Code: http.StatusNotAcceptable, - Message: `the Attack handler is not set`, - Name: `ERR_ATTACK_HANDLER_NOT_SET`, - }, -} - func errInternal(err error) error { res := &libhttp.EndpointResponse{ E: liberrors.E{ @@ -17,7 +17,6 @@ import ( libhttp "github.com/shuLhan/share/lib/http" "github.com/shuLhan/share/lib/mlog" "github.com/shuLhan/share/lib/websocket" - vegeta "github.com/tsenart/vegeta/v12/lib" ) const ( @@ -52,10 +51,6 @@ type requestResponse struct { type Example struct { *Gorankusu wsServer *websocket.Server - - targetExampleErrorGet vegeta.Target - targetExampleGet vegeta.Target - targetExamplePostForm vegeta.Target } // NewExample create, initialize, and setup an example of Gorankusu. @@ -239,8 +234,6 @@ func (ex *Example) registerTargetHTTP() (err error) { }, Run: ex.runExampleGet, AllowAttack: true, - Attack: ex.attackExampleGet, - PreAttack: ex.preattackExampleGet, RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, }, { @@ -268,8 +261,6 @@ func (ex *Example) registerTargetHTTP() (err error) { }, Run: ex.runExampleGet, AllowAttack: true, - Attack: ex.attackExampleErrorGet, - PreAttack: ex.preattackExampleErrorGet, RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, }, { @@ -303,13 +294,11 @@ func (ex *Example) registerTargetHTTP() (err error) { }, Run: ex.runExamplePostForm, AllowAttack: true, - PreAttack: ex.preattackExamplePostForm, - Attack: ex.attackExamplePostForm, RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, }, { ID: `http_free_form`, - Name: `HTTP free form`, + Name: `HTTP Free Form`, Hint: fmt.Sprintf(`Test endpoint %q using custom HTTP method and/or content type.`, pathExample), Method: libhttp.RequestMethodGet, Path: pathExample, @@ -333,6 +322,7 @@ func (ex *Example) registerTargetHTTP() (err error) { RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, IsCustomizable: true, + AllowAttack: true, }, { ID: `http_post_path_binding`, Name: `HTTP Post path binding`, @@ -354,6 +344,7 @@ func (ex *Example) registerTargetHTTP() (err error) { }, RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, + AllowAttack: true, }, { ID: `http_rawbody_json`, Name: `HTTP raw body - JSON`, @@ -364,6 +355,7 @@ func (ex *Example) registerTargetHTTP() (err error) { RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, WithRawBody: true, + AllowAttack: true, }, { ID: `http_upload`, Name: `HTTP upload`, @@ -391,6 +383,7 @@ func (ex *Example) registerTargetHTTP() (err error) { }, RequestDumper: requestDumperWithoutDate, ResponseDumper: responseDumperWithoutDate, + AllowAttack: true, }}, } @@ -586,54 +579,6 @@ func (ex *Example) runExampleGet(req *RunRequest) (res *RunResponse, err error) return res, nil } -func (ex *Example) preattackExampleErrorGet(rr *RunRequest) { - ex.targetExampleErrorGet = vegeta.Target{ - Method: rr.HTTPTarget.Method.String(), - URL: fmt.Sprintf(`%s%s`, rr.Target.BaseURL, rr.HTTPTarget.Path), - Header: rr.HTTPTarget.Headers.ToHTTPHeader(), - } - - var q = rr.HTTPTarget.Params.ToURLValues().Encode() - if len(q) > 0 { - ex.targetExampleErrorGet.URL += `?` + q - } - - fmt.Printf("preattackExampleErrorGet: %+v\n", ex.targetExampleErrorGet) -} - -func (ex *Example) preattackExampleGet(rr *RunRequest) { - ex.targetExampleGet = vegeta.Target{ - Method: rr.HTTPTarget.Method.String(), - URL: fmt.Sprintf(`%s%s`, rr.Target.BaseURL, rr.HTTPTarget.Path), - Header: rr.HTTPTarget.Headers.ToHTTPHeader(), - } - - var q = rr.HTTPTarget.Params.ToURLValues().Encode() - if len(q) > 0 { - ex.targetExampleGet.URL += `?` + q - } - - fmt.Printf("preattackExampleGet: %+v\n", ex.targetExampleGet) -} - -func (ex *Example) attackExampleErrorGet(rr *RunRequest) vegeta.Targeter { - return func(tgt *vegeta.Target) error { - rr.HTTPTarget.Lock() - *tgt = ex.targetExampleErrorGet - rr.HTTPTarget.Unlock() - return nil - } -} - -func (ex *Example) attackExampleGet(rr *RunRequest) vegeta.Targeter { - return func(tgt *vegeta.Target) error { - rr.HTTPTarget.Lock() - *tgt = ex.targetExampleGet - rr.HTTPTarget.Unlock() - return nil - } -} - func (ex *Example) runExamplePostForm(req *RunRequest) (res *RunResponse, err error) { if req.Target.HTTPClient == nil { var httpcOpts = &libhttp.ClientOptions{ @@ -683,30 +628,6 @@ func (ex *Example) runExamplePostForm(req *RunRequest) (res *RunResponse, err er return res, nil } -func (ex *Example) preattackExamplePostForm(rr *RunRequest) { - ex.targetExamplePostForm = vegeta.Target{ - Method: rr.HTTPTarget.Method.String(), - URL: fmt.Sprintf(`%s%s`, rr.Target.BaseURL, rr.HTTPTarget.Path), - Header: rr.HTTPTarget.Headers.ToHTTPHeader(), - } - - var q = rr.HTTPTarget.Params.ToURLValues().Encode() - if len(q) > 0 { - ex.targetExamplePostForm.Body = []byte(q) - } - - fmt.Printf("preattackExamplePostForm: %+v\n", ex.targetExamplePostForm) -} - -func (ex *Example) attackExamplePostForm(rr *RunRequest) vegeta.Targeter { - return func(tgt *vegeta.Target) error { - rr.HTTPTarget.Lock() - *tgt = ex.targetExamplePostForm - rr.HTTPTarget.Unlock() - return nil - } -} - func (ex *Example) handleWSExampleGet(_ context.Context, req *websocket.Request) (res websocket.Response) { res.ID = req.ID res.Code = http.StatusOK @@ -7,7 +7,7 @@ go 1.20 require ( git.sr.ht/~shulhan/ciigo v0.11.0 - github.com/shuLhan/share v0.53.0 + github.com/shuLhan/share v0.53.1-0.20240215151058-c14c0aada881 github.com/tsenart/vegeta/v12 v12.11.1 ) @@ -26,8 +26,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8= github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= -github.com/shuLhan/share v0.53.0 h1:hxQQbUWKav0pGaVarakEBX7hqJsyfd5jGDG3l6mcAsU= -github.com/shuLhan/share v0.53.0/go.mod h1:97/BcWdLau8i+xeFvPHdyqph1HgxVBSVhQEUIyCmgRc= +github.com/shuLhan/share v0.53.1-0.20240215151058-c14c0aada881 h1:SGgdg7934FRJZZ1G+ZZNEdwSL7UgUCFRohtJnwSNrDI= +github.com/shuLhan/share v0.53.1-0.20240215151058-c14c0aada881/go.mod h1:97/BcWdLau8i+xeFvPHdyqph1HgxVBSVhQEUIyCmgRc= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d h1:X4+kt6zM/OVO6gbJdAfJR60MGPsqCzbtXNnjoGqdfAs= github.com/tsenart/vegeta/v12 v12.11.1 h1:Rbwe7Zxr7sJ+BDTReemeQalYPvKiSV+O7nwmUs20B3E= github.com/tsenart/vegeta/v12 v12.11.1/go.mod h1:swiFmrgpqj2llHURgHYFRFN0tfrIrlnspg01HjwOnSQ= diff --git a/gorankusu.go b/gorankusu.go index f3d120e..c807336 100644 --- a/gorankusu.go +++ b/gorankusu.go @@ -103,9 +103,6 @@ func (gorankusu *Gorankusu) AttackHTTP(req *RunRequest) (err error) { if !origHTTPTarget.AllowAttack { return errAttackNotAllowed() } - if origHTTPTarget.Attack == nil { - return fmt.Errorf(`%s: %w`, logp, &errAttackHandlerNotSet) - } req = generateRunRequest(gorankusu.Env, req, origTarget, origHTTPTarget) diff --git a/http_attack_handler.go b/http_attack_handler.go new file mode 100644 index 0000000..8a70416 --- /dev/null +++ b/http_attack_handler.go @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2024 M. Shulhan <ms@kilabit.info> +// SPDX-License-Identifier: GPL-3.0-or-later + +package gorankusu + +import ( + "encoding/json" + "fmt" + + libhttp "github.com/shuLhan/share/lib/http" + vegeta "github.com/tsenart/vegeta/v12/lib" +) + +// HTTPAttackHandler define the function type that will be called when client +// send request to attack HTTP target. +type HTTPAttackHandler func(rr *RunRequest) vegeta.Targeter + +// DefaultHTTPAttack define the default value for [HTTPTarget.Attack] handler +// that return [vegeta.Targeter]. +func DefaultHTTPAttack() HTTPAttackHandler { + return func(rr *RunRequest) vegeta.Targeter { + var err error + + rr.HTTPTarget.Lock() + defer rr.HTTPTarget.Unlock() + + rr.HTTPTarget.paramsToPath() + + var vegetaTarget = vegeta.Target{ + Method: rr.HTTPTarget.Method.String(), + URL: fmt.Sprintf(`%s%s`, rr.Target.BaseURL, rr.HTTPTarget.Path), + Header: rr.HTTPTarget.Headers.ToHTTPHeader(), + } + + var contentType = rr.HTTPTarget.RequestType.String() + if len(contentType) != 0 { + vegetaTarget.Header.Set(libhttp.HeaderContentType, contentType) + } + + if rr.HTTPTarget.WithRawBody { + vegetaTarget.Body = rr.HTTPTarget.RawBody + } else { + switch rr.HTTPTarget.RequestType { + case libhttp.RequestTypeQuery: + var q = rr.HTTPTarget.Params.ToURLValues().Encode() + if len(q) > 0 { + vegetaTarget.URL += `?` + q + } + + case libhttp.RequestTypeForm: + var form = rr.HTTPTarget.Params.ToURLValues().Encode() + vegetaTarget.Body = []byte(form) + + case libhttp.RequestTypeJSON: + var mapStringAny = rr.HTTPTarget.Params.ToJSONObject() + + vegetaTarget.Body, err = json.Marshal(mapStringAny) + + case libhttp.RequestTypeMultipartForm: + var ( + params map[string][]byte + body string + ) + + params = rr.HTTPTarget.Params.ToMultipartFormData() + contentType, body, err = libhttp.GenerateFormData(params) + if err == nil { + vegetaTarget.Body = []byte(body) + vegetaTarget.Header.Set(libhttp.HeaderContentType, contentType) + } + } + } + + return func(tgt *vegeta.Target) error { + if err != nil { + return fmt.Errorf(`DefaultHTTPAttack: %w`, err) + } + *tgt = vegetaTarget + return nil + } + } +} diff --git a/http_target.go b/http_target.go index b62156a..262f8ec 100644 --- a/http_target.go +++ b/http_target.go @@ -15,7 +15,6 @@ import ( libhttp "github.com/shuLhan/share/lib/http" "github.com/shuLhan/share/lib/mlog" libpath "github.com/shuLhan/share/lib/path" - vegeta "github.com/tsenart/vegeta/v12/lib" ) // HTTPConvertParams is a handler that will be called inside the Run handler @@ -26,10 +25,6 @@ type HTTPConvertParams func(target *HTTPTarget) (interface{}, error) // send request to run the HTTP target. type HTTPRunHandler func(rr *RunRequest) (runres *RunResponse, err error) -// HTTPAttackHandler define the function type that will be called when client -// send request to attack HTTP target. -type HTTPAttackHandler func(rr *RunRequest) vegeta.Targeter - // HTTPPreAttackHandler define the function type that will be called before // the actual Attack being called. type HTTPPreAttackHandler func(rr *RunRequest) @@ -43,7 +38,10 @@ type HTTPTarget struct { Run HTTPRunHandler `json:"-"` PreAttack HTTPPreAttackHandler `json:"-"` - Attack HTTPAttackHandler `json:"-"` + + // Attack define custom handler to generate [vegeta.Attacker]. + // This field is optional default to [DefaultAttack]. + Attack HTTPAttackHandler `json:"-"` // RequestDumper define the handler to store [http.Request] after // Run into [RunRequest] DumpRequest. @@ -135,6 +133,11 @@ func (ht *HTTPTarget) init() (err error) { if len(ht.Path) == 0 { ht.Path = "/" } + + if ht.AllowAttack && ht.Attack == nil { + ht.Attack = DefaultHTTPAttack() + } + if ht.RequestDumper == nil { ht.RequestDumper = DumpHTTPRequest } |
