From 2b6dc0730aa3c30787e440c1090b8041ff6b4ca8 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Fri, 16 Feb 2024 00:24:47 +0700 Subject: 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 --- errors.go | 8 ----- example.go | 89 +++----------------------------------------------- go.mod | 2 +- go.sum | 4 +-- gorankusu.go | 3 -- http_attack_handler.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++ http_target.go | 15 +++++---- 7 files changed, 99 insertions(+), 104 deletions(-) create mode 100644 http_attack_handler.go diff --git a/errors.go b/errors.go index 42781ea..b2ff902 100644 --- a/errors.go +++ b/errors.go @@ -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{ diff --git a/example.go b/example.go index 16fa47b..cf53ae9 100644 --- a/example.go +++ b/example.go @@ -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 diff --git a/go.mod b/go.mod index 46042f0..7d18ecd 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index b708445..36e21d7 100644 --- a/go.sum +++ b/go.sum @@ -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 +// 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 } -- cgit v1.3