Repository navigation
Expand file tree
/
Copy pathserver.go
More file actions
executable file
·272 lines (252 loc) · 7.96 KB
/
Copy pathserver.go
File metadata and controls
executable file
·272 lines (252 loc) · 7.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// server.go
package main
import (
"bufio"
"context"
"fmt"
"io"
"log"
"net"
"strings"
"time"
)
type TCPServer struct {
listenAddr string
engine *Engine
keepAlive time.Duration
}
func NewTCPServer(listenAddr string, engine *Engine, keepAliveSeconds int) *TCPServer {
var keepAlive time.Duration
if keepAliveSeconds > 0 {
keepAlive = time.Duration(keepAliveSeconds) * time.Second
}
return &TCPServer{
listenAddr: listenAddr,
engine: engine,
keepAlive: keepAlive,
}
}
func (s *TCPServer) Start() error {
var listener net.Listener
var err error
if s.keepAlive > 0 {
var lc net.ListenConfig
lc.KeepAlive = s.keepAlive
listener, err = lc.Listen(context.Background(), "tcp", s.listenAddr)
} else {
listener, err = net.Listen("tcp", s.listenAddr)
}
if err != nil {
return err
}
defer listener.Close()
logInfof("CheetahDB TCP server listening on %s", s.listenAddr)
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("ERROR: Accepting connection: %v", err)
continue
}
go s.handleConnection(conn)
}
}
// connSession è lo stato di scope connessione: il database corrente, che
// DATABASE e RESET_DB spostano. Esiste perché i due front-end della connessione
// TCP — testo e binario — devono instradare esattamente allo stesso modo: una
// seconda copia dello smistamento divergerebbe in silenzio, com'è già successo
// fra CLI e TCP.
type connSession struct {
engine *Engine
db *Database
}
// execute instrada una riga di comando canonica. È l'unico punto in cui si
// decide chi esegue cosa: i comandi di scope engine, i due di scope connessione
// e tutto il resto a ExecuteCommand.
func (c *connSession) execute(line string) string {
parts := strings.SplitN(line, " ", 2)
command := strings.ToUpper(parts[0])
controlArgs := ""
if len(parts) > 1 {
controlArgs = parts[1]
}
// I comandi di scope engine (DB_CONFIG, DB_CREATE, DB_LIST) stanno in engine.go, in
// una funzione condivisa con la CLI: non toccano il database corrente,
// ma non possono nemmeno passare da ExecuteCommand.
if handled, ok := engineControlCommand(c.engine, command, controlArgs); ok {
return handled
}
switch command {
case "DATABASE":
if len(parts) < 2 {
return "ERROR,missing_database_name"
}
target, overrides, parseErr := parseDatabaseTarget(parts[1])
if parseErr != nil {
return fmt.Sprintf("ERROR,%v", parseErr)
}
if overrides != nil {
c.engine.SetDatabaseOverrides(target, *overrides)
}
newDB, errDb := c.engine.GetDatabase(target)
if errDb != nil {
return fmt.Sprintf("ERROR,cannot_load_db:%v", errDb)
}
c.db = newDB
return fmt.Sprintf("SUCCESS,database_changed_to_%s", target)
case "RESET_DB":
target := c.db.Name()
var overrides *DatabaseOverrides
if len(parts) > 1 && strings.TrimSpace(parts[1]) != "" {
var parseErr error
target, overrides, parseErr = parseDatabaseTarget(parts[1])
if parseErr != nil {
return fmt.Sprintf("ERROR,%v", parseErr)
}
}
if overrides != nil {
c.engine.SetDatabaseOverrides(target, *overrides)
}
if err := c.engine.ResetDatabase(target); err != nil {
return fmt.Sprintf("ERROR,cannot_reset_db:%v", err)
}
newDB, errDb := c.engine.GetDatabase(target)
if errDb != nil {
return fmt.Sprintf("ERROR,cannot_load_db:%v", errDb)
}
c.db = newDB
return fmt.Sprintf("SUCCESS,database_reset_to_%s", target)
default:
response, err := c.db.ExecuteCommand(line)
if err != nil {
return fmt.Sprintf("ERROR,internal_error:%v", err)
}
return response
}
}
func (s *TCPServer) handleConnection(conn net.Conn) {
if s.keepAlive > 0 {
if tcpConn, ok := conn.(*net.TCPConn); ok {
if err := tcpConn.SetKeepAlive(true); err != nil {
log.Printf("WARN: Unable to enable TCP keep-alive for %s: %v", conn.RemoteAddr(), err)
} else if err := tcpConn.SetKeepAlivePeriod(s.keepAlive); err != nil {
log.Printf("WARN: Unable to set TCP keep-alive period for %s: %v", conn.RemoteAddr(), err)
}
}
}
logInfof("New connection from %s", conn.RemoteAddr())
defer conn.Close()
currentDB, err := s.engine.GetDatabase(s.engine.DefaultDatabaseName())
if err != nil {
io.WriteString(conn, "ERROR,failed_to_load_default_db\n")
return
}
session := &connSession{engine: s.engine, db: currentDB}
reader := bufio.NewReader(conn)
// Un frame binario comincia con 0xC7, byte che nessun comando testuale può
// avere in prima posizione: la modalità si riconosce dal primo byte, senza
// negoziazione preventiva e senza rompere i client che parlano testo.
if head, peekErr := reader.Peek(1); peekErr == nil && head[0] == binaryFrameMagic {
s.handleBinaryConnection(conn, reader, session)
logInfof("Connection closed for %s", conn.RemoteAddr())
return
}
for {
// Non scriviamo un prompt via TCP, il client dovrebbe sapere cosa fare
line, err := reader.ReadString('\n')
if err != nil {
if err != io.EOF {
logErrorf("Reading from %s: %v", conn.RemoteAddr(), err)
}
break
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
if _, err := io.WriteString(conn, session.execute(line)+"\n"); err != nil {
logErrorf("Writing to %s: %v", conn.RemoteAddr(), err)
break
}
}
logInfof("Connection closed for %s", conn.RemoteAddr())
}
// handleBinaryConnection serve una connessione in modalità binaria
// (binary_protocol.go). L'handshake è obbligatorio e viene per primo: è lì che
// si fissano le larghezze di default dei tipi numerici per tutta la
// connessione.
//
// Un frame malformato non chiude la connessione — risponde con un ERROR come
// farebbe un comando sbagliato in testo. Solo un frame illeggibile a livello di
// intestazione (magic sbagliato, lunghezza fuori scala) la chiude, perché a
// quel punto non si sa più dove finisce un frame e dove comincia il prossimo.
func (s *TCPServer) handleBinaryConnection(conn net.Conn, reader *bufio.Reader, session *connSession) {
binSession := newBinarySession()
frame, err := readBinaryFrame(reader)
if err != nil {
logErrorf("Binary handshake from %s: %v", conn.RemoteAddr(), err)
return
}
if frame.Type != binaryFrameHandshake {
writeBinaryError(conn, "handshake_expected")
return
}
version, widths, err := decodeHandshake(frame.Body)
if err != nil {
writeBinaryError(conn, sanitizeResponseToken(err.Error()))
return
}
if version != binaryProtocolVersion {
writeBinaryError(conn, fmt.Sprintf("unsupported_protocol_version:%d", version))
return
}
binSession.widths = widths.overlay(defaultNumericProfile())
if _, err := conn.Write(encodeHandshakeAck(binSession, currentCommandIndex(), currentArgumentKeys())); err != nil {
logErrorf("Writing to %s: %v", conn.RemoteAddr(), err)
return
}
logInfof("Binary protocol enabled for %s (uint=%d int=%d float=%d)",
conn.RemoteAddr(), binSession.widths.Uint, binSession.widths.Int, binSession.widths.Float)
for {
frame, err := readBinaryFrame(reader)
if err != nil {
if err != io.EOF {
logErrorf("Reading binary frame from %s: %v", conn.RemoteAddr(), err)
}
return
}
if frame.Type != binaryFrameRequest {
if !writeBinaryError(conn, "request_frame_expected") {
return
}
continue
}
line, decodeErr := decodeBinaryRequest(session.db, binSession, frame.Body)
if decodeErr != nil {
if !writeBinaryError(conn, sanitizeResponseToken(decodeErr.Error())) {
return
}
continue
}
if strings.TrimSpace(line) == "" {
if !writeBinaryError(conn, "empty_command") {
return
}
continue
}
response := session.execute(line)
if _, err := conn.Write(encodeBinaryResponse(response, binSession.widths)); err != nil {
logErrorf("Writing to %s: %v", conn.RemoteAddr(), err)
return
}
}
}
// writeBinaryError risponde con un ERROR incorniciato. Rende false quando la
// scrittura fallisce, cioè quando il chiamante deve smettere.
func writeBinaryError(conn net.Conn, reason string) bool {
_, err := conn.Write(encodeBinaryResponse("ERROR,"+reason, defaultNumericProfile()))
if err != nil {
logErrorf("Writing to %s: %v", conn.RemoteAddr(), err)
return false
}
return true
}