forked from connectrpc/connect-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
example_init_test.go
144 lines (126 loc) · 4 KB
/
example_init_test.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
140
141
142
143
144
// Copyright 2021-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package connect_test
import (
"context"
"errors"
"net"
"net/http"
"net/http/httptest"
"sync"
"github.com/bufbuild/connect-go/internal/gen/connect/ping/v1/pingv1connect"
)
var examplePingServer *inMemoryServer
func init() {
// Generally, init functions are bad.
//
// To write testable examples that users can grok *and* can execute in the
// playground, where networking is disabled, we need an HTTP server that uses
// in-memory pipes instead of TCP. We don't want to pollute every example
// with this setup code.
//
// The least-awful option is to set up the server in init().
mux := http.NewServeMux()
mux.Handle(pingv1connect.NewPingServiceHandler(pingServer{}))
examplePingServer = newInMemoryServer(mux)
}
// inMemoryServer is an HTTP server that uses in-memory pipes instead of TCP.
// It supports HTTP/2 and has TLS enabled.
//
// The Go Playground panics if we try to start a TCP-backed server. If you're
// not familiar with the Playground's behavior, it looks like our examples are
// broken. This server lets us write examples that work in the playground
// without abstracting over HTTP.
type inMemoryServer struct {
server *httptest.Server
listener *memoryListener
}
// newInMemoryServer constructs and starts an inMemoryServer.
func newInMemoryServer(handler http.Handler) *inMemoryServer {
lis := &memoryListener{
conns: make(chan net.Conn),
closed: make(chan struct{}),
}
server := httptest.NewUnstartedServer(handler)
server.Listener = lis
server.EnableHTTP2 = true
server.StartTLS()
return &inMemoryServer{
server: server,
listener: lis,
}
}
// Client returns an HTTP client configured to trust the server's TLS
// certificate and use HTTP/2 over an in-memory pipe. Automatic HTTP-level gzip
// compression is disabled. It closes its idle connections when the server is
// closed.
func (s *inMemoryServer) Client() *http.Client {
client := s.server.Client()
if transport, ok := client.Transport.(*http.Transport); ok {
transport.DialContext = s.listener.DialContext
transport.DisableCompression = true
}
return client
}
// URL is the server's URL.
func (s *inMemoryServer) URL() string {
return s.server.URL
}
// Close shuts down the server, blocking until all outstanding requests have
// completed.
func (s *inMemoryServer) Close() {
s.server.Close()
}
type memoryListener struct {
conns chan net.Conn
once sync.Once
closed chan struct{}
}
// Accept implements net.Listener.
func (l *memoryListener) Accept() (net.Conn, error) {
select {
case conn := <-l.conns:
return conn, nil
case <-l.closed:
return nil, errors.New("listener closed")
}
}
// Close implements net.Listener.
func (l *memoryListener) Close() error {
l.once.Do(func() {
close(l.closed)
})
return nil
}
// Addr implements net.Listener.
func (l *memoryListener) Addr() net.Addr {
return &memoryAddr{}
}
// DialContext is the type expected by http.Transport.DialContext.
func (l *memoryListener) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
select {
case <-l.closed:
return nil, errors.New("listener closed")
default:
}
server, client := net.Pipe()
l.conns <- server
return client, nil
}
type memoryAddr struct{}
// Network implements net.Addr.
func (*memoryAddr) Network() string { return "memory" }
// String implements io.Stringer, returning a value that matches the
// certificates used by net/http/httptest.
func (*memoryAddr) String() string { return "example.com" }