From ef2e76de4529cd08c18bc48b29821f6b6d752b46 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 24 Jan 2019 19:23:04 -0500 Subject: [PATCH] mhttp: implement MListenAndServe, using new mctx/mrun/mcfg framework --- mhttp/mhttp.go | 52 ++++++++++++++++++++++++++++----------------- mhttp/mhttp_test.go | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/mhttp/mhttp.go b/mhttp/mhttp.go index 7013d0a..d6dc288 100644 --- a/mhttp/mhttp.go +++ b/mhttp/mhttp.go @@ -10,35 +10,47 @@ import ( "net/url" "strings" - "github.com/mediocregopher/mediocre-go-lib/m" - "github.com/mediocregopher/mediocre-go-lib/mcfg" + "github.com/mediocregopher/mediocre-go-lib/mctx" + "github.com/mediocregopher/mediocre-go-lib/merr" "github.com/mediocregopher/mediocre-go-lib/mlog" "github.com/mediocregopher/mediocre-go-lib/mnet" + "github.com/mediocregopher/mediocre-go-lib/mrun" ) -// CfgServer initializes and returns an *http.Server which will initialize on -// the Start hook. This also sets up config params for configuring the address -// being listened on. -func CfgServer(cfg *mcfg.Cfg, h http.Handler) *http.Server { - cfg = cfg.Child("http") - addr := cfg.ParamString("addr", ":0", "Address to listen on. Default is any open port") +// MListenAndServe returns an http.Server which will be initialized and have +// ListenAndServe called on it (asynchronously) when the start event is +// triggered on ctx (see mrun.Start). The Server will have Shutdown called on it +// when the stop event is triggered on ctx (see mrun.Stop). +// +// This function automatically handles setting up configuration parameters via +// mcfg. The default listen address is ":0". +func MListenAndServe(ctx mctx.Context, h http.Handler) *http.Server { + ctx = mctx.ChildOf(ctx, "http") + listener := mnet.MListen(ctx, "tcp", "") + listener.NoCloseOnStop = true // http.Server.Shutdown will do this + logger := mlog.From(ctx).WithKV(listener) srv := http.Server{Handler: h} - cfg.Start.Then(func(ctx context.Context) error { - srv.Addr = *addr - kv := mlog.KV{"addr": *addr} - log := m.Log(cfg, kv) - log.Info("listening") - go func() { - err := srv.ListenAndServe() - // TODO the listening log should happen here, somehow, now that we - // know the actual address being listened on - log.Fatal("http server fataled", mlog.ErrKV(err)) - }() + mrun.OnStart(ctx, func(mctx.Context) error { + srv.Addr = listener.Addr().String() + mrun.Thread(ctx, func() error { + if err := srv.Serve(listener); err != http.ErrServerClosed { + logger.Error("error serving listener", merr.KV(err)) + return err + } + return nil + }) return nil }) - // TODO shutdown logic + mrun.OnStop(ctx, func(innerCtx mctx.Context) error { + logger.Info("shutting down server") + if err := srv.Shutdown(context.Context(innerCtx)); err != nil { + return err + } + return mrun.Wait(ctx, innerCtx.Done()) + }) + return &srv } diff --git a/mhttp/mhttp_test.go b/mhttp/mhttp_test.go index af92d05..fa7fda2 100644 --- a/mhttp/mhttp_test.go +++ b/mhttp/mhttp_test.go @@ -1,12 +1,51 @@ package mhttp import ( + "bytes" + "io" + "io/ioutil" + "net/http" "net/http/httptest" . "testing" + "github.com/mediocregopher/mediocre-go-lib/mctx" + "github.com/mediocregopher/mediocre-go-lib/mlog" + "github.com/mediocregopher/mediocre-go-lib/mrun" "github.com/mediocregopher/mediocre-go-lib/mtest/massert" ) +func TestMListenAndServe(t *T) { + ctx := mctx.ChildOf(mctx.New(), "test") + mlog.CtxSet(ctx, mlog.From(ctx).WithMaxLevel(mlog.DebugLevel)) + + h := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + io.Copy(rw, r.Body) + }) + + srv := MListenAndServe(ctx, h) + if err := mrun.Start(ctx); err != nil { + t.Fatal(err) + } + + body := bytes.NewBufferString("HELLO") + resp, err := http.Post("http://"+srv.Addr, "text/plain", body) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } else if string(respBody) != "HELLO" { + t.Fatalf("unexpected respBody: %q", respBody) + } + + if err := mrun.Stop(ctx); err != nil { + t.Fatal(err) + } +} + func TestAddXForwardedFor(t *T) { assertXFF := func(prev []string, ipStr, expected string) massert.Assertion { r := httptest.NewRequest("GET", "/", nil)