Skip to content

Commit 5b241ac

Browse files
committed
Proper IPv6 support
- fix net listen error when server.host is an IPv6 - server.Host(), BaseURL() and ProxyBaseURL() properly format IPv6 hosts, which also fixes route.BuildURL() and route.BuildProxyURL() when the host is an IPv6 - server.BaseURL() convert "::" to "::1" (IPv6 loopback)
1 parent d8c14f9 commit 5b241ac

File tree

2 files changed

+98
-7
lines changed

2 files changed

+98
-7
lines changed

server.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"os/signal"
1212
"strconv"
13+
"strings"
1314
"sync/atomic"
1415
"syscall"
1516
"time"
@@ -145,12 +146,12 @@ func New(opts Options) (*Server, error) {
145146
return nil, err
146147
}
147148

149+
host := cfg.GetString("server.host")
148150
port := cfg.GetInt("server.port")
149-
host := cfg.GetString("server.host") + ":" + strconv.Itoa(port)
150151

151152
server := &Server{
152153
server: &http.Server{
153-
Addr: host,
154+
Addr: net.JoinHostPort(host, strconv.Itoa(port)),
154155
WriteTimeout: time.Duration(cfg.GetInt("server.writeTimeout")) * time.Second,
155156
ReadTimeout: time.Duration(cfg.GetInt("server.readTimeout")) * time.Second,
156157
ReadHeaderTimeout: time.Duration(cfg.GetInt("server.readHeaderTimeout")) * time.Second,
@@ -168,7 +169,7 @@ func New(opts Options) (*Server, error) {
168169
stopChannel: make(chan struct{}, 1),
169170
startupHooks: []func(*Server){},
170171
shutdownHooks: []func(*Server){},
171-
host: cfg.GetString("server.host"),
172+
host: host,
172173
port: port,
173174
Logger: slogger,
174175
}
@@ -193,18 +194,29 @@ func (s *Server) internalBaseContext(_ net.Listener) context.Context {
193194
return s.ctx
194195
}
195196

197+
func (s *Server) isIPv6(host string) bool {
198+
return strings.IndexByte(host, ':') >= 0
199+
}
200+
196201
func (s *Server) getAddress(cfg *config.Config) string {
197202
shouldShowPort := s.port != 80
198203
host := cfg.GetString("server.domain")
199204
if len(host) == 0 {
200205
host = cfg.GetString("server.host")
201-
if host == "0.0.0.0" {
206+
switch host {
207+
case "0.0.0.0":
202208
host = "127.0.0.1"
209+
case "::":
210+
host = "::1"
203211
}
204212
}
205213

206214
if shouldShowPort {
207-
host += ":" + strconv.Itoa(s.port)
215+
return "http://" + net.JoinHostPort(host, strconv.Itoa(s.port))
216+
}
217+
218+
if s.isIPv6(host) {
219+
host = "[" + host + "]"
208220
}
209221

210222
return "http://" + host
@@ -225,7 +237,9 @@ func (s *Server) getProxyAddress(cfg *config.Config) string {
225237
}
226238
host := cfg.GetString("server.proxy.host")
227239
if shouldShowPort {
228-
host += ":" + strconv.Itoa(port)
240+
host = net.JoinHostPort(host, strconv.Itoa(s.port))
241+
} else if s.isIPv6(host) {
242+
host = "[" + host + "]"
229243
}
230244

231245
return proto + "://" + host + cfg.GetString("server.proxy.base")
@@ -262,7 +276,7 @@ func (s *Server) RegisterService(service Service) {
262276

263277
// Host returns the hostname and port the server is running on.
264278
func (s *Server) Host() string {
265-
return s.host + ":" + strconv.Itoa(s.port)
279+
return net.JoinHostPort(s.host, strconv.Itoa(s.port))
266280
}
267281

268282
// Port returns the port the server is running on.

server_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ func TestServer(t *testing.T) {
8888
assert.Equal(t, "http://127.0.0.1:8080", s.BaseURL())
8989
assert.Equal(t, "http://127.0.0.1:8080", s.ProxyBaseURL())
9090
assert.NoError(t, s.CloseDB())
91+
92+
t.Run("ipv6_host", func(t *testing.T) {
93+
cfg := config.LoadDefault()
94+
cfg.Set("server.host", "::")
95+
s, err = New(Options{Config: cfg})
96+
require.NoError(t, err)
97+
assert.Equal(t, "[::]:8080", s.server.Addr)
98+
})
9199
})
92100

93101
t.Run("New_invalid_config", func(t *testing.T) {
@@ -153,6 +161,25 @@ func TestServer(t *testing.T) {
153161
assert.Nil(t, server)
154162
})
155163

164+
t.Run("Host", func(t *testing.T) {
165+
t.Run("ipv4", func(t *testing.T) {
166+
server := &Server{host: "0.0.0.0", port: 80}
167+
assert.Equal(t, "0.0.0.0:80", server.Host())
168+
})
169+
t.Run("ipv4_loopback", func(t *testing.T) {
170+
server := &Server{host: "127.0.0.1", port: 80}
171+
assert.Equal(t, "127.0.0.1:80", server.Host())
172+
})
173+
t.Run("ipv6", func(t *testing.T) {
174+
server := &Server{host: "::", port: 80}
175+
assert.Equal(t, "[::]:80", server.Host())
176+
})
177+
t.Run("ipv6_loopback", func(t *testing.T) {
178+
server := &Server{host: "::1", port: 80}
179+
assert.Equal(t, "[::1]:80", server.Host())
180+
})
181+
})
182+
156183
t.Run("getAddress", func(t *testing.T) {
157184
t.Run("0.0.0.0", func(t *testing.T) {
158185
cfg := config.LoadDefault()
@@ -161,6 +188,13 @@ func TestServer(t *testing.T) {
161188
server := &Server{config: cfg, port: 8080}
162189
assert.Equal(t, "http://127.0.0.1:8080", server.getAddress(cfg))
163190
})
191+
t.Run("0.0.0.0_ipv6", func(t *testing.T) {
192+
cfg := config.LoadDefault()
193+
cfg.Set("server.host", "::")
194+
cfg.Set("server.port", 8080)
195+
server := &Server{config: cfg, port: 8080}
196+
assert.Equal(t, "http://[::1]:8080", server.getAddress(cfg))
197+
})
164198
t.Run("hide_port", func(t *testing.T) {
165199
cfg := config.LoadDefault()
166200
cfg.Set("server.port", 80)
@@ -173,6 +207,19 @@ func TestServer(t *testing.T) {
173207
server := &Server{config: cfg, port: 1234}
174208
assert.Equal(t, "http://example.org:1234", server.getAddress(cfg))
175209
})
210+
t.Run("ipv6", func(t *testing.T) {
211+
cfg := config.LoadDefault()
212+
cfg.Set("server.host", "::1")
213+
server := &Server{config: cfg, port: 1234}
214+
assert.Equal(t, "http://[::1]:1234", server.getAddress(cfg))
215+
})
216+
t.Run("ipv6_hide_port", func(t *testing.T) {
217+
cfg := config.LoadDefault()
218+
cfg.Set("server.host", "::1")
219+
cfg.Set("server.port", 80)
220+
server := &Server{config: cfg, port: 80}
221+
assert.Equal(t, "http://[::1]", server.getAddress(cfg))
222+
})
176223
})
177224

178225
t.Run("getProxyAddress", func(t *testing.T) {
@@ -203,6 +250,36 @@ func TestServer(t *testing.T) {
203250
server = &Server{config: cfg, port: 80}
204251
assert.Equal(t, "http://proxy.example.org/base", server.getProxyAddress(cfg))
205252
})
253+
254+
t.Run("full_ipv4", func(t *testing.T) {
255+
cfg := config.LoadDefault()
256+
cfg.Set("server.proxy.host", "192.168.1.11")
257+
cfg.Set("server.proxy.protocol", "http")
258+
cfg.Set("server.proxy.port", 1234)
259+
cfg.Set("server.proxy.base", "/base")
260+
server := &Server{config: cfg, port: 1234}
261+
assert.Equal(t, "http://192.168.1.11:1234/base", server.getProxyAddress(cfg))
262+
})
263+
264+
t.Run("full_ipv6", func(t *testing.T) {
265+
cfg := config.LoadDefault()
266+
cfg.Set("server.proxy.host", "::ffff:c0a8:10b")
267+
cfg.Set("server.proxy.protocol", "http")
268+
cfg.Set("server.proxy.port", 1234)
269+
cfg.Set("server.proxy.base", "/base")
270+
server := &Server{config: cfg, port: 1234}
271+
assert.Equal(t, "http://[::ffff:c0a8:10b]:1234/base", server.getProxyAddress(cfg))
272+
})
273+
274+
t.Run("hide_port_ipv6", func(t *testing.T) {
275+
cfg := config.LoadDefault()
276+
cfg.Set("server.proxy.host", "::ffff:c0a8:10b")
277+
cfg.Set("server.proxy.protocol", "http")
278+
cfg.Set("server.proxy.port", 80)
279+
cfg.Set("server.proxy.base", "/base")
280+
server := &Server{config: cfg, port: 80}
281+
assert.Equal(t, "http://[::ffff:c0a8:10b]/base", server.getProxyAddress(cfg))
282+
})
206283
})
207284

208285
t.Run("Service", func(t *testing.T) {

0 commit comments

Comments
 (0)