146 lines
4.2 KiB
Go
Raw Permalink Normal View History

2022-02-06 07:06:32 +00:00
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
2021-12-04 16:42:11 +00:00
// Package server provides support routines for running jrpc2 servers.
package server
import (
2022-02-06 07:06:32 +00:00
"context"
2021-12-04 16:42:11 +00:00
"net"
"sync"
"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/channel"
)
// Service is the interface used by the Loop and Run functions to start up a
// server. The methods of this interface allow the instance to manage its
// state: The Assigner method is called before the server is started, and can
// be used to initialize the service. The Finish method is called after the
// server exits, and can be used to clean up.
type Service interface {
// This method is called to create an assigner and initialize the service
// for use. If it reports an error, the server is not started.
Assigner() (jrpc2.Assigner, error)
// This method is called when the server for this service has exited.
// The arguments are the assigner returned by the Assigner method and the
// server exit status.
Finish(jrpc2.Assigner, jrpc2.ServerStatus)
}
// Static wraps a jrpc2.Assigner to trivially implement the Service interface.
func Static(m jrpc2.Assigner) func() Service { return static{methods: m}.New }
type static struct{ methods jrpc2.Assigner }
func (s static) New() Service { return s }
func (s static) Assigner() (jrpc2.Assigner, error) { return s.methods, nil }
func (static) Finish(jrpc2.Assigner, jrpc2.ServerStatus) {}
// An Accepter obtains client connections from an external source and
// constructs channels from them.
type Accepter interface {
2022-02-06 07:06:32 +00:00
// Accept blocks until a connection is available, or until ctx ends.
// If a connection is found, Accept returns a new channel for it.
Accept(ctx context.Context) (channel.Channel, error)
2021-12-04 16:42:11 +00:00
}
// NetAccepter adapts a net.Listener to the Accepter interface, using f as the
// channel framing.
func NetAccepter(lst net.Listener, f channel.Framing) Accepter {
return netAccepter{Listener: lst, newChannel: f}
}
type netAccepter struct {
net.Listener
newChannel channel.Framing
}
2022-02-06 07:06:32 +00:00
func (n netAccepter) Accept(ctx context.Context) (channel.Channel, error) {
// A net.Listener does not obey a context, so simulate it by closing the
// listener if ctx ends.
ok := make(chan struct{})
defer close(ok)
go func() {
select {
case <-ctx.Done():
n.Listener.Close()
case <-ok:
return
}
}()
2021-12-04 16:42:11 +00:00
conn, err := n.Listener.Accept()
if err != nil {
return nil, err
}
return n.newChannel(conn, conn), nil
}
// Loop obtains connections from lst and starts a server for each using a
// service instance returned by newService and the given options. Each server
// runs in a new goroutine.
//
2022-02-06 07:06:32 +00:00
// If lst is closed or otherwise reports an error, the loop will terminate.
// The error will be reported to the caller of Loop once any active servers
// have returned. In addition, if ctx ends, any active servers will be stopped.
func Loop(ctx context.Context, lst Accepter, newService func() Service, opts *LoopOptions) error {
2021-12-04 16:42:11 +00:00
serverOpts := opts.serverOpts()
log := func(string, ...interface{}) {}
if serverOpts != nil && serverOpts.Logger != nil {
log = serverOpts.Logger.Printf
}
var wg sync.WaitGroup
for {
2022-02-06 07:06:32 +00:00
ch, err := lst.Accept(ctx)
2021-12-04 16:42:11 +00:00
if err != nil {
if channel.IsErrClosing(err) {
err = nil
} else {
log("Error accepting new connection: %v", err)
}
wg.Wait()
return err
}
wg.Add(1)
go func() {
defer wg.Done()
2022-02-06 07:06:32 +00:00
2021-12-04 16:42:11 +00:00
svc := newService()
assigner, err := svc.Assigner()
if err != nil {
log("Service initialization failed: %v", err)
return
}
2022-02-06 07:06:32 +00:00
sctx, cancel := context.WithCancel(ctx)
defer cancel()
2021-12-04 16:42:11 +00:00
srv := jrpc2.NewServer(assigner, serverOpts).Start(ch)
2022-02-06 07:06:32 +00:00
go func() { <-sctx.Done(); srv.Stop() }()
2021-12-04 16:42:11 +00:00
stat := srv.WaitStatus()
svc.Finish(assigner, stat)
if stat.Err != nil {
log("Server exit: %v", stat.Err)
}
}()
}
}
// LoopOptions control the behaviour of the Loop function. A nil *LoopOptions
// provides default values as described.
type LoopOptions struct {
// If non-nil, these options are used when constructing the server to
// handle requests on an inbound connection.
ServerOptions *jrpc2.ServerOptions
}
func (o *LoopOptions) serverOpts() *jrpc2.ServerOptions {
if o == nil {
return nil
}
return o.ServerOptions
}