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 }