package gemini import ( "context" "crypto/tls" "io" "net" "sync" ) type Server struct { ctx context.Context network string address string cancel context.CancelFunc wg *sync.WaitGroup listener net.Listener handler Handler } func NewServer( ctx context.Context, tlsConfig *tls.Config, network string, address string, handler Handler, ) (*Server, error) { listener, err := net.Listen(network, address) if err != nil { return nil, err } s := &Server{ ctx: ctx, network: network, address: address, wg: &sync.WaitGroup{}, listener: tls.NewListener(listener, tlsConfig), handler: handler, } return s, nil } // Serve starts the server and blocks until it is closed. // // This function will allocate resources which are not cleaned up until // Close() is called. func (s *Server) Serve() error { s.wg.Add(1) defer s.wg.Done() s.ctx, s.cancel = context.WithCancel(s.ctx) s.wg.Add(1) go s.propagateCancel() for { conn, err := s.listener.Accept() if err != nil { if s.closed() { err = nil } return err } s.wg.Add(1) go s.handleConn(conn) } } // Close begins a graceful shutdown of the server. // // It cancels the server's context which interrupts all concurrently running // request handlers, if they support it. It then blocks until all resources // have been cleaned up and all request handlers have completed. func (s *Server) Close() { s.cancel() s.wg.Wait() } // Network returns the network type on which the server is running. func (s *Server) Network() string { return s.network } // Address returns the address on which the server is listening. func (s *Server) Address() string { return s.address } // Hostname returns just the hostname portion of the listen address. func (s *Server) Hostname() string { host, _, _ := net.SplitHostPort(s.address) return host } // Port returns the port on which the server is listening. func (s *Server) Port() string { _, portStr, _ := net.SplitHostPort(s.address) return portStr } func (s *Server) handleConn(conn net.Conn) { defer s.wg.Done() defer conn.Close() req, err := ParseRequest(conn) if err != nil { _, _ = io.Copy(conn, BadRequest(err.Error())) return } req.Server = s req.RemoteAddr = conn.RemoteAddr() if tlsconn, ok := conn.(*tls.Conn); req != nil && ok { state := tlsconn.ConnectionState() req.TLSState = &state } resp := s.handler(s.ctx, req) defer resp.Close() _, _ = io.Copy(conn, resp) } func (s *Server) propagateCancel() { go func() { defer s.wg.Done() <-s.ctx.Done() _ = s.listener.Close() }() } func (s *Server) closed() bool { select { case <-s.ctx.Done(): return true default: return false } }