💾 Archived View for source.community › ckaznocha › gemini › raw › main › geminitest › server.go captured on 2024-05-26 at 15:09:52.

View Raw

More Information

⬅️ Previous capture (2021-12-17)

-=-=-=-=-=-=-

package geminitest

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"sync"
	"testing"

	"source.community/ckaznocha/gemini"
)

// Server is gemini server listing on a randomly chosen open port.
type Server struct {
	Listener    net.Listener
	TLS         *tls.Config
	Config      *gemini.Server
	certificate *x509.Certificate
	URL         string
	Addr        string
	wg          sync.WaitGroup
	mu          sync.Mutex
	closed      bool
}

func newLocalListener() net.Listener {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
			panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
		}
	}

	return l
}

// NewUnstartedServer returns a server which has not been started.
func NewUnstartedServer(t *testing.T, handler gemini.Handler) *Server {
	t.Helper()

	return &Server{
		Listener: newLocalListener(),
		Config: &gemini.Server{
			Handler:    handler,
			LogHandler: func(message string, isError bool) { t.Log(message) },
		},
	}
}

// Start starts an unstarted server.
func (s *Server) Start() {
	if s.URL != "" {
		panic("Server already started")
	}

	cert, err := tls.LoadX509KeyPair("testdata/cert.pem", "testdata/key.pem")
	if err != nil {
		panic(fmt.Sprintf("httptest: NewTLSServer: %v", err))
	}

	if existingConfig := s.TLS; existingConfig != nil {
		s.TLS = existingConfig.Clone()
	} else {
		s.TLS = new(tls.Config)
	}

	if len(s.TLS.Certificates) == 0 {
		s.TLS.Certificates = []tls.Certificate{cert}
	}

	s.certificate, err = x509.ParseCertificate(s.TLS.Certificates[0].Certificate[0])
	if err != nil {
		panic(fmt.Sprintf("httptest: NewTLSServer: %v", err))
	}

	certpool := x509.NewCertPool()
	certpool.AddCert(s.certificate)

	s.Listener = tls.NewListener(s.Listener, s.TLS)
	s.URL = "gemini://" + s.Listener.Addr().String()
	s.Addr = s.Listener.Addr().String()
	s.goServe()
}

func (s *Server) goServe() {
	s.wg.Add(1)

	go func() {
		defer s.wg.Done()

		if err := s.Config.Serve(s.Listener); err != nil && s.Config.LogHandler != nil {
			s.Config.LogHandler(err.Error(), true)
		}
	}()
}

// NewServer returns a test server which has been started and is ready to use.
func NewServer(t *testing.T, handler gemini.Handler) *Server {
	t.Helper()

	ts := NewUnstartedServer(t, handler)
	ts.Start()

	return ts
}

// Close closes the server.
func (s *Server) Close() {
	s.mu.Lock()
	if !s.closed {
		s.closed = true

		if err := s.Listener.Close(); err != nil && s.Config.LogHandler != nil {
			s.Config.LogHandler(err.Error(), true)
		}
	}

	s.mu.Unlock()

	s.wg.Wait()
}