Skip to content

Commit ff6a57b

Browse files
committed
Add web.NewListener()
1 parent e57d1f6 commit ff6a57b

File tree

6 files changed

+121
-28
lines changed

6 files changed

+121
-28
lines changed

.github/workflows/go.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ jobs:
1616
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # [email protected]
1717

1818
- name: Set up Go
19-
uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # pin@v3.3.0
19+
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # pin@v3.4.0
2020
with:
21-
go-version: 1.19.2
21+
go-version: 1.19.4
2222

2323
- name: Build
2424
run: go build -v ./...

server.go

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ import (
1313

1414
// Server describes an web server
1515
type Server struct {
16-
// The socket address that the server will listen to. Must be in the format of "address:port", such as
17-
// "localhost:8080" or "0.0.0.0:8080". Changing this after the server has started has no effect.
16+
// The socket address that the server is listening on. Only populated if the server was created with web.New().
1817
BindAddress string
19-
// The port that this server is listening on.
18+
// The port that this server is listening on. Only populated if the server was created with web.New().
2019
ListenPort uint16
2120
// The API instance that is used to register JSON endpoints.
2221
API API
@@ -51,7 +50,8 @@ type ServerOptions struct {
5150
IgnoreHTTPRangeRequests bool
5251
}
5352

54-
// New create a new server object that will bind to the provided address. Does not start the service automatically.
53+
// New create a new server object that will bind to the provided address. Does not accept incoming connections until
54+
// the server is started.
5555
// Bind address must be in the format of "address:port", such as "localhost:8080" or "0.0.0.0:8080".
5656
func New(bindAddress string) *Server {
5757
httpRouter := router.New()
@@ -76,24 +76,51 @@ func New(bindAddress string) *Server {
7676
return &server
7777
}
7878

79+
// NewListener creates a new server object that will use the given listener. Does not accept incoming connections until
80+
// the server is started.
81+
func NewListener(listener net.Listener) *Server {
82+
httpRouter := router.New()
83+
server := Server{
84+
Options: ServerOptions{
85+
RequestLogLevel: logtic.LevelDebug,
86+
},
87+
router: httpRouter,
88+
listener: listener,
89+
limits: map[string]*rate.Limiter{},
90+
limitLock: &sync.Mutex{},
91+
}
92+
httpRouter.SetNotFoundHandle(server.notFoundHandle)
93+
httpRouter.SetMethodNotAllowedHandle(server.methodNotAllowedHandle)
94+
server.API = API{
95+
server: &server,
96+
}
97+
server.HTTP = HTTP{
98+
server: &server,
99+
}
100+
101+
return &server
102+
}
103+
79104
// Start will start the web server and listen on the socket address. This method blocks.
80105
// If a server is stopped using the Stop() method, this returns no error.
81106
func (s *Server) Start() error {
82-
listener, err := net.Listen("tcp", s.BindAddress)
83-
if err != nil {
84-
log.PError("Error listening on address", map[string]interface{}{
107+
if s.BindAddress != "" {
108+
listener, err := net.Listen("tcp", s.BindAddress)
109+
if err != nil {
110+
log.PError("Error listening on address", map[string]interface{}{
111+
"listen_address": s.BindAddress,
112+
"error": err.Error(),
113+
})
114+
return err
115+
}
116+
s.listener = listener
117+
s.ListenPort = uint16(listener.Addr().(*net.TCPAddr).Port)
118+
log.PInfo("HTTP server listen", map[string]interface{}{
85119
"listen_address": s.BindAddress,
86-
"error": err.Error(),
120+
"listen_port": s.ListenPort,
87121
})
88-
return err
89122
}
90-
s.listener = listener
91-
s.ListenPort = uint16(listener.Addr().(*net.TCPAddr).Port)
92-
log.PInfo("HTTP server listen", map[string]interface{}{
93-
"listen_address": s.BindAddress,
94-
"listen_port": s.ListenPort,
95-
})
96-
if err := s.router.Serve(listener); err != nil {
123+
if err := s.router.Serve(s.listener); err != nil {
97124
if s.shuttingDown {
98125
log.Info("HTTP server stopped")
99126
return nil

server_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,64 @@
11
package web_test
22

33
import (
4+
"context"
45
"fmt"
56
"io"
7+
"net"
68
"net/http"
9+
"path"
710
"strings"
811
"testing"
912
"time"
1013

1114
"github.com/ecnepsnai/web"
1215
)
1316

17+
func TestUnixSocket(t *testing.T) {
18+
t.Parallel()
19+
socketPath := path.Join(t.TempDir(), "TestUnixSocket")
20+
l, err := net.Listen("unix", socketPath)
21+
if err != nil {
22+
t.Fatalf("Error starting unix socket: %s", err.Error())
23+
}
24+
server := web.NewListener(l)
25+
go server.Start()
26+
time.Sleep(5 * time.Millisecond)
27+
28+
handle := func(request web.Request) (interface{}, *web.Error) {
29+
return true, nil
30+
}
31+
options := web.HandleOptions{}
32+
33+
path1 := "path1"
34+
path2 := "path2"
35+
36+
server.API.GET("/"+path1, handle, options)
37+
38+
httpc := http.Client{
39+
Transport: &http.Transport{
40+
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
41+
return net.Dial("unix", socketPath)
42+
},
43+
},
44+
}
45+
46+
check := func(path string, expected int) {
47+
resp, err := httpc.Get(fmt.Sprintf("http://unix/%s", path))
48+
if err != nil {
49+
t.Fatalf("Network error: %s", err.Error())
50+
}
51+
52+
if resp.StatusCode != expected {
53+
body, _ := io.ReadAll(resp.Body)
54+
t.Fatalf("Unexpected status code for %s. Expected %d got %d: %s", path, expected, resp.StatusCode, body)
55+
}
56+
}
57+
58+
check(path1, 200)
59+
check(path2, 404)
60+
}
61+
1462
func TestRestartServer(t *testing.T) {
1563
t.Parallel()
1664
server := newServer()

util.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,10 @@ func getIPFromRemoteAddr(remoteAddr string) string {
3232
// stripPortFromSocketAddr strip the port from a socket address (address:port, return address)
3333
func stripPortFromSocketAddr(socketAddr string) string {
3434
length := len(socketAddr)
35-
if socketAddr[length-6] == ':' {
36-
return socketAddr[0 : length-6]
37-
} else if socketAddr[length-5] == ':' {
38-
return socketAddr[0 : length-5]
39-
} else if socketAddr[length-4] == ':' {
40-
return socketAddr[0 : length-4]
41-
} else if socketAddr[length-3] == ':' {
42-
return socketAddr[0 : length-3]
43-
} else if socketAddr[length-2] == ':' {
44-
return socketAddr[0 : length-2]
35+
for i := length - 1; i >= 0; i-- {
36+
if socketAddr[i] == ':' {
37+
return socketAddr[0:i]
38+
}
4539
}
4640

4741
return socketAddr

util_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ func TestGetIPFromRemoteAddr(t *testing.T) {
1414
if ip := getIPFromRemoteAddr("[1::1]:4233"); ip != "1::1" {
1515
t.Errorf("Incorrect result for IP address. Expected: %s Actual: %s", "1::1", ip)
1616
}
17+
18+
if ip := getIPFromRemoteAddr("@"); ip != "@" {
19+
t.Errorf("Incorrect result for invalid IP address. Expected: %s Actual %s", "@", ip)
20+
}
1721
}
1822

1923
func BenchmarkGetIPFromRemoteAddr(b *testing.B) {

web_example_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package web_test
22

33
import (
4+
"net"
45
"net/http"
56
"os"
67
"time"
@@ -158,3 +159,22 @@ func Example_ratelimit() {
158159
panic(err)
159160
}
160161
}
162+
163+
func Example_unixsocket() {
164+
l, err := net.Listen("unix", "/example.socket")
165+
if err != nil {
166+
panic(err)
167+
}
168+
server := web.NewListener(l)
169+
170+
handle := func(request web.Request) (interface{}, *web.Error) {
171+
return time.Now().Unix(), nil
172+
}
173+
174+
options := web.HandleOptions{}
175+
server.API.GET("/time", handle, options)
176+
177+
if err := server.Start(); err != nil {
178+
panic(err)
179+
}
180+
}

0 commit comments

Comments
 (0)