change: update docs

This commit is contained in:
徐聪
2022-07-25 21:29:19 +08:00
parent 60a6aebbcb
commit 1c5039697a
14 changed files with 93 additions and 66 deletions
+31 -7
View File
@@ -1,17 +1,25 @@
# Release History # Release History
## v4.2.0 (2022-07-22)
**go version**
- feat: support multi-machine collaborative distributed load testing
## v4.1.7 (2022-07-18) ## v4.1.7 (2022-07-18)
**go version** **go version**
- fix: using `@FILEPATH` to indicate the path of the file
- feat: support indicating type and filename when uploading file - feat: support indicating type and filename when uploading file
- feat: support to infer MIME type of the file automatically - feat: support to infer MIME type of the file automatically
- feat: support omitting websocket url if not necessary - feat: support omitting websocket url if not necessary
- feat: support multiple websocket connections each session - feat: support multiple websocket connections each session
- fix: optimize websocket step initialization
- feat: support convert curl command(s) to testcase(s) - feat: support convert curl command(s) to testcase(s)
- feat: support run curl as subcommand of run/boom/convert - feat: support run curl as subcommand of run/boom/convert
- fix: optimize websocket step initialization
- fix: using `@FILEPATH` to indicate the path of the file
- fix: reuse plugin instance if it already initialized
- fix: deep copy api step to avoid data racing
## v4.1.6 (2022-07-04) ## v4.1.6 (2022-07-04)
@@ -276,7 +284,8 @@
- feat: implement `transaction` mechanism for load test - feat: implement `transaction` mechanism for load test
- feat: continue running next step when failure occurs with `--continue-on-failure` flag, default to failfast - feat: continue running next step when failure occurs with `--continue-on-failure` flag, default to failfast
- feat: report GA events with version - feat: report GA events with version
- feat: run load test with the given limit and burst as rate limiter, use `--spawn-count`, `--spawn-rate` and `--request-increase-rate` flag - feat: run load test with the given limit and burst as rate limiter, use `--spawn-count`, `--spawn-rate`
and `--request-increase-rate` flag
- feat: report runner state to prometheus - feat: report runner state to prometheus
- refactor: fork [boomer] as submodule initially and made a lot of changes - refactor: fork [boomer] as submodule initially and made a lot of changes
- change: update API models - change: update API models
@@ -330,7 +339,8 @@
## 3.1.8 (2022-03-22) ## 3.1.8 (2022-03-22)
- feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json configuration file - feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json configuration
file
- feat: support variable and function in response extract expression - feat: support variable and function in response extract expression
- fix: keep negative index in jmespath unchanged when converting pytest files, e.g. body.users[-1] - fix: keep negative index in jmespath unchanged when converting pytest files, e.g. body.users[-1]
- fix: variable should not start with digit - fix: variable should not start with digit
@@ -462,9 +472,9 @@
**Changed** **Changed**
- change: override variables - change: override variables
(1) testcase: session variables > step variables > config variables (1) testcase: session variables > step variables > config variables
(2) testsuite: testcase variables > config variables (2) testsuite: testcase variables > config variables
(3) testsuite testcase variables > testcase config variables (3) testsuite testcase variables > testcase config variables
**Fixed** **Fixed**
@@ -648,17 +658,31 @@ reference: [v2-changelog]
[hrp]: https://github.com/httprunner/hrp [hrp]: https://github.com/httprunner/hrp
[hashicorp/go-plugin]: https://github.com/hashicorp/go-plugin [hashicorp/go-plugin]: https://github.com/hashicorp/go-plugin
[go plugin]: https://pkg.go.dev/plugin [go plugin]: https://pkg.go.dev/plugin
[docs repo]: https://github.com/httprunner/httprunner.github.io [docs repo]: https://github.com/httprunner/httprunner.github.io
[zerolog]: https://github.com/rs/zerolog [zerolog]: https://github.com/rs/zerolog
[jmespath]: https://jmespath.org/ [jmespath]: https://jmespath.org/
[mkdocs]: https://www.mkdocs.org/ [mkdocs]: https://www.mkdocs.org/
[github-actions]: https://github.com/httprunner/hrp/actions [github-actions]: https://github.com/httprunner/hrp/actions
[boomer]: github.com/myzhan/boomer [boomer]: github.com/myzhan/boomer
[sentry sdk]: https://github.com/getsentry/sentry-go [sentry sdk]: https://github.com/getsentry/sentry-go
[pushgateway]: https://github.com/prometheus/pushgateway [pushgateway]: https://github.com/prometheus/pushgateway
[locust]: https://locust.io/ [locust]: https://locust.io/
[black]: https://github.com/psf/black [black]: https://github.com/psf/black
[loguru]: https://github.com/Delgan/loguru [loguru]: https://github.com/Delgan/loguru
[v2-changelog]: https://github.com/httprunner/httprunner/blob/v2/docs/CHANGELOG.md [v2-changelog]: https://github.com/httprunner/httprunner/blob/v2/docs/CHANGELOG.md
+2 -1
View File
@@ -21,7 +21,7 @@ hrp boom [flags]
### Options ### Options
``` ```
--autostart Starts the test immediately (without disabling the web UI). Use --spawn-count and --spawn-rate to control user count and run time --autostart Starts the test immediately (without disabling the web UI). Use --spawn-count and --spawn-rate to control user count and increase rate
--cpu-profile string Enable CPU profiling. --cpu-profile string Enable CPU profiling.
--cpu-profile-duration duration CPU profile duration. (default 30s) --cpu-profile-duration duration CPU profile duration. (default 30s)
--disable-compression Disable compression --disable-compression Disable compression
@@ -36,6 +36,7 @@ hrp boom [flags]
--master-bind-host string Interfaces (hostname, ip) that hrp master should bind to. Only used when running with --master. Defaults to * (all available interfaces). (default "127.0.0.1") --master-bind-host string Interfaces (hostname, ip) that hrp master should bind to. Only used when running with --master. Defaults to * (all available interfaces). (default "127.0.0.1")
--master-bind-port int Port that hrp master should bind to. Only used when running with --master. Defaults to 5557. (default 5557) --master-bind-port int Port that hrp master should bind to. Only used when running with --master. Defaults to 5557. (default 5557)
--master-host string Host or IP address of hrp master for distributed load testing. (default "127.0.0.1") --master-host string Host or IP address of hrp master for distributed load testing. (default "127.0.0.1")
--master-http-address string Interfaces (ip:port) that hrp master should control by user. Only used when running with --master. Defaults to *:9771. (default ":9771")
--master-port int The port to connect to that is used by the hrp master for distributed load testing. (default 5557) --master-port int The port to connect to that is used by the hrp master for distributed load testing. (default 5557)
--max-rps int Max RPS that boomer can generate, disabled by default. --max-rps int Max RPS that boomer can generate, disabled by default.
--mem-profile string Enable memory profiling. --mem-profile string Enable memory profiling.
+2 -2
View File
@@ -1,5 +1,5 @@
{ {
"project_name": "demo-empty-project", "project_name": "demo-empty-project",
"create_time": "2022-07-04T14:54:33.795693+08:00", "create_time": "2022-07-11T11:45:29.942532+08:00",
"hrp_version": "v4.1.5" "hrp_version": "v4.1.6"
} }
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"project_name": "demo-with-go-plugin", "project_name": "demo-with-go-plugin",
"create_time": "2022-07-06T13:57:04.054424+08:00", "create_time": "2022-07-11T11:44:36.214909+08:00",
"hrp_version": "v4.1.6" "hrp_version": "v4.1.6"
} }
@@ -20,4 +20,4 @@ if __name__ == "__main__":
funppy.register("concatenate", concatenate) funppy.register("concatenate", concatenate)
funppy.register("setup_hook_example", setup_hook_example) funppy.register("setup_hook_example", setup_hook_example)
funppy.register("teardown_hook_example", teardown_hook_example) funppy.register("teardown_hook_example", teardown_hook_example)
funppy.serve() funppy.serve()
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"project_name": "demo-with-py-plugin", "project_name": "demo-with-py-plugin",
"create_time": "2022-07-06T13:57:04.482633+08:00", "create_time": "2022-07-11T11:44:37.021634+08:00",
"hrp_version": "v4.1.6" "hrp_version": "v4.1.6"
} }
+2 -2
View File
@@ -1,5 +1,5 @@
{ {
"project_name": "demo-without-plugin", "project_name": "demo-without-plugin",
"create_time": "2022-07-04T14:54:33.495643+08:00", "create_time": "2022-07-11T11:45:29.800018+08:00",
"hrp_version": "v4.1.5" "hrp_version": "v4.1.6"
} }
+4 -1
View File
@@ -208,6 +208,7 @@ func (b *HRPBoomer) runTestCases(testCases []*TCase, profile *boomer.Profile) {
testcases = append(testcases, tesecase) testcases = append(testcases, tesecase)
} }
b.SetProfile(profile) b.SetProfile(profile)
b.InitBoomer() b.InitBoomer()
log.Info().Interface("testcases", testcases).Interface("profile", profile).Msg("run tasks successful") log.Info().Interface("testcases", testcases).Interface("profile", profile).Msg("run tasks successful")
@@ -222,7 +223,7 @@ func (b *HRPBoomer) rebalanceBoomer(profile *boomer.Profile) {
log.Info().Interface("profile", profile).Msg("rebalance tasks successful") log.Info().Interface("profile", profile).Msg("rebalance tasks successful")
} }
func (b *HRPBoomer) PollTasks() { func (b *HRPBoomer) PollTasks(ctx context.Context) {
for { for {
select { select {
case task := <-b.Boomer.GetTasksChan(): case task := <-b.Boomer.GetTasksChan():
@@ -240,6 +241,8 @@ func (b *HRPBoomer) PollTasks() {
case <-b.Boomer.GetCloseChan(): case <-b.Boomer.GetCloseChan():
return return
case <-ctx.Done():
return
} }
} }
} }
+5 -4
View File
@@ -71,7 +71,7 @@ var boomCmd = &cobra.Command{
if boomArgs.autoStart { if boomArgs.autoStart {
hrpBoomer.InitBoomer() hrpBoomer.InitBoomer()
} else { } else {
go hrpBoomer.StartServer() go hrpBoomer.StartServer(ctx, boomArgs.masterHttpAddress)
} }
go hrpBoomer.PollTestCases(ctx) go hrpBoomer.PollTestCases(ctx)
hrpBoomer.RunMaster() hrpBoomer.RunMaster()
@@ -79,9 +79,8 @@ var boomCmd = &cobra.Command{
if boomArgs.ignoreQuit { if boomArgs.ignoreQuit {
hrpBoomer.SetIgnoreQuit() hrpBoomer.SetIgnoreQuit()
} }
go hrpBoomer.PollTasks() go hrpBoomer.PollTasks(ctx)
hrpBoomer.RunWorker() hrpBoomer.RunWorker()
time.Sleep(3 * time.Second)
case "standalone": case "standalone":
if venv != "" { if venv != "" {
hrpBoomer.SetPython3Venv(venv) hrpBoomer.SetPython3Venv(venv)
@@ -102,6 +101,7 @@ type BoomArgs struct {
masterPort int masterPort int
masterBindHost string masterBindHost string
masterBindPort int masterBindPort int
masterHttpAddress string
autoStart bool autoStart bool
expectWorkers int expectWorkers int
expectWorkersMaxWait int expectWorkersMaxWait int
@@ -129,11 +129,12 @@ func init() {
boomCmd.Flags().BoolVar(&boomArgs.master, "master", false, "master of distributed testing") boomCmd.Flags().BoolVar(&boomArgs.master, "master", false, "master of distributed testing")
boomCmd.Flags().StringVar(&boomArgs.masterBindHost, "master-bind-host", "127.0.0.1", "Interfaces (hostname, ip) that hrp master should bind to. Only used when running with --master. Defaults to * (all available interfaces).") boomCmd.Flags().StringVar(&boomArgs.masterBindHost, "master-bind-host", "127.0.0.1", "Interfaces (hostname, ip) that hrp master should bind to. Only used when running with --master. Defaults to * (all available interfaces).")
boomCmd.Flags().IntVar(&boomArgs.masterBindPort, "master-bind-port", 5557, "Port that hrp master should bind to. Only used when running with --master. Defaults to 5557.") boomCmd.Flags().IntVar(&boomArgs.masterBindPort, "master-bind-port", 5557, "Port that hrp master should bind to. Only used when running with --master. Defaults to 5557.")
boomCmd.Flags().StringVar(&boomArgs.masterHttpAddress, "master-http-address", ":9771", "Interfaces (ip:port) that hrp master should control by user. Only used when running with --master. Defaults to *:9771.")
boomCmd.Flags().BoolVar(&boomArgs.worker, "worker", false, "worker of distributed testing") boomCmd.Flags().BoolVar(&boomArgs.worker, "worker", false, "worker of distributed testing")
boomCmd.Flags().BoolVar(&boomArgs.ignoreQuit, "ignore-quit", false, "ignores quit from master (only when --worker is used)") boomCmd.Flags().BoolVar(&boomArgs.ignoreQuit, "ignore-quit", false, "ignores quit from master (only when --worker is used)")
boomCmd.Flags().StringVar(&boomArgs.masterHost, "master-host", "127.0.0.1", "Host or IP address of hrp master for distributed load testing.") boomCmd.Flags().StringVar(&boomArgs.masterHost, "master-host", "127.0.0.1", "Host or IP address of hrp master for distributed load testing.")
boomCmd.Flags().IntVar(&boomArgs.masterPort, "master-port", 5557, "The port to connect to that is used by the hrp master for distributed load testing.") boomCmd.Flags().IntVar(&boomArgs.masterPort, "master-port", 5557, "The port to connect to that is used by the hrp master for distributed load testing.")
boomCmd.Flags().BoolVar(&boomArgs.autoStart, "autostart", false, "Starts the test immediately (without disabling the web UI). Use --spawn-count and --spawn-rate to control user count and run time") boomCmd.Flags().BoolVar(&boomArgs.autoStart, "autostart", false, "Starts the test immediately (without disabling the web UI). Use --spawn-count and --spawn-rate to control user count and increase rate")
boomCmd.Flags().IntVar(&boomArgs.expectWorkers, "expect-workers", 1, "How many workers master should expect to connect before starting the test (only when --autostart is used)") boomCmd.Flags().IntVar(&boomArgs.expectWorkers, "expect-workers", 1, "How many workers master should expect to connect before starting the test (only when --autostart is used)")
boomCmd.Flags().IntVar(&boomArgs.expectWorkersMaxWait, "expect-workers-max-wait", 0, "How many workers master should expect to connect before starting the test (only when --autostart is used") boomCmd.Flags().IntVar(&boomArgs.expectWorkersMaxWait, "expect-workers-max-wait", 0, "How many workers master should expect to connect before starting the test (only when --autostart is used")
} }
+3
View File
@@ -461,6 +461,9 @@ func (b *Boomer) Start(Args *Profile) error {
if b.masterRunner.isStarted() { if b.masterRunner.isStarted() {
return errors.New("already started") return errors.New("already started")
} }
if b.masterRunner.getState() == StateStopping {
return errors.New("Please wait for all workers to finish")
}
b.SetSpawnCount(Args.SpawnCount) b.SetSpawnCount(Args.SpawnCount)
b.SetSpawnRate(Args.SpawnRate) b.SetSpawnRate(Args.SpawnRate)
b.SetProfile(Args) b.SetProfile(Args)
+1 -1
View File
@@ -216,7 +216,7 @@ func (c *grpcClient) newBiStreamClient() (err error) {
return err return err
} }
c.config.setBiStreamClient(biStream) c.config.setBiStreamClient(biStream)
println("successful to establish bidirectional stream with master, press Ctrl+c to quit.\n") println("successful to establish bidirectional stream with master, press Ctrl+c to quit.")
return nil return nil
} }
+30 -38
View File
@@ -10,12 +10,10 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/go-errors/errors"
"github.com/httprunner/httprunner/v4/hrp/internal/boomer/grpc/messager" "github.com/httprunner/httprunner/v4/hrp/internal/boomer/grpc/messager"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/go-errors/errors"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -200,15 +198,15 @@ type runner struct {
controller *Controller controller *Controller
loop *Loop // specify loop count for testcase, count = loopCount * spawnCount loop *Loop // specify loop count for testcase, count = loopCount * spawnCount
// rebalance spawn // dynamically balance boomer running parameters
rebalance chan bool rebalance chan bool
// all running workers(goroutines) will select on this channel. // stop signals the run goroutine should shutdown.
// close this channel will stop all running workers.
stopChan chan bool stopChan chan bool
// all running workers(goroutines) will select on this channel.
// stopping is closed by run goroutine on shutdown.
stoppingChan chan bool stoppingChan chan bool
// done is closed when all goroutines from start() complete.
doneChan chan bool doneChan chan bool
reportChan chan bool reportChan chan bool
@@ -216,10 +214,10 @@ type runner struct {
// close this channel will stop all goroutines used in runner. // close this channel will stop all goroutines used in runner.
closeChan chan bool closeChan chan bool
// wgMu blocks concurrent waitgroup mutation while server stopping // wgMu blocks concurrent waitgroup mutation while boomer stopping
wgMu sync.RWMutex wgMu sync.RWMutex
// wg is used to wait for the goroutines that depends on the server state // wg is used to wait for all running workers(goroutines) that depends on the boomer state
// to exit when stopping the server. // to exit when stopping the boomer.
wg sync.WaitGroup wg sync.WaitGroup
outputs []Output outputs []Output
@@ -544,15 +542,20 @@ func (r *runner) statsStart() {
func (r *runner) stop() { func (r *runner) stop() {
// stop previous goroutines without blocking // stop previous goroutines without blocking
// those goroutines will exit when r.safeRun returns // those goroutines will exit when r.safeRun returns
r.Stop() r.gracefulStop()
if r.rateLimitEnabled { if r.rateLimitEnabled {
r.rateLimiter.Stop() r.rateLimiter.Stop()
} }
r.updateState(StateStopped) r.updateState(StateStopped)
} }
// HardStop stops the server without coordination with other members in the cluster. // gracefulStop stops the boomer gracefully, and shuts down the running goroutine.
func (r *runner) hardStop() { // gracefulStop should be called after a start(), otherwise it will block forever.
// When stopping leader, Stop transfers its leadership to one of its peers
// before stopping the boomer.
// gracefulStop terminates the boomer and performs any necessary finalization.
// Do and Process cannot be called after Stop has been invoked.
func (r *runner) gracefulStop() {
select { select {
case r.stopChan <- true: case r.stopChan <- true:
case <-r.doneChan: case <-r.doneChan:
@@ -561,30 +564,16 @@ func (r *runner) hardStop() {
<-r.doneChan <-r.doneChan
} }
// Stop stops the server gracefully, and shuts down the running goroutine. // StopNotify returns a channel that receives a bool type value
// Stop should be called after a Start(s), otherwise it will block forever. // when the runner is stopped.
// When stopping leader, Stop transfers its leadership to one of its peers func (r *runner) StopNotify() <-chan bool { return r.doneChan }
// before stopping the server.
// Stop terminates the Server and performs any necessary finalization.
// Do and Process cannot be called after Stop has been invoked.
func (r *runner) Stop() {
r.hardStop()
}
// StopNotify returns a channel that receives a empty struct // StoppingNotify returns a channel that receives a bool type value
// when the server is stopped. // when the runner is being stopped.
func (r *runner) StopNotify() <-chan bool { return r.stopChan }
// DoneNotify returns a channel that receives a empty struct
// when the server is stopped.
func (r *runner) DoneNotify() <-chan bool { return r.doneChan }
// StoppingNotify returns a channel that receives a empty struct
// when the server is being stopped.
func (r *runner) StoppingNotify() <-chan bool { return r.stoppingChan } func (r *runner) StoppingNotify() <-chan bool { return r.stoppingChan }
// RebalanceNotify returns a channel that receives a empty struct // RebalanceNotify returns a channel that receives a bool type value
// when the server is being stopped. // when the runner is being rebalance.
func (r *runner) RebalanceNotify() <-chan bool { return r.rebalance } func (r *runner) RebalanceNotify() <-chan bool { return r.rebalance }
func (r *runner) getState() int32 { func (r *runner) getState() int32 {
@@ -758,6 +747,7 @@ func (r *workerRunner) onMessage(msg *genericMessage) {
r.onSpawnMessage(msg) r.onSpawnMessage(msg)
case "quit": case "quit":
if r.ignoreQuit { if r.ignoreQuit {
log.Warn().Msg("master already quit, waiting to reconnect master.")
break break
} }
r.close() r.close()
@@ -777,6 +767,7 @@ func (r *workerRunner) onMessage(msg *genericMessage) {
case "quit": case "quit":
r.stop() r.stop()
if r.ignoreQuit { if r.ignoreQuit {
log.Warn().Msg("master already quit, waiting to reconnect master.")
break break
} }
r.close() r.close()
@@ -788,6 +779,7 @@ func (r *workerRunner) onMessage(msg *genericMessage) {
r.onSpawnMessage(msg) r.onSpawnMessage(msg)
case "quit": case "quit":
if r.ignoreQuit { if r.ignoreQuit {
log.Warn().Msg("master already quit, waiting to reconnect master.")
break break
} }
r.close() r.close()
@@ -815,13 +807,13 @@ func (r *workerRunner) startListener() {
// run worker service // run worker service
func (r *workerRunner) run() { func (r *workerRunner) run() {
println("\n========================= HttpRunner Worker for Distributed Load Testing ========================= ") println("==================== HttpRunner Worker for Distributed Load Testing ==================== ")
r.updateState(StateInit) r.updateState(StateInit)
r.client = newClient(r.masterHost, r.masterPort, r.nodeID) r.client = newClient(r.masterHost, r.masterPort, r.nodeID)
println(fmt.Sprintf("ready to connect master to %s:%d", r.masterHost, r.masterPort)) println(fmt.Sprintf("ready to connect master to %s:%d", r.masterHost, r.masterPort))
err := r.client.start() err := r.client.start()
if err != nil { if err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("failed to connect to master(%s:%d) with error %v\n", r.masterHost, r.masterPort)) log.Error().Err(err).Msg(fmt.Sprintf("failed to connect to master(%s:%d)", r.masterHost, r.masterPort))
} }
if err = r.client.register(r.client.config.ctx); err != nil { if err = r.client.register(r.client.config.ctx); err != nil {
@@ -904,7 +896,7 @@ func (r *workerRunner) start() {
r.rateLimiter.Start() r.rateLimiter.Start()
} }
r.once.Do(r.outputOnStart) r.outputOnStart()
go r.spawnWorkers(r.getSpawnCount(), r.getSpawnRate(), r.stoppingChan, r.spawnComplete) go r.spawnWorkers(r.getSpawnCount(), r.getSpawnRate(), r.stoppingChan, r.spawnComplete)
+2 -2
View File
@@ -126,13 +126,13 @@ func TestStopNotify(t *testing.T) {
close(r.doneChan) close(r.doneChan)
}() }()
notifier := r.DoneNotify() notifier := r.StopNotify()
select { select {
case <-notifier: case <-notifier:
t.Fatalf("received unexpected stop notification") t.Fatalf("received unexpected stop notification")
default: default:
} }
r.Stop() r.gracefulStop()
select { select {
case <-notifier: case <-notifier:
default: default:
+8 -5
View File
@@ -163,7 +163,7 @@ func (api *apiHandler) Index(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not Found", http.StatusNotFound) http.Error(w, "Not Found", http.StatusNotFound)
return return
} }
w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' camo.githubusercontent.com") w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' www.httprunner.com")
fmt.Fprintf(w, "Welcome to httprunner page!") fmt.Fprintf(w, "Welcome to httprunner page!")
} }
@@ -315,23 +315,26 @@ func (api *apiHandler) Handler() http.Handler {
func (apiHandler) ServeHTTP(http.ResponseWriter, *http.Request) {} func (apiHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}
func (b *HRPBoomer) StartServer() { func (b *HRPBoomer) StartServer(ctx context.Context, addr string) {
h := b.NewAPIHandler() h := b.NewAPIHandler()
mux := h.Handler() mux := h.Handler()
server := &http.Server{ server := &http.Server{
Addr: ":9771", Addr: addr,
Handler: mux, Handler: mux,
} }
go func() { go func() {
<-b.GetCloseChan() select {
case <-ctx.Done():
case <-b.GetCloseChan():
}
if err := server.Shutdown(context.Background()); err != nil { if err := server.Shutdown(context.Background()); err != nil {
log.Fatal("shutdown server:", err) log.Fatal("shutdown server:", err)
} }
}() }()
log.Println("Starting HTTP server...") log.Println(fmt.Sprintf("starting HTTP server (%v), please use the API to control master", server.Addr))
err := server.ListenAndServe() err := server.ListenAndServe()
if err != nil { if err != nil {
if err == http.ErrServerClosed { if err == http.ErrServerClosed {