104 lines
2.9 KiB
Go

// Package server provides support routines for running jrpc2 servers.
package server
import (
"net"
"sync"
"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/channel"
)
// Service is the interface used by the Loop function to start up a server.
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.
Finish(jrpc2.ServerStatus)
}
type singleton struct{ assigner jrpc2.Assigner }
func (s singleton) Assigner() (jrpc2.Assigner, error) { return s.assigner, nil }
func (singleton) Finish(jrpc2.ServerStatus) {}
// 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 }
}
// 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
// reports an error, the loop will terminate and the error will be reported
// once all the servers currently active have returned.
//
// TODO: Add options to support sensible rate-limitation.
func Loop(lst net.Listener, newService func() Service, opts *LoopOptions) error {
newChannel := opts.framing()
serverOpts := opts.serverOpts()
log := func(string, ...interface{}) {}
if serverOpts != nil && serverOpts.Logger != nil {
log = serverOpts.Logger.Printf
}
var wg sync.WaitGroup
for {
conn, err := lst.Accept()
if err != nil {
if channel.IsErrClosing(err) {
err = nil
} else {
log("Error accepting new connection: %v", err)
}
wg.Wait()
return err
}
ch := newChannel(conn, conn)
wg.Add(1)
go func() {
defer wg.Done()
svc := newService()
assigner, err := svc.Assigner()
if err != nil {
log("Service initialization failed: %v", err)
return
}
srv := jrpc2.NewServer(assigner, serverOpts).Start(ch)
stat := srv.WaitStatus()
svc.Finish(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, this function is used to convert a stream connection to an
// RPC channel. If this field is nil, channel.RawJSON is used.
Framing channel.Framing
// 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
}
func (o *LoopOptions) framing() channel.Framing {
if o == nil || o.Framing == nil {
return channel.RawJSON
}
return o.Framing
}