104 lines
2.9 KiB
Go
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
|
||
|
}
|