Skip to content

Commit

Permalink
server: Change the listener type for the Loop function.
Browse files Browse the repository at this point in the history
Instead of taking a net.Listener, which is hard to implement outside the net
package because of the complexity of the net.Conn it returns, define a local
Accepter interface that returns a Channel directly. Since the Accepter is now
responsible for building the channel, the Framing option is no longer needed.

To make it easier to update existing use, add a NetAccepter function that
adapts a net.Listener and a framing to the new Accepter interface.

This is a breaking API change in the server package.
  • Loading branch information
creachadair committed Oct 7, 2021
1 parent 33541d1 commit 31ce9d5
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 18 deletions.
43 changes: 28 additions & 15 deletions server/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,40 @@ 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 {
// Accept accepts a connection and returns a new channel for it.
Accept() (channel.Channel, error)
}

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

func (n netAccepter) Accept() (channel.Channel, error) {
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.
//
// If the listener reports an error, the loop will terminate and that error
// will be reported to the caller of Loop once any active servers have
// returned.
func Loop(lst net.Listener, newService func() Service, opts *LoopOptions) error {
newChannel := opts.framing()
func Loop(lst Accepter, newService func() Service, opts *LoopOptions) error {
serverOpts := opts.serverOpts()
log := func(string, ...interface{}) {}
if serverOpts != nil && serverOpts.Logger != nil {
Expand All @@ -51,7 +76,7 @@ func Loop(lst net.Listener, newService func() Service, opts *LoopOptions) error

var wg sync.WaitGroup
for {
conn, err := lst.Accept()
ch, err := lst.Accept()
if err != nil {
if channel.IsErrClosing(err) {
err = nil
Expand All @@ -61,7 +86,6 @@ func Loop(lst net.Listener, newService func() Service, opts *LoopOptions) error
wg.Wait()
return err
}
ch := newChannel(conn, conn)
wg.Add(1)
go func() {
defer wg.Done()
Expand All @@ -84,10 +108,6 @@ func Loop(lst net.Listener, newService func() Service, opts *LoopOptions) error
// 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
Expand All @@ -99,10 +119,3 @@ func (o *LoopOptions) serverOpts() *jrpc2.ServerOptions {
}
return o.ServerOptions
}

func (o *LoopOptions) framing() channel.Framing {
if o == nil || o.Framing == nil {
return channel.RawJSON
}
return o.Framing
}
5 changes: 2 additions & 3 deletions server/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,8 @@ func mustServe(t *testing.T, lst net.Listener, newService func() Service) <-chan
defer close(sc)
// Start a server loop to accept connections from the clients. This should
// exit cleanly once all the clients have finished and the listener closes.
if err := Loop(lst, newService, &LoopOptions{
Framing: newChan,
}); err != nil {
lst := NetAccepter(lst, newChan)
if err := Loop(lst, newService, nil); err != nil {
t.Errorf("Loop: unexpected failure: %v", err)
}
}()
Expand Down

0 comments on commit 31ce9d5

Please sign in to comment.