We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Code Review

I just want to point out a few of the ways I implemented some of this, and talk through some of the decisions. First the Serve and listen functions:

func Serve(port int, handler Handler) (*Server, error) {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return nil, err
	}
	s := &Server{
		handler:  handler,
		listener: listener,
	}
	go s.listen()
	return s, nil
}

func (s *Server) listen() {
	for {
		conn, err := s.listener.Accept()
		if err != nil {
			if s.closed.Load() {
				return
			}
			log.Printf("Error accepting connection: %v", err)
			continue
		}
		go s.handle(conn)
	}
}

You'll notice they're very similar to our original TCP listener, with the added handler to actually handle and respond to the HTTP request. In fact, the interesting part is that handle method:

func (s *Server) handle(conn net.Conn) {
	defer conn.Close()
	req, err := request.RequestFromReader(conn)
	if err != nil {
		hErr := &HandlerError{
			StatusCode: response.StatusCodeBadRequest,
			Message:    err.Error(),
		}
		hErr.Write(conn)
		return
	}
	buf := bytes.NewBuffer([]byte{})
	hErr := s.handler(buf, req)
	if hErr != nil {
		hErr.Write(conn)
		return
	}
	b := buf.Bytes()
	response.WriteStatusLine(conn, response.StatusCodeSuccess)
	headers := response.GetDefaultHeaders(len(b))
	response.WriteHeaders(conn, headers)
	conn.Write(b)
	return
}

Some interesting things to note:

  • I used a "raw" net.Conn it implements both the io.Reader and io.Writer interfaces, so it works perfectly with our request.RequestFromReader and response.WriteX functions.
  • Our user supplied handler functions are kinda wonky: in the event of an error, they return a *HandlerError and don't write to the io.Writer themselves, but in the event of success, they do write to the io.Writer themselves and don't return an error.