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