diff options
| author | Shulhan <ms@kilabit.info> | 2021-09-25 23:50:28 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2021-09-26 01:05:32 +0700 |
| commit | 2d3e5d9f2bb36135472732f591ac0843cbe70e09 (patch) | |
| tree | 1a9cb5a7a3fbc6a6beea599870428188b4a82ec9 /http_server.go | |
| parent | 8800b8fb860f6d8429487bf6bb45dc9cca29507a (diff) | |
| download | gorankusu-2d3e5d9f2bb36135472732f591ac0843cbe70e09.tar.xz | |
all: unembed HTTP server on Trunks
Since we will implement API using WebSocket later, we need to split
between the one that manage HTTP and one that manage WebSocket.
Diffstat (limited to 'http_server.go')
| -rw-r--r-- | http_server.go | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/http_server.go b/http_server.go new file mode 100644 index 0000000..96fcb83 --- /dev/null +++ b/http_server.go @@ -0,0 +1,347 @@ +// Copyright 2021, Shulhan <ms@kilabit.info>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package trunks + +import ( + "encoding/json" + "fmt" + "net/http" + + libhttp "github.com/shuLhan/share/lib/http" + "github.com/shuLhan/share/lib/memfs" + "github.com/shuLhan/share/lib/mlog" +) + +// List of HTTP APIs provided by Trunks HTTP server. +var ( + apiEnvironmentGet = &libhttp.Endpoint{ + Method: libhttp.RequestMethodGet, + Path: "/_trunks/api/environment", + RequestType: libhttp.RequestTypeJSON, + ResponseType: libhttp.ResponseTypeJSON, + } + + apiTargetAttack = &libhttp.Endpoint{ + Method: libhttp.RequestMethodPost, + Path: "/_trunks/api/target/attack", + RequestType: libhttp.RequestTypeJSON, + ResponseType: libhttp.ResponseTypeJSON, + } + apiTargetAttackCancel = &libhttp.Endpoint{ + Method: libhttp.RequestMethodDelete, + Path: "/_trunks/api/target/attack", + RequestType: libhttp.RequestTypeNone, + ResponseType: libhttp.ResponseTypeJSON, + } + + apiTargetAttackResultDelete = &libhttp.Endpoint{ + Method: libhttp.RequestMethodDelete, + Path: "/_trunks/api/target/attack/result", + RequestType: libhttp.RequestTypeJSON, + ResponseType: libhttp.ResponseTypeJSON, + } + apiTargetAttackResultGet = &libhttp.Endpoint{ + Method: libhttp.RequestMethodGet, + Path: "/_trunks/api/target/attack/result", + RequestType: libhttp.RequestTypeQuery, + ResponseType: libhttp.ResponseTypeJSON, + } + + apiTargetRunHttp = &libhttp.Endpoint{ + Method: libhttp.RequestMethodPost, + Path: "/_trunks/api/target/run/http", + RequestType: libhttp.RequestTypeJSON, + ResponseType: libhttp.ResponseTypeJSON, + } + apiTargetRunWebSocket = &libhttp.Endpoint{ + Method: libhttp.RequestMethodPost, + Path: "/_trunks/api/target/run/websocket", + RequestType: libhttp.RequestTypeJSON, + ResponseType: libhttp.ResponseTypeJSON, + } + + apiTargets = &libhttp.Endpoint{ + Method: libhttp.RequestMethodGet, + Path: "/_trunks/api/targets", + RequestType: libhttp.RequestTypeNone, + ResponseType: libhttp.ResponseTypeJSON, + } +) + +func (trunks *Trunks) initHttpServer(isDevelopment bool) (err error) { + logp := "initHttpServer" + + httpdOpts := &libhttp.ServerOptions{ + Options: memfs.Options{ + Root: "_www", + Includes: []string{ + `.*\.(js|png|html|ico)$`, + }, + Development: isDevelopment, + }, + Memfs: memfsWWW, + Address: trunks.Env.ListenAddress, + } + + trunks.Httpd, err = libhttp.NewServer(httpdOpts) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + apiEnvironmentGet.Call = trunks.apiEnvironmentGet + err = trunks.Httpd.RegisterEndpoint(apiEnvironmentGet) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + apiTargetAttack.Call = trunks.apiTargetAttack + err = trunks.Httpd.RegisterEndpoint(apiTargetAttack) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + apiTargetAttackCancel.Call = trunks.apiTargetAttackCancel + err = trunks.Httpd.RegisterEndpoint(apiTargetAttackCancel) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + apiTargetAttackResultDelete.Call = trunks.apiTargetAttackResultDelete + err = trunks.Httpd.RegisterEndpoint(apiTargetAttackResultDelete) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + apiTargetAttackResultGet.Call = trunks.apiTargetAttackResultGet + err = trunks.Httpd.RegisterEndpoint(apiTargetAttackResultGet) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + apiTargetRunHttp.Call = trunks.apiTargetRunHttp + err = trunks.Httpd.RegisterEndpoint(apiTargetRunHttp) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + apiTargetRunWebSocket.Call = trunks.apiTargetRunWebSocket + err = trunks.Httpd.RegisterEndpoint(apiTargetRunWebSocket) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + apiTargets.Call = trunks.apiTargets + err = trunks.Httpd.RegisterEndpoint(apiTargets) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + return nil +} + +// +// apiEnvironmentGet get the Trunks environment including its state. +// +func (trunks *Trunks) apiEnvironmentGet(epr *libhttp.EndpointRequest) (resbody []byte, err error) { + res := libhttp.EndpointResponse{} + res.Code = http.StatusOK + res.Data = trunks.Env + return json.Marshal(&res) +} + +// +// apiTargetAttack run the load testing on HTTP endpoint with target and +// options defined in request. +// +func (trunks *Trunks) apiTargetAttack(epr *libhttp.EndpointRequest) (resbody []byte, err error) { + if trunks.Env.isAttackRunning() { + return nil, errAttackConflict(trunks.Env.getRunningAttack()) + } + + logp := "apiTargetAttack" + req := &RunRequest{} + + err = json.Unmarshal(epr.RequestBody, req) + if err != nil { + return nil, errInternal(err) + } + + origTarget := trunks.getTargetByID(req.Target.ID) + if origTarget == nil { + return nil, errInvalidTarget(req.Target.ID) + } + + origHttpTarget := origTarget.getHttpTargetByID(req.HttpTarget.ID) + if origTarget == nil { + return nil, errInvalidHttpTarget(req.HttpTarget.ID) + } + + if !origHttpTarget.AllowAttack { + return nil, errAttackNotAllowed() + } + + req = generateRunRequest(trunks.Env, req, origTarget, origHttpTarget) + + req.result, err = newAttackResult(trunks.Env, req) + if err != nil { + return nil, err + } + + trunks.attackq <- req + + msg := fmt.Sprintf("Attacking %s%s with %d RPS for %s seconds", + req.Target.BaseUrl, req.HttpTarget.Path, + req.Target.Opts.RatePerSecond, req.Target.Opts.Duration) + + mlog.Outf("%s: %s\n", logp, msg) + + res := libhttp.EndpointResponse{} + res.Code = http.StatusOK + res.Name = "OK_ATTACK" + res.Message = msg + + return json.Marshal(res) +} + +func (trunks *Trunks) apiTargetAttackCancel(epr *libhttp.EndpointRequest) (resbody []byte, err error) { + res := &libhttp.EndpointResponse{} + + rr := trunks.Env.getRunningAttack() + if rr == nil { + res.Code = http.StatusNotFound + res.Message = "No attack is currently running." + res.Name = "ERR_ATTACK_CANCEL_NOT_FOUND" + return nil, res + } + + trunks.cancelq <- true + + res.Code = http.StatusOK + res.Name = "OK_ATTACK_CANCEL" + res.Message = fmt.Sprintf(`Attack on target "%s / %s" has been canceled`, + rr.Target.Name, rr.HttpTarget.Name) + res.Data = rr + + return json.Marshal(res) +} + +func (trunks *Trunks) apiTargetAttackResultDelete(epr *libhttp.EndpointRequest) (resbody []byte, err error) { + name := epr.HttpRequest.Form.Get(paramNameName) + if len(name) == 0 { + return nil, errInvalidParameter(paramNameName, name) + } + + _, ht, result, err := trunks.getAttackResultByName(name) + if err != nil { + return nil, err + } + + ht.deleteResult(result) + + res := libhttp.EndpointResponse{} + res.Code = http.StatusOK + res.Name = "OK_TARGET_ATTACK_RESULT_GET" + res.Data = result + + return json.Marshal(&res) +} + +func (trunks *Trunks) apiTargetAttackResultGet(epr *libhttp.EndpointRequest) (resbody []byte, err error) { + name := epr.HttpRequest.Form.Get(paramNameName) + if len(name) == 0 { + return nil, errInvalidParameter(paramNameName, name) + } + + _, _, result, err := trunks.getAttackResultByName(name) + if err != nil { + return nil, err + } + + err = result.load() + if err != nil { + return nil, err + } + + res := libhttp.EndpointResponse{} + res.Code = http.StatusOK + res.Name = "OK_TARGET_ATTACK_RESULT_GET" + res.Data = result + + return json.Marshal(&res) +} + +func (trunks *Trunks) apiTargetRunHttp(epr *libhttp.EndpointRequest) ([]byte, error) { + req := &RunRequest{} + err := json.Unmarshal(epr.RequestBody, req) + if err != nil { + return nil, errInternal(err) + } + + origTarget := trunks.getTargetByID(req.Target.ID) + if origTarget == nil { + return nil, errInvalidTarget(req.Target.ID) + } + + origHttpTarget := origTarget.getHttpTargetByID(req.HttpTarget.ID) + if origHttpTarget == nil { + return nil, errInvalidHttpTarget(req.HttpTarget.ID) + } + + var res *RunResponse + + if origHttpTarget.Run == nil { + req.Target.BaseUrl = origTarget.BaseUrl + req.Target.Name = origTarget.Name + res, err = trunks.runHttpTarget(req) + } else { + req := generateRunRequest(trunks.Env, req, origTarget, origHttpTarget) + res, err = req.HttpTarget.Run(req) + } + if err != nil { + return nil, errInternal(err) + } + + epres := libhttp.EndpointResponse{} + epres.Code = http.StatusOK + epres.Data = res + + return json.Marshal(&epres) +} + +func (trunks *Trunks) apiTargetRunWebSocket(epr *libhttp.EndpointRequest) ([]byte, error) { + req := &RunRequest{} + err := json.Unmarshal(epr.RequestBody, req) + if err != nil { + return nil, errInternal(err) + } + + origTarget := trunks.getTargetByID(req.Target.ID) + if origTarget == nil { + return nil, errInvalidTarget(req.Target.ID) + } + + origWsTarget := origTarget.getWebSocketTargetByID(req.WebSocketTarget.ID) + if origWsTarget == nil { + return nil, errInvalidWebSocketTarget(req.WebSocketTarget.ID) + } + + req = generateWebSocketTarget(trunks.Env, req, origTarget, origWsTarget) + + res, err := req.WebSocketTarget.Run(req) + if err != nil { + return nil, errInternal(err) + } + + epres := libhttp.EndpointResponse{} + epres.Code = http.StatusOK + epres.Data = res + + return json.Marshal(&epres) +} + +func (trunks *Trunks) apiTargets(epr *libhttp.EndpointRequest) (resbody []byte, err error) { + res := libhttp.EndpointResponse{} + res.Code = http.StatusOK + res.Data = trunks.targets + return json.Marshal(&res) +} |
