mhttp: implement MListenAndServe, using new mctx/mrun/mcfg framework

This commit is contained in:
Brian Picciano 2019-01-24 19:23:04 -05:00
parent a0a531cdd2
commit ef2e76de45
2 changed files with 71 additions and 20 deletions

View File

@ -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
}

View File

@ -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)