diff options
| -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 } |
