• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wrouesnel / multihttp / 24834061649

23 Apr 2026 12:01PM UTC coverage: 79.231% (-3.8%) from 83.065%
24834061649

push

github

wrouesnel
Make ListenerError a proper error type

1 of 6 new or added lines in 1 file covered. (16.67%)

1 existing line in 1 file now uncovered.

103 of 130 relevant lines covered (79.23%)

2.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.23
/multihttp.go
1
package multihttp //nolint:typecheck
2

3
import (
4
        "crypto/tls"
5
        "fmt"
6
        "net"
7
        "net/http"
8
        "net/url"
9
        "os"
10
        "time"
11

12
        //"fmt".
13
        "crypto/x509"
14
        "errors"
15
        "io/ioutil"
16

17
        "github.com/hashicorp/errwrap"
18
)
19

20
const (
21
        certParam   string = "tlscert"
22
        keyParam    string = "tlskey"
23
        caCertParam string = "tlsclientca"
24

25
        networkUnix string = "unix"
26
        networkTCP  string = "tcp"
27
)
28

29
// nolint: golint
30
var (
31
        ErrErrorLoadingCertificate         = errors.New("could not load TLS certificate")
32
        ErrErrorMissingTLSParameters       = errors.New("TLS modes require tlscert and tlskey params")
33
        ErrErrorLoadingClientCACertificate = errors.New("error loading client CA certificate")
34
        ErrUnknownListenScheme             = errors.New("unknown listen scheme")
35
)
36

37
// ListenAddressConfig is the parsed form of a multihttp address.
38
type ListenAddressConfig struct {
39
        // NetworkType is the type of socket connection
40
        NetworkType string
41
        // Address is either the IP or socket path.
42
        Address string
43
        // TLS config is the TLS parameters passed as part of the URL.
44
        TLSConfig *tls.Config
45
}
46

47
// ListenerError maps a listener to it's error channel.
48
type ListenerError struct {
49
        Listener net.Listener
50
        Err      error
51
}
52

NEW
53
func (e ListenerError) Unwrap() error {
×
NEW
54
        return e.Err
×
NEW
55
}
×
56

NEW
57
func (e ListenerError) Error() string {
×
NEW
58
        return fmt.Sprintf("listener %s: %s", e.Listener.Addr().String(), e.Err.Error())
×
UNCOV
59
}
×
60

61
func getNetworkTypeAndAddressFromURL(u *url.URL) (string, string) {
6✔
62
        var networkType, address string
6✔
63

6✔
64
        switch u.Scheme {
6✔
65
        case networkTCP:
4✔
66
                networkType = u.Scheme
4✔
67
                address = u.Host
4✔
68
        case networkUnix:
2✔
69
                networkType = networkUnix
2✔
70
                address = u.Path
2✔
71
        }
72

73
        return networkType, address
6✔
74
}
75

76
// ParseAddress parses the given address string into an expanded configuration
77
// struct. It is normally used by the Listen function.
78
func ParseAddress(address string) (ListenAddressConfig, error) {
7✔
79
        retAddr := ListenAddressConfig{}
7✔
80

7✔
81
        urlp, err := url.Parse(address)
7✔
82
        if err != nil {
7✔
83
                return retAddr, err
×
84
        }
×
85

86
        switch urlp.Scheme {
7✔
87
        case networkTCP, networkUnix: // tcp
4✔
88
                retAddr.NetworkType, retAddr.Address = getNetworkTypeAndAddressFromURL(urlp)
4✔
89
        case "tcps", "unixs": // tcp with tls
2✔
90
                urlp.Scheme = urlp.Scheme[:len(urlp.Scheme)-1]
2✔
91
                retAddr.NetworkType, retAddr.Address = getNetworkTypeAndAddressFromURL(urlp)
2✔
92

2✔
93
                tlsConfig := new(tls.Config)
2✔
94
                tlsConfig.NextProtos = []string{"http/1.1"}
2✔
95

2✔
96
                queryParams := urlp.Query()
2✔
97
                if queryParams == nil {
2✔
98
                        return retAddr, ErrErrorMissingTLSParameters
×
99
                }
×
100

101
                // Get certificate and key path.
102
                certPath := queryParams.Get(certParam)
2✔
103
                keyPath := queryParams.Get(keyParam)
2✔
104

2✔
105
                tlsConfig.Certificates = make([]tls.Certificate, 1)
2✔
106
                tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(certPath, keyPath)
2✔
107
                if err != nil {
2✔
108
                        return retAddr, errwrap.Wrap(ErrErrorLoadingCertificate, err)
×
109
                }
×
110

111
                // Optional: client verification path
112
                if caCertPath := queryParams.Get(caCertParam); caCertPath != "" {
4✔
113
                        tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
2✔
114

2✔
115
                        // Require acceptable clientCAs to be explicitly specified.
2✔
116
                        caCerts, caerr := ioutil.ReadFile(caCertPath)
2✔
117
                        if caerr != nil {
2✔
118
                                return retAddr, errwrap.Wrap(ErrErrorLoadingClientCACertificate, caerr)
×
119
                        }
×
120

121
                        caCertPool := x509.NewCertPool()
2✔
122
                        caCertPool.AppendCertsFromPEM(caCerts)
2✔
123

2✔
124
                        tlsConfig.ClientCAs = caCertPool
2✔
125
                }
126
                retAddr.TLSConfig = tlsConfig
2✔
127
        default:
1✔
128
                return retAddr, ErrUnknownListenScheme
1✔
129
        }
130

131
        return retAddr, nil
6✔
132
}
133

134
// CloseAndCleanUpListeners runs clean up on a list of listeners,
135
// namely deleting any Unix socket files
136
// nolint: errcheck,gas
137
func CloseAndCleanUpListeners(listeners []net.Listener) {
3✔
138
        for _, listener := range listeners {
6✔
139
                listener.Close()
3✔
140
                addr := listener.Addr()
3✔
141
                switch addr.(type) {
3✔
142
                case *net.UnixAddr:
1✔
143
                        os.Remove(addr.String())
1✔
144
                }
145
        }
146
}
147

148
// Listen is a non-blocking function to listen on multiple sockets. Returns
149
// a list of the created listener interfaces. Even in the case of errors,
150
// successfully listening interfaces are returned to allow for clean up.
151
func Listen(addresses []string, handler http.Handler) ([]net.Listener, <-chan *ListenerError, error) {
3✔
152
        return ListenFunc(addresses, func(listener net.Listener) error {
6✔
153
                return http.Serve(listener, handler)
3✔
154
        })
3✔
155
}
156

157
// ListenFunc is a non-blocking function to listen on multiple http sockets. Returns
158
// a list of the created listener interfaces. Even in the case of errors,
159
// successfully listening interfaces are returned to allow for clean up.
160
func ListenFunc(addresses []string, listenFunc func(listener net.Listener) error) ([]net.Listener, <-chan *ListenerError, error) {
3✔
161
        var listeners []net.Listener
3✔
162

3✔
163
        // Master error channel - all errors are propagated here. Length is set to
3✔
164
        // listener length so go routines will clean up even if the channel is
3✔
165
        // ignored.
3✔
166
        errCh := make(chan *ListenerError, len(addresses))
3✔
167

3✔
168
        for _, address := range addresses {
6✔
169
                addressConfig, aerr := ParseAddress(address)
3✔
170
                if aerr != nil {
3✔
171
                        return listeners, errCh, aerr
×
172
                }
×
173

174
                var listener net.Listener
3✔
175
                var lerr error
3✔
176

3✔
177
                listener, lerr = net.Listen(addressConfig.NetworkType, addressConfig.Address)
3✔
178
                // Errored making listener?
3✔
179
                if lerr != nil {
3✔
180
                        return listeners, errCh, lerr
×
181
                }
×
182

183
                // TLS connection?
184
                if addressConfig.TLSConfig != nil {
4✔
185
                        listener = tls.NewListener(listener, addressConfig.TLSConfig)
1✔
186
                }
1✔
187

188
                // Append and start serving on listener
189
                listener = maybeKeepAlive(listener)
3✔
190
                listeners = append(listeners, listener)
3✔
191

3✔
192
                // Allow specifying a nil handler if the user just wants the listeners.
3✔
193
                if listenFunc != nil {
6✔
194
                        go func(listener net.Listener) {
6✔
195
                                err := listenFunc(listener)
3✔
196
                                // Return the listener and the error it returned.
3✔
197
                                errCh <- &ListenerError{
3✔
198
                                        Listener: listener,
3✔
199
                                        Err:      err,
3✔
200
                                }
3✔
201
                        }(listener)
3✔
202
                }
203
        }
204

205
        return listeners, errCh, nil
3✔
206
}
207

208
// Checks if a listener is a TCP and needs a keepalive handler.
209
func maybeKeepAlive(ln net.Listener) net.Listener {
3✔
210
        if o, ok := ln.(*net.TCPListener); ok {
4✔
211
                return &tcpKeepAliveListener{o}
1✔
212
        }
1✔
213
        return ln
2✔
214
}
215

216
// Irritatingly the tcpKeepAliveListener is not public, so we need to recreate it.
217
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted connections.
218
type tcpKeepAliveListener struct {
219
        *net.TCPListener
220
}
221

222
func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
1✔
223
        tc, err := ln.AcceptTCP()
1✔
224
        if err != nil {
2✔
225
                return nil, err
1✔
226
        }
1✔
227
        err = tc.SetKeepAlive(true)
×
228
        if err != nil {
×
229
                return nil, err
×
230
        }
×
231
        err = tc.SetKeepAlivePeriod(3 * time.Minute)
×
232
        if err != nil {
×
233
                return nil, err
×
234
        }
×
235
        return tc, nil
×
236
}
237

238
// Returns a dialer which ignores the address string and connects to the
239
// given socket always.
240
//func newDialer(addr string) (func (proto, addr string) (conn net.Conn, err error), error) {
241
//        realProtocol, realAddress, err := ParseAddress(addr)
242
//        if err != nil {
243
//                return nil, err
244
//        }
245
//
246
//        return func (proto, addr string) (conn net.Conn, err error) {
247
//                return net.Dial(realProtocol, realAddress)
248
//        }, nil
249
//}
250
//
251
//// Initialize an HTTP client which connects to the provided socket address to
252
//// service requests. The hostname in requests is parsed as a header only.
253
//func NewClient(addr string) (*http.Client, error) {
254
//        dialer, err := newDialer(addr)
255
//        if err != nil {
256
//                return nil, err
257
//        }
258
//
259
//        tr := &http.Transport{ Dial: dialer, }
260
//        client := &http.Client{Transport: tr}
261
//
262
//        return client, nil
263
//}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc