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