From 9d60d32e4548d50264841dc98622aa2e45bc9d2a Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Thu, 6 May 2021 07:52:27 -0400 Subject: [PATCH] server: Simplify the API for static and single-run services. - Rework NewStatic as "Static", which returns a plain constructor. - Remove the "Simple" type and move its Run method to a top-level function. - Update usage examples. https://github.com/creachadair/jrpc2/issues/46#issuecomment-831968340 --- cmd/examples/server/server.go | 2 +- server/example_test.go | 4 +-- server/loop.go | 14 ++++----- server/loop_test.go | 2 +- server/run.go | 23 ++++++++++++++ server/{simple_test.go => run_test.go} | 5 ++- server/simple.go | 43 -------------------------- 7 files changed, 35 insertions(+), 58 deletions(-) create mode 100644 server/run.go rename server/{simple_test.go => run_test.go} (92%) delete mode 100644 server/simple.go diff --git a/cmd/examples/server/server.go b/cmd/examples/server/server.go index cd526ce..21f3fd7 100644 --- a/cmd/examples/server/server.go +++ b/cmd/examples/server/server.go @@ -100,7 +100,7 @@ func main() { log.Fatalln("Listen:", err) } log.Printf("Listening at %v...", lst.Addr()) - server.Loop(lst, server.NewStatic(mux), &server.LoopOptions{ + server.Loop(lst, server.Static(mux), &server.LoopOptions{ ServerOptions: &jrpc2.ServerOptions{ Logger: log.New(os.Stderr, "[jrpc2.Server] ", log.LstdFlags|log.Lshortfile), Concurrency: *maxTasks, diff --git a/server/example_test.go b/server/example_test.go index ab70288..c0acb89 100644 --- a/server/example_test.go +++ b/server/example_test.go @@ -46,10 +46,10 @@ func (s Service) Finish(stat jrpc2.ServerStatus) { close(s.done) } -func ExampleNewSimple() { +func ExampleRun() { done := make(chan struct{}) cch, sch := channel.Direct() - go server.NewSimple(Service{done}, nil).Run(sch) + go server.Run(sch, Service{done}, nil) cli := jrpc2.NewClient(cch, nil) if _, err := cli.Call(context.Background(), "Hello", nil); err != nil { diff --git a/server/loop.go b/server/loop.go index 5e02afb..365c7b0 100644 --- a/server/loop.go +++ b/server/loop.go @@ -19,16 +19,14 @@ type Service interface { Finish(jrpc2.ServerStatus) } -type singleton struct{ assigner jrpc2.Assigner } +// Static wraps a jrpc2.Assigner to trivially implement the Service interface. +func Static(m jrpc2.Assigner) func() Service { return static{methods: m}.New } -func (s singleton) Assigner() (jrpc2.Assigner, error) { return s.assigner, nil } -func (singleton) Finish(jrpc2.ServerStatus) {} +type static struct{ methods jrpc2.Assigner } -// NewStatic creates a static (singleton) service from the given assigner. -func NewStatic(assigner jrpc2.Assigner) func() Service { - svc := singleton{assigner} - return func() Service { return svc } -} +func (s static) New() Service { return s } +func (s static) Assigner() (jrpc2.Assigner, error) { return s.methods, nil } +func (static) Finish(jrpc2.ServerStatus) {} // Loop obtains connections from lst and starts a server for each with the // given service constructor and options, running in a new goroutine. If accept diff --git a/server/loop_test.go b/server/loop_test.go index df61a16..65b2d3d 100644 --- a/server/loop_test.go +++ b/server/loop_test.go @@ -16,7 +16,7 @@ import ( var newChan = channel.Line // A static test service that returns the same thing each time. -var testService = NewStatic(handler.Map{ +var testService = Static(handler.Map{ "Test": handler.New(func(context.Context) (string, error) { return "OK", nil }), diff --git a/server/run.go b/server/run.go new file mode 100644 index 0000000..66f3133 --- /dev/null +++ b/server/run.go @@ -0,0 +1,23 @@ +package server + +import ( + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/channel" +) + +// Run starts a server for svc on the given channel, and blocks until it +// returns. The server exit status is reported to the service, and the error +// value is returned. +// +// If the caller does not need the error value and does not want to wait for +// the server to complete, call Run in a goroutine. +func Run(ch channel.Channel, svc Service, opts *jrpc2.ServerOptions) error { + assigner, err := svc.Assigner() + if err != nil { + return err + } + srv := jrpc2.NewServer(assigner, opts).Start(ch) + stat := srv.WaitStatus() + svc.Finish(stat) + return stat.Err +} diff --git a/server/simple_test.go b/server/run_test.go similarity index 92% rename from server/simple_test.go rename to server/run_test.go index 378369e..7993fa1 100644 --- a/server/simple_test.go +++ b/server/run_test.go @@ -26,7 +26,7 @@ func (t *testService) Finish(stat jrpc2.ServerStatus) { t.stat = stat } -func TestSimple(t *testing.T) { +func TestRun(t *testing.T) { svc := &testService{assigner: handler.Map{ "Test": handler.New(func(ctx context.Context) string { return "OK" @@ -44,8 +44,7 @@ func TestSimple(t *testing.T) { } }() - srv := server.NewSimple(svc, nil) - if err := srv.Run(spipe); err != nil { + if err := server.Run(spipe, svc, nil); err != nil { t.Errorf("Server failed: %v", err) } if result != "OK" { diff --git a/server/simple.go b/server/simple.go deleted file mode 100644 index 70ee3de..0000000 --- a/server/simple.go +++ /dev/null @@ -1,43 +0,0 @@ -package server - -import ( - "errors" - - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/channel" -) - -// A Simple server manages execution of a server for a single service instance. -type Simple struct { - running bool - svc Service - opts *jrpc2.ServerOptions -} - -// NewSimple constructs a new, unstarted *Simple instance for the given -// service. When run, the server will use the specified options. -func NewSimple(svc Service, opts *jrpc2.ServerOptions) *Simple { - return &Simple{svc: svc, opts: opts} -} - -// Run starts a server on the given channel, and blocks until it returns. The -// server exit status is reported to the service, and the error value returned. -// Once Run returns, it can be run again with a new channel. -// -// If the caller does not need the error value and does not want to wait for -// the server to complete, call Run in a goroutine. -func (s *Simple) Run(ch channel.Channel) error { - if s.running { // safety check - return errors.New("server is already running") - } - assigner, err := s.svc.Assigner() - if err != nil { - return err - } - s.running = true - srv := jrpc2.NewServer(assigner, s.opts).Start(ch) - stat := srv.WaitStatus() - s.svc.Finish(stat) - s.running = false - return stat.Err -}