github.com/jd-ly/tools@v0.5.7/internal/jsonrpc2/serve.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package jsonrpc2
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"os"
    13  	"time"
    14  
    15  	"github.com/jd-ly/tools/internal/event"
    16  	errors "golang.org/x/xerrors"
    17  )
    18  
    19  // NOTE: This file provides an experimental API for serving multiple remote
    20  // jsonrpc2 clients over the network. For now, it is intentionally similar to
    21  // net/http, but that may change in the future as we figure out the correct
    22  // semantics.
    23  
    24  // A StreamServer is used to serve incoming jsonrpc2 clients communicating over
    25  // a newly created connection.
    26  type StreamServer interface {
    27  	ServeStream(context.Context, Conn) error
    28  }
    29  
    30  // The ServerFunc type is an adapter that implements the StreamServer interface
    31  // using an ordinary function.
    32  type ServerFunc func(context.Context, Conn) error
    33  
    34  // ServeStream calls f(ctx, s).
    35  func (f ServerFunc) ServeStream(ctx context.Context, c Conn) error {
    36  	return f(ctx, c)
    37  }
    38  
    39  // HandlerServer returns a StreamServer that handles incoming streams using the
    40  // provided handler.
    41  func HandlerServer(h Handler) StreamServer {
    42  	return ServerFunc(func(ctx context.Context, conn Conn) error {
    43  		conn.Go(ctx, h)
    44  		<-conn.Done()
    45  		return conn.Err()
    46  	})
    47  }
    48  
    49  // ListenAndServe starts an jsonrpc2 server on the given address.  If
    50  // idleTimeout is non-zero, ListenAndServe exits after there are no clients for
    51  // this duration, otherwise it exits only on error.
    52  func ListenAndServe(ctx context.Context, network, addr string, server StreamServer, idleTimeout time.Duration) error {
    53  	ln, err := net.Listen(network, addr)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	defer ln.Close()
    58  	if network == "unix" {
    59  		defer os.Remove(addr)
    60  	}
    61  	return Serve(ctx, ln, server, idleTimeout)
    62  }
    63  
    64  // Serve accepts incoming connections from the network, and handles them using
    65  // the provided server. If idleTimeout is non-zero, ListenAndServe exits after
    66  // there are no clients for this duration, otherwise it exits only on error.
    67  func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error {
    68  	ctx, cancel := context.WithCancel(ctx)
    69  	defer cancel()
    70  	// Max duration: ~290 years; surely that's long enough.
    71  	const forever = 1<<63 - 1
    72  	if idleTimeout <= 0 {
    73  		idleTimeout = forever
    74  	}
    75  	connTimer := time.NewTimer(idleTimeout)
    76  
    77  	newConns := make(chan net.Conn)
    78  	doneListening := make(chan error)
    79  	closedConns := make(chan error)
    80  
    81  	go func() {
    82  		for {
    83  			nc, err := ln.Accept()
    84  			if err != nil {
    85  				select {
    86  				case doneListening <- fmt.Errorf("Accept(): %w", err):
    87  				case <-ctx.Done():
    88  				}
    89  				return
    90  			}
    91  			newConns <- nc
    92  		}
    93  	}()
    94  
    95  	activeConns := 0
    96  	for {
    97  		select {
    98  		case netConn := <-newConns:
    99  			activeConns++
   100  			connTimer.Stop()
   101  			stream := NewHeaderStream(netConn)
   102  			go func() {
   103  				conn := NewConn(stream)
   104  				closedConns <- server.ServeStream(ctx, conn)
   105  				stream.Close()
   106  			}()
   107  		case err := <-doneListening:
   108  			return err
   109  		case err := <-closedConns:
   110  			if !isClosingError(err) {
   111  				event.Error(ctx, "closed a connection", err)
   112  			}
   113  			activeConns--
   114  			if activeConns == 0 {
   115  				connTimer.Reset(idleTimeout)
   116  			}
   117  		case <-connTimer.C:
   118  			return ErrIdleTimeout
   119  		case <-ctx.Done():
   120  			return ctx.Err()
   121  		}
   122  	}
   123  }
   124  
   125  // isClosingError reports if the error occurs normally during the process of
   126  // closing a network connection. It uses imperfect heuristics that err on the
   127  // side of false negatives, and should not be used for anything critical.
   128  func isClosingError(err error) bool {
   129  	if errors.Is(err, io.EOF) {
   130  		return true
   131  	}
   132  	// Per https://github.com/golang/go/issues/4373, this error string should not
   133  	// change. This is not ideal, but since the worst that could happen here is
   134  	// some superfluous logging, it is acceptable.
   135  	if err.Error() == "use of closed network connection" {
   136  		return true
   137  	}
   138  	return false
   139  }