Skip to content

Commit 25be2ce

Browse files
authored
Merge pull request #67 from systemli/refactor/extract-lookup-server
♻️ Extract generic TCP server and rename to LookupServer
2 parents 384c152 + c085d41 commit 25be2ce

File tree

9 files changed

+746
-882
lines changed

9 files changed

+746
-882
lines changed

adapter_test.go

Lines changed: 0 additions & 420 deletions
This file was deleted.

adapter.go renamed to lookup.go

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"net"
88
"strings"
9+
"sync"
910
"time"
1011

1112
"github.com/markdingo/netstring"
@@ -27,29 +28,49 @@ func (r *SocketmapResponse) String() string {
2728
return fmt.Sprintf("%s %s", r.Status, r.Data)
2829
}
2930

30-
// SocketmapAdapter handles socketmap protocol requests
31-
type SocketmapAdapter struct {
31+
// LookupServer handles Postfix socketmap protocol requests for lookups.
32+
// It implements the ConnectionHandler interface.
33+
type LookupServer struct {
3234
client UserliService
3335
}
3436

35-
// NewSocketmapAdapter creates a new SocketmapAdapter with the given UserliService
36-
func NewSocketmapAdapter(client UserliService) *SocketmapAdapter {
37-
return &SocketmapAdapter{client: client}
37+
// NewLookupServer creates a new LookupServer with the given UserliService
38+
func NewLookupServer(client UserliService) *LookupServer {
39+
return &LookupServer{client: client}
3840
}
3941

40-
// HandleConnection processes a single socketmap connection
41-
// Supports persistent connections with multiple requests
42-
func (s *SocketmapAdapter) HandleConnection(conn net.Conn) {
43-
defer func() {
44-
if err := conn.Close(); err != nil {
45-
logger.Error("Error closing connection", zap.Error(err))
46-
}
47-
}()
42+
// StartLookupServer starts the lookup server on the given address
43+
func StartLookupServer(ctx context.Context, wg *sync.WaitGroup, addr string, server *LookupServer) {
44+
config := TCPServerConfig{
45+
Name: "socketmap",
46+
Addr: addr,
47+
OnConnectionAcquired: func() {
48+
activeConnections.Inc()
49+
},
50+
OnConnectionReleased: func() {
51+
activeConnections.Dec()
52+
},
53+
OnPoolUsageChanged: func(size int) {
54+
connectionPoolUsage.Set(float64(size))
55+
},
56+
}
57+
58+
StartTCPServer(ctx, wg, config, server)
59+
}
4860

61+
// HandleConnection implements ConnectionHandler interface for LookupServer.
62+
// It processes socketmap protocol requests, supporting persistent connections with multiple requests.
63+
// Note: The caller (tcpserver.go) is responsible for closing the connection.
64+
func (s *LookupServer) HandleConnection(ctx context.Context, conn net.Conn) {
4965
decoder := netstring.NewDecoder(conn)
5066
encoder := netstring.NewEncoder(conn)
5167

5268
for {
69+
// Check if context is cancelled
70+
if ctx.Err() != nil {
71+
return
72+
}
73+
5374
// Set read deadline for each request
5475
_ = conn.SetReadDeadline(time.Now().Add(ReadTimeout))
5576

@@ -80,34 +101,38 @@ func (s *SocketmapAdapter) HandleConnection(conn net.Conn) {
80101
mapName := parts[0]
81102
key := parts[1]
82103

83-
logger.Debug("Processing socketmap request", zap.String("map", mapName), zap.String("key", key))
84-
85-
// Create context with timeout for this request
86-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
87-
88-
// Route to appropriate handler based on map name
89-
var response *SocketmapResponse
90-
switch mapName {
91-
case "alias":
92-
response = s.handleAlias(ctx, key)
93-
case "domain":
94-
response = s.handleDomain(ctx, key)
95-
case "mailbox":
96-
response = s.handleMailbox(ctx, key)
97-
case "senders":
98-
response = s.handleSenders(ctx, key)
99-
default:
100-
logger.Error("Unknown map name", zap.String("map", mapName))
101-
response = &SocketmapResponse{Status: "PERM", Data: "Unknown map name"}
102-
}
104+
logger.Debug("Processing socketmap request",
105+
zap.String("map", mapName),
106+
zap.String("key", key))
103107

104-
cancel() // Always cancel context when done
108+
response := s.processRequest(ctx, mapName, key)
105109
s.writeResponse(encoder, conn, response, now, mapName)
106110
}
107111
}
108112

113+
// processRequest routes a socketmap request to the appropriate handler with a timeout context.
114+
// Using a separate method ensures defer cancel() runs after each request, preventing context leaks.
115+
func (s *LookupServer) processRequest(ctx context.Context, mapName, key string) *SocketmapResponse {
116+
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
117+
defer cancel()
118+
119+
switch mapName {
120+
case "alias":
121+
return s.handleAlias(reqCtx, key)
122+
case "domain":
123+
return s.handleDomain(reqCtx, key)
124+
case "mailbox":
125+
return s.handleMailbox(reqCtx, key)
126+
case "senders":
127+
return s.handleSenders(reqCtx, key)
128+
default:
129+
logger.Error("Unknown map name", zap.String("map", mapName))
130+
return &SocketmapResponse{Status: "PERM", Data: "Unknown map name"}
131+
}
132+
}
133+
109134
// handleAlias processes alias lookup requests
110-
func (s *SocketmapAdapter) handleAlias(ctx context.Context, key string) *SocketmapResponse {
135+
func (s *LookupServer) handleAlias(ctx context.Context, key string) *SocketmapResponse {
111136
aliases, err := s.client.GetAliases(ctx, key)
112137
if err != nil {
113138
logger.Error("Error fetching aliases", zap.String("key", key), zap.Error(err))
@@ -122,7 +147,7 @@ func (s *SocketmapAdapter) handleAlias(ctx context.Context, key string) *Socketm
122147
}
123148

124149
// handleDomain processes domain lookup requests
125-
func (s *SocketmapAdapter) handleDomain(ctx context.Context, key string) *SocketmapResponse {
150+
func (s *LookupServer) handleDomain(ctx context.Context, key string) *SocketmapResponse {
126151
exists, err := s.client.GetDomain(ctx, key)
127152
if err != nil {
128153
logger.Error("Error fetching domain", zap.String("key", key), zap.Error(err))
@@ -137,7 +162,7 @@ func (s *SocketmapAdapter) handleDomain(ctx context.Context, key string) *Socket
137162
}
138163

139164
// handleMailbox processes mailbox lookup requests
140-
func (s *SocketmapAdapter) handleMailbox(ctx context.Context, key string) *SocketmapResponse {
165+
func (s *LookupServer) handleMailbox(ctx context.Context, key string) *SocketmapResponse {
141166
exists, err := s.client.GetMailbox(ctx, key)
142167
if err != nil {
143168
logger.Error("Error fetching mailbox", zap.String("key", key), zap.Error(err))
@@ -152,7 +177,7 @@ func (s *SocketmapAdapter) handleMailbox(ctx context.Context, key string) *Socke
152177
}
153178

154179
// handleSenders processes senders lookup requests
155-
func (s *SocketmapAdapter) handleSenders(ctx context.Context, key string) *SocketmapResponse {
180+
func (s *LookupServer) handleSenders(ctx context.Context, key string) *SocketmapResponse {
156181
senders, err := s.client.GetSenders(ctx, key)
157182
if err != nil {
158183
logger.Error("Error fetching senders", zap.String("key", key), zap.Error(err))
@@ -167,7 +192,7 @@ func (s *SocketmapAdapter) handleSenders(ctx context.Context, key string) *Socke
167192
}
168193

169194
// writeResponse sends a socketmap response back to the client
170-
func (s *SocketmapAdapter) writeResponse(encoder *netstring.Encoder, conn net.Conn, response *SocketmapResponse, startTime time.Time, mapName string) {
195+
func (s *LookupServer) writeResponse(encoder *netstring.Encoder, conn net.Conn, response *SocketmapResponse, startTime time.Time, mapName string) {
171196
var status string
172197
switch response.Status {
173198
case "OK":

0 commit comments

Comments
 (0)