added full URL for listening server
This commit is contained in:
@ -5,7 +5,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"supervisor/internal/config"
|
||||
@ -56,7 +58,7 @@ func New(cfg config.Config) (*App, error) {
|
||||
func (a *App) Run(ctx context.Context) error {
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
a.logger.Printf("HTTP server listening on %s", a.cfg.Addr)
|
||||
a.logServerURLs()
|
||||
errCh <- a.httpServer.ListenAndServe()
|
||||
}()
|
||||
|
||||
@ -73,3 +75,134 @@ func (a *App) Run(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) logServerURLs() {
|
||||
urls, err := serverURLs(a.cfg.Addr)
|
||||
if err != nil {
|
||||
a.logger.Printf("warning: determine local server URLs: %v", err)
|
||||
}
|
||||
if len(urls) == 0 {
|
||||
a.logger.Printf("HTTP server listening on %s", a.cfg.Addr)
|
||||
return
|
||||
}
|
||||
|
||||
a.logger.Print("HTTP server available at:")
|
||||
for _, url := range urls {
|
||||
a.logger.Printf(" %s", url)
|
||||
}
|
||||
}
|
||||
|
||||
func serverURLs(addr string) ([]string, error) {
|
||||
return serverURLsWithLocalIPv4Addrs(addr, discoverLocalIPv4Addrs)
|
||||
}
|
||||
|
||||
func serverURLsWithLocalIPv4Addrs(addr string, localIPv4Addrs func() ([]string, error)) ([]string, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urls := make([]string, 0, 4)
|
||||
seen := make(map[string]struct{})
|
||||
addURL := func(host string) {
|
||||
if host == "" {
|
||||
return
|
||||
}
|
||||
url := "http://" + net.JoinHostPort(host, port)
|
||||
if _, ok := seen[url]; ok {
|
||||
return
|
||||
}
|
||||
seen[url] = struct{}{}
|
||||
urls = append(urls, url)
|
||||
}
|
||||
|
||||
if isWildcardHost(host) {
|
||||
addURL("localhost")
|
||||
addURL("127.0.0.1")
|
||||
|
||||
addrs, err := localIPv4Addrs()
|
||||
for _, addr := range addrs {
|
||||
ip := net.ParseIP(addr)
|
||||
if !isUsableLocalIPv4(ip) {
|
||||
continue
|
||||
}
|
||||
addURL(ip.String())
|
||||
}
|
||||
return urls, err
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
addURL(host)
|
||||
return urls, nil
|
||||
}
|
||||
if ip4 := ip.To4(); ip4 != nil && !ip4.IsUnspecified() {
|
||||
addURL(ip4.String())
|
||||
}
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
func isWildcardHost(host string) bool {
|
||||
return host == "" || host == "0.0.0.0" || host == "::"
|
||||
}
|
||||
|
||||
func discoverLocalIPv4Addrs() ([]string, error) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
seen := make(map[string]struct{})
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ifaceAddrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return addrs, err
|
||||
}
|
||||
for _, ifaceAddr := range ifaceAddrs {
|
||||
ip := ipFromAddr(ifaceAddr)
|
||||
if !isUsableLocalIPv4(ip) {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := ip.To4().String()
|
||||
if _, ok := seen[addr]; ok {
|
||||
continue
|
||||
}
|
||||
seen[addr] = struct{}{}
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(addrs)
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func ipFromAddr(addr net.Addr) net.IP {
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
return v.IP
|
||||
case *net.IPAddr:
|
||||
return v.IP
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func isUsableLocalIPv4(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
ip4 := ip.To4()
|
||||
if ip4 == nil {
|
||||
return false
|
||||
}
|
||||
return !ip4.IsUnspecified() &&
|
||||
!ip4.IsLoopback() &&
|
||||
!ip4.IsMulticast() &&
|
||||
!ip4.IsLinkLocalUnicast()
|
||||
}
|
||||
|
||||
122
internal/app/app_test.go
Normal file
122
internal/app/app_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServerURLsForWildcardAddr(t *testing.T) {
|
||||
urls, err := serverURLsWithLocalIPv4Addrs(":8080", func() ([]string, error) {
|
||||
return []string{
|
||||
"192.168.1.100",
|
||||
"127.0.0.1",
|
||||
"192.168.1.100",
|
||||
"10.0.0.2",
|
||||
}, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("serverURLsWithLocalIPv4Addrs returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []string{
|
||||
"http://localhost:8080",
|
||||
"http://127.0.0.1:8080",
|
||||
"http://192.168.1.100:8080",
|
||||
"http://10.0.0.2:8080",
|
||||
}
|
||||
if !reflect.DeepEqual(urls, want) {
|
||||
t.Fatalf("urls = %#v, want %#v", urls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerURLsForZeroIPv4Addr(t *testing.T) {
|
||||
urls, err := serverURLsWithLocalIPv4Addrs("0.0.0.0:8080", func() ([]string, error) {
|
||||
return []string{"192.168.1.100"}, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("serverURLsWithLocalIPv4Addrs returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []string{
|
||||
"http://localhost:8080",
|
||||
"http://127.0.0.1:8080",
|
||||
"http://192.168.1.100:8080",
|
||||
}
|
||||
if !reflect.DeepEqual(urls, want) {
|
||||
t.Fatalf("urls = %#v, want %#v", urls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerURLsForSpecificIPv4Addr(t *testing.T) {
|
||||
urls, err := serverURLsWithLocalIPv4Addrs("192.168.1.100:8080", func() ([]string, error) {
|
||||
t.Fatal("local IPv4 addrs should not be discovered for a specific bind address")
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("serverURLsWithLocalIPv4Addrs returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"http://192.168.1.100:8080"}
|
||||
if !reflect.DeepEqual(urls, want) {
|
||||
t.Fatalf("urls = %#v, want %#v", urls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerURLsForLocalhostAddr(t *testing.T) {
|
||||
urls, err := serverURLsWithLocalIPv4Addrs("localhost:8080", func() ([]string, error) {
|
||||
t.Fatal("local IPv4 addrs should not be discovered for a hostname bind address")
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("serverURLsWithLocalIPv4Addrs returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"http://localhost:8080"}
|
||||
if !reflect.DeepEqual(urls, want) {
|
||||
t.Fatalf("urls = %#v, want %#v", urls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerURLsReturnsFallbackURLsWhenDiscoveryFails(t *testing.T) {
|
||||
wantErr := errors.New("interfaces unavailable")
|
||||
urls, err := serverURLsWithLocalIPv4Addrs(":8080", func() ([]string, error) {
|
||||
return nil, wantErr
|
||||
})
|
||||
if !errors.Is(err, wantErr) {
|
||||
t.Fatalf("serverURLsWithLocalIPv4Addrs error = %v, want %v", err, wantErr)
|
||||
}
|
||||
|
||||
want := []string{
|
||||
"http://localhost:8080",
|
||||
"http://127.0.0.1:8080",
|
||||
}
|
||||
if !reflect.DeepEqual(urls, want) {
|
||||
t.Fatalf("urls = %#v, want %#v", urls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUsableLocalIPv4(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip net.IP
|
||||
want bool
|
||||
}{
|
||||
{name: "private", ip: net.ParseIP("192.168.1.100"), want: true},
|
||||
{name: "zero", ip: net.ParseIP("0.0.0.0"), want: false},
|
||||
{name: "loopback", ip: net.ParseIP("127.0.0.1"), want: false},
|
||||
{name: "ipv6", ip: net.ParseIP("2001:db8::1"), want: false},
|
||||
{name: "multicast", ip: net.ParseIP("224.0.0.1"), want: false},
|
||||
{name: "link local", ip: net.ParseIP("169.254.1.2"), want: false},
|
||||
{name: "nil", ip: nil, want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isUsableLocalIPv4(tt.ip); got != tt.want {
|
||||
t.Fatalf("isUsableLocalIPv4(%v) = %v, want %v", tt.ip, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user