server.go raw
1 // Package httpsrv is a small HTTP/1.1 server built directly on net.Listener.
2 //
3 // TinyGo's net/http implements Hijack, but a hijacked connection is closed as
4 // soon as the handler returns and writes to it never reach the peer. A relay
5 // needs a stable upgraded connection, so torly speaks HTTP itself: one request
6 // per connection, Connection: close, and an explicit Hijack that hands the
7 // socket and its buffered reader to the WebSocket layer.
8 package httpsrv
9
10 import (
11 "bufio"
12 "errors"
13 "fmt"
14 "io"
15 "net"
16 "strconv"
17 "strings"
18 "sync"
19 "time"
20 )
21
22 // Headers maps lowercase header names to their combined values.
23 type Headers map[string]string
24
25 // Request is one parsed HTTP request.
26 type Request struct {
27 Method string
28 Path string
29 RawQuery string
30 Proto string
31 Header Headers
32 Body []byte
33 RemoteAddr string
34
35 conn net.Conn
36 brw *bufio.ReadWriter
37 hijacked bool
38 }
39
40 // Hijack gives the socket and its buffered reader to the caller. After Hijack
41 // the HTTP server writes nothing more on this connection.
42 func (r *Request) Hijack() (net.Conn, *bufio.ReadWriter, error) {
43 if r.hijacked {
44 return nil, nil, errors.New("httpsrv: already hijacked")
45 }
46 r.hijacked = true
47 return r.conn, r.brw, nil
48 }
49
50 // PathParam is a convenience for routes such as /list/<pubkey>.
51 func (r *Request) PathParam() string {
52 if i := strings.LastIndexByte(r.Path, '/'); i >= 0 {
53 return r.Path[i+1:]
54 }
55 return r.Path
56 }
57
58 // ResponseWriter writes one response.
59 type ResponseWriter struct {
60 bw *bufio.Writer
61 status int
62 wroteHeader bool
63 headers Headers
64 }
65
66 // SetHeader sets a response header.
67 func (w *ResponseWriter) SetHeader(name, value string) {
68 if w.headers == nil {
69 w.headers = make(Headers, 6)
70 }
71 w.headers[strings.ToLower(name)] = value
72 }
73
74 // WriteHeader sends the status line and headers.
75 func (w *ResponseWriter) WriteHeader(status int) {
76 if w.wroteHeader {
77 return
78 }
79 w.status = status
80 w.wroteHeader = true
81 if w.headers == nil {
82 w.headers = make(Headers, 4)
83 }
84 w.headers["connection"] = "close"
85 fmt.Fprintf(w.bw, "HTTP/1.1 %d %s\r\n", status, StatusText(status))
86 fmt.Fprintf(w.bw, "Date: %s\r\n", time.Now().UTC().Format(time.RFC1123))
87 for k, v := range w.headers {
88 fmt.Fprintf(w.bw, "%s: %s\r\n", canonicalHeader(k), v)
89 }
90 w.bw.WriteString("\r\n")
91 }
92
93 // Write appends body bytes.
94 func (w *ResponseWriter) Write(p []byte) (int, error) {
95 if !w.wroteHeader {
96 w.WriteHeader(200)
97 }
98 return w.bw.Write(p)
99 }
100
101 // WriteString appends a string body.
102 func (w *ResponseWriter) WriteString(s string) (int, error) {
103 if !w.wroteHeader {
104 w.WriteHeader(200)
105 }
106 return w.bw.WriteString(s)
107 }
108
109 // Flush pushes buffered bytes to the socket.
110 func (w *ResponseWriter) Flush() error { return w.bw.Flush() }
111
112 // Status returns the status code sent, or 0.
113 func (w *ResponseWriter) Status() int { return w.status }
114
115 func (w *ResponseWriter) finish() error {
116 if !w.wroteHeader {
117 w.WriteHeader(204)
118 }
119 return w.bw.Flush()
120 }
121
122 func canonicalHeader(k string) string {
123 out := make([]byte, 0, len(k))
124 upper := true
125 for i := 0; i < len(k); i++ {
126 c := k[i]
127 if upper && c >= 'a' && c <= 'z' {
128 c -= 'a' - 'A'
129 }
130 out = append(out, c)
131 upper = c == '-'
132 }
133 return string(out)
134 }
135
136 // StatusText returns the reason phrase for common codes.
137 func StatusText(code int) string {
138 switch code {
139 case 200:
140 return "OK"
141 case 201:
142 return "Created"
143 case 202:
144 return "Accepted"
145 case 204:
146 return "No Content"
147 case 304:
148 return "Not Modified"
149 case 400:
150 return "Bad Request"
151 case 401:
152 return "Unauthorized"
153 case 403:
154 return "Forbidden"
155 case 404:
156 return "Not Found"
157 case 405:
158 return "Method Not Allowed"
159 case 409:
160 return "Conflict"
161 case 411:
162 return "Length Required"
163 case 413:
164 return "Payload Too Large"
165 case 415:
166 return "Unsupported Media Type"
167 case 429:
168 return "Too Many Requests"
169 case 500:
170 return "Internal Server Error"
171 case 501:
172 return "Not Implemented"
173 case 503:
174 return "Service Unavailable"
175 default:
176 return "Status"
177 }
178 }
179
180 // Server accepts connections and dispatches one request per connection.
181 type Server struct {
182 Addr string
183 Handler func(*Request, *ResponseWriter)
184 MaxHeaderBytes int
185 MaxBodyBytes int64
186 ReadTimeout time.Duration
187 WriteTimeout time.Duration
188
189 ln net.Listener
190 mu sync.Mutex
191 conns map[net.Conn]struct{}
192 closed bool
193 }
194
195 // Start binds the listener without serving. It is separate from Serve so a
196 // caller (or a test) can learn the bound port before traffic arrives.
197 func (s *Server) Start() error {
198 if s.MaxHeaderBytes <= 0 {
199 s.MaxHeaderBytes = 32 << 10
200 }
201 if s.MaxBodyBytes <= 0 {
202 s.MaxBodyBytes = 64 << 20
203 }
204 ln, err := net.Listen("tcp", s.Addr)
205 if err != nil {
206 return err
207 }
208 s.mu.Lock()
209 s.ln = ln
210 if s.conns == nil {
211 s.conns = make(map[net.Conn]struct{})
212 }
213 s.mu.Unlock()
214 return nil
215 }
216
217 // ListenAndServe binds Addr and serves until Close is called.
218 func (s *Server) ListenAndServe() error {
219 if err := s.Start(); err != nil {
220 return err
221 }
222 return s.Serve()
223 }
224
225 // Serve accepts connections on the bound listener.
226 func (s *Server) Serve() error {
227 s.mu.Lock()
228 ln := s.ln
229 s.mu.Unlock()
230 if ln == nil {
231 return errors.New("httpsrv: not started")
232 }
233 for {
234 c, err := ln.Accept()
235 if err != nil {
236 s.mu.Lock()
237 closed := s.closed
238 s.mu.Unlock()
239 if closed {
240 return nil
241 }
242 return err
243 }
244 sc := &serverConn{Conn: c}
245 s.mu.Lock()
246 if s.closed {
247 s.mu.Unlock()
248 sc.Close()
249 return nil
250 }
251 s.conns[sc] = struct{}{}
252 s.mu.Unlock()
253 go s.serveConn(sc)
254 }
255 }
256
257 // Addr returns the bound address, useful when Addr used port 0.
258 func (s *Server) BoundAddr() string {
259 s.mu.Lock()
260 defer s.mu.Unlock()
261 if s.ln == nil {
262 return ""
263 }
264 return s.ln.Addr().String()
265 }
266
267 // Close stops the listener and every live connection.
268 func (s *Server) Close() error {
269 s.mu.Lock()
270 if s.closed {
271 s.mu.Unlock()
272 return nil
273 }
274 s.closed = true
275 ln := s.ln
276 conns := s.conns
277 s.conns = make(map[net.Conn]struct{})
278 s.mu.Unlock()
279 var err error
280 if ln != nil {
281 err = ln.Close()
282 }
283 for c := range conns {
284 c.Close()
285 }
286 return err
287 }
288
289 // serverConn makes Close idempotent. TinyGo's TCPConn.Close calls close(2)
290 // unconditionally, so a double close can close an unrelated descriptor that
291 // reused the number.
292 type serverConn struct {
293 net.Conn
294 once sync.Once
295 err error
296 }
297
298 func (c *serverConn) Close() error {
299 c.once.Do(func() { c.err = c.Conn.Close() })
300 return c.err
301 }
302
303 func (s *Server) serveConn(c net.Conn) {
304 defer func() {
305 s.mu.Lock()
306 delete(s.conns, c)
307 s.mu.Unlock()
308 }()
309 if s.ReadTimeout > 0 {
310 c.SetReadDeadline(time.Now().Add(s.ReadTimeout))
311 }
312 br := bufio.NewReaderSize(c, 8<<10)
313 bw := bufio.NewWriterSize(c, 8<<10)
314 brw := bufio.NewReadWriter(br, bw)
315 rw := &ResponseWriter{bw: bw}
316
317 req, err := readRequest(c, brw, s.MaxHeaderBytes, s.MaxBodyBytes)
318 if err != nil {
319 if err != errClosed {
320 rw.SetHeader("content-type", "text/plain")
321 rw.WriteHeader(400)
322 rw.WriteString(err.Error() + "\n")
323 rw.finish()
324 }
325 c.Close()
326 return
327 }
328 if req == nil {
329 c.Close()
330 return
331 }
332 if s.WriteTimeout > 0 {
333 c.SetWriteDeadline(time.Now().Add(s.WriteTimeout))
334 }
335 s.Handler(req, rw)
336 if req.hijacked {
337 return
338 }
339 if err := rw.finish(); err != nil {
340 _ = err
341 }
342 c.Close()
343 }
344
345 var errClosed = errors.New("closed")
346
347 func readRequest(c net.Conn, brw *bufio.ReadWriter, maxHeader int, maxBody int64) (*Request, error) {
348 req, err := ReadRequest(brw.Reader, c.RemoteAddr().String(), maxHeader, maxBody, func() error {
349 fmt.Fprintf(brw, "HTTP/1.1 100 Continue\r\n\r\n")
350 return brw.Flush()
351 })
352 if err != nil || req == nil {
353 return req, err
354 }
355 req.conn = c
356 req.brw = brw
357 return req, nil
358 }
359
360 // ReadRequest parses one HTTP request from br. It is the transport-neutral
361 // half of the server and is also used by the relay's reader workers, which own
362 // their socket and therefore do not need a net.Conn here.
363 func ReadRequest(br *bufio.Reader, remote string, maxHeader int, maxBody int64, onContinue func() error) (*Request, error) {
364 line, n, err := readLine(br, maxHeader)
365 if err != nil {
366 return nil, err
367 }
368 if line == "" {
369 return nil, nil
370 }
371 headerBytes := n
372 parts := strings.Split(line, " ")
373 if len(parts) != 3 {
374 return nil, errors.New("malformed request line")
375 }
376 req := &Request{
377 Method: parts[0],
378 Proto: parts[2],
379 Header: make(Headers, 12),
380 RemoteAddr: remote,
381 }
382 target := parts[1]
383 if i := strings.IndexByte(target, '?'); i >= 0 {
384 req.Path = target[:i]
385 req.RawQuery = target[i+1:]
386 } else {
387 req.Path = target
388 }
389
390 for {
391 line, n, err := readLine(br, maxHeader)
392 if err != nil {
393 return nil, err
394 }
395 headerBytes += n
396 if headerBytes > maxHeader {
397 return nil, errors.New("headers too large")
398 }
399 if line == "" {
400 break
401 }
402 i := strings.IndexByte(line, ':')
403 if i <= 0 {
404 return nil, errors.New("malformed header")
405 }
406 name := strings.ToLower(strings.TrimSpace(line[:i]))
407 value := strings.TrimSpace(line[i+1:])
408 if old, ok := req.Header[name]; ok && old != "" {
409 req.Header[name] = old + ", " + value
410 } else {
411 req.Header[name] = value
412 }
413 }
414
415 if te := req.Header["transfer-encoding"]; te != "" && !strings.EqualFold(te, "identity") {
416 return nil, errors.New("chunked requests are not supported")
417 }
418 length := int64(0)
419 if cl := req.Header["content-length"]; cl != "" {
420 v, err := strconv.ParseInt(cl, 10, 64)
421 if err != nil || v < 0 {
422 return nil, errors.New("bad content-length")
423 }
424 length = v
425 }
426 if length > maxBody {
427 return nil, errors.New("request body too large")
428 }
429 if strings.EqualFold(req.Header["expect"], "100-continue") {
430 if onContinue != nil {
431 if err := onContinue(); err != nil {
432 return nil, err
433 }
434 }
435 }
436 if length > 0 {
437 body := make([]byte, length)
438 if _, err := io.ReadFull(br, body); err != nil {
439 return nil, err
440 }
441 req.Body = body
442 }
443 return req, nil
444 }
445
446 func readLine(br *bufio.Reader, max int) (string, int, error) {
447 line, err := br.ReadString('\n')
448 if err != nil {
449 if err == io.EOF && line == "" {
450 return "", 0, errClosed
451 }
452 if err != io.EOF {
453 return "", 0, err
454 }
455 }
456 if len(line) > max {
457 return "", len(line), errors.New("line too long")
458 }
459 line = strings.TrimSuffix(line, "\n")
460 line = strings.TrimSuffix(line, "\r")
461 return line, len(line) + 1, nil
462 }
463