๐Ÿ’พ Archived View for source.community โ€บ ckaznocha โ€บ gemini โ€บ blob โ€บ main โ€บ server.go captured on 2024-02-05 at 09:53:11. Gemini links have been rewritten to link to archived content

View Raw

More Information

โฌ…๏ธ Previous capture (2023-01-29)

๐Ÿšง View Differences

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

                                                         .
,-. ,-. . . ,-. ,-. ,-.    ,-. ,-. ,-,-. ,-,-. . . ,-. . |- . .
`-. | | | | |   |   |-'    |   | | | | | | | | | | | | | |  | |
`-' `-' `-^ '   `-' `-' :: `-' `-' ' ' ' ' ' ' `-^ ' ' ' `' `-|
                                                             /|
                                                            `-'

Profile for ckaznocha

ckaznocha / gemini

git clone https://source.community/ckaznocha/gemini.git

Branches

Log

Tree

/server.go (main)

โ†‘ /

blob

View raw contents of /server.go (main)

โ”€โ”€โ”€โ”€โ•ฎ
   1โ”‚ package gemini
   2โ”‚ 
   3โ”‚ import (
   4โ”‚ 	"bufio"
   5โ”‚ 	"context"
   6โ”‚ 	"crypto/tls"
   7โ”‚ 	"errors"
   8โ”‚ 	"fmt"
   9โ”‚ 	"math/rand"
  10โ”‚ 	"net"
  11โ”‚ 	"strings"
  12โ”‚ 	"sync"
  13โ”‚ 	"sync/atomic"
  14โ”‚ 	"time"
  15โ”‚ )
  16โ”‚ 
  17โ”‚ type serverContextKeyType struct{}
  18โ”‚ 
  19โ”‚ //nolint:gochecknoglobals // context keys need to be global.
  20โ”‚ var serverContextKey = &serverContextKeyType{}
  21โ”‚ 
  22โ”‚ // ServerFromCtx extracts a server from a context if present. If a server is not
  23โ”‚ // present on the context the returned bool will be false.
  24โ”‚ func ServerFromCtx(ctx context.Context) (*Server, bool) {
  25โ”‚ 	srv, ok := ctx.Value(serverContextKey).(*Server)
  26โ”‚ 
  27โ”‚ 	return srv, ok
  28โ”‚ }
  29โ”‚ 
  30โ”‚ // Server serves network requests using the Gemini protocol.
  31โ”‚ type Server struct {
  32โ”‚ 	// Handler is a handler that is called each time a new network requests is
  33โ”‚ 	// received. Unlike Go's net/http panics in handlers will not be recovered
  34โ”‚ 	// automatically.
  35โ”‚ 	Handler Handler
  36โ”‚ 
  37โ”‚ 	// LogHandler is an optional function that allows a custom logger to be
  38โ”‚ 	// hooked into the server. Erroneous logs will be passed in with `isError`
  39โ”‚ 	// set to true.
  40โ”‚ 	LogHandler func(message string, isError bool)
  41โ”‚ 
  42โ”‚ 	// BaseContext is a optional function that takes a listener and returns a
  43โ”‚ 	// context. The context returned by BaseContext will be used to create all
  44โ”‚ 	// other contexts in the request lifecycle.
  45โ”‚ 	BaseContext func(net.Listener) context.Context
  46โ”‚ 
  47โ”‚ 	// ConnContext is an optional function that takes a context and a net.Conn
  48โ”‚ 	// and returns a context. Like BaseContext, the context returned by
  49โ”‚ 	// ConnContext will be used to create all contexts in the request lifecycle
  50โ”‚ 	// after the connection has been created.
  51โ”‚ 	ConnContext func(ctx context.Context, c net.Conn) context.Context
  52โ”‚ 
  53โ”‚ 	// TLSConfig is the TLS config to use for the server.
  54โ”‚ 	TLSConfig *tls.Config
  55โ”‚ 
  56โ”‚ 	listeners     *listeners
  57โ”‚ 	shutdownHooks *hooks
  58โ”‚ 	inShutdown    atomicBool
  59โ”‚ }
  60โ”‚ 
  61โ”‚ type hooks struct {
  62โ”‚ 	fns []func()
  63โ”‚ 	mu  sync.RWMutex
  64โ”‚ }
  65โ”‚ 
  66โ”‚ func (h *hooks) register(fn func()) {
  67โ”‚ 	h.mu.Lock()
  68โ”‚ 	defer h.mu.Unlock()
  69โ”‚ 
  70โ”‚ 	h.fns = append(h.fns, fn)
  71โ”‚ }
  72โ”‚ 
  73โ”‚ func (h *hooks) call() {
  74โ”‚ 	var wg sync.WaitGroup
  75โ”‚ 	for _, f := range h.fns {
  76โ”‚ 		wg.Add(1)
  77โ”‚ 
  78โ”‚ 		go func(f func()) {
  79โ”‚ 			defer wg.Done()
  80โ”‚ 			f()
  81โ”‚ 		}(f)
  82โ”‚ 	}
  83โ”‚ 
  84โ”‚ 	wg.Wait()
  85โ”‚ }
  86โ”‚ 
  87โ”‚ type atomicBool int32
  88โ”‚ 
  89โ”‚ func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
  90โ”‚ func (b *atomicBool) set()        { atomic.StoreInt32((*int32)(b), 1) }
  91โ”‚ 
  92โ”‚ func (s *Server) closeAndLogOnError(closeFn func() error) {
  93โ”‚ 	if err := closeFn(); err != nil && s.LogHandler != nil {
  94โ”‚ 		s.LogHandler(err.Error(), true)
  95โ”‚ 	}
  96โ”‚ }
  97โ”‚ 
  98โ”‚ // ListenAndServeTLS creates a listener and starts the server. If certFile and
  99โ”‚ // keyFile are non-empty strings the key pair will be loaded and used.
 100โ”‚ func (s *Server) ListenAndServeTLS(addr, certFile, keyFile string) error {
 101โ”‚ 	if s.shuttingDown() {
 102โ”‚ 		return fmt.Errorf("%w", ErrServerShutdown)
 103โ”‚ 	}
 104โ”‚ 
 105โ”‚ 	if addr == "" {
 106โ”‚ 		addr = ":1965"
 107โ”‚ 	}
 108โ”‚ 
 109โ”‚ 	l, err := net.Listen("tcp", addr)
 110โ”‚ 	if err != nil {
 111โ”‚ 		return fmt.Errorf("%w: %s", ErrStartingServer, err)
 112โ”‚ 	}
 113โ”‚ 
 114โ”‚ 	return s.ServeTLS(l, certFile, keyFile)
 115โ”‚ }
 116โ”‚ 
 117โ”‚ // ServeTLS starts a server with the provided listener, wrapping it in a TLS
 118โ”‚ // listener. If certFile and keyFile are non-empty strings the key pair will be
 119โ”‚ // loaded and used.
 120โ”‚ func (s *Server) ServeTLS(l net.Listener, certFile, keyFile string) error {
 121โ”‚ 	if s.shuttingDown() {
 122โ”‚ 		return fmt.Errorf("%w", ErrServerShutdown)
 123โ”‚ 	}
 124โ”‚ 
 125โ”‚ 	var tlsConfig *tls.Config
 126โ”‚ 	if s.TLSConfig == nil {
 127โ”‚ 		tlsConfig = &tls.Config{
 128โ”‚ 			MinVersion: tls.VersionTLS13,
 129โ”‚ 			ClientAuth: tls.RequestClientCert,
 130โ”‚ 		}
 131โ”‚ 	} else {
 132โ”‚ 		tlsConfig = s.TLSConfig.Clone()
 133โ”‚ 	}
 134โ”‚ 
 135โ”‚ 	if tlsConfig.MinVersion < tls.VersionTLS12 {
 136โ”‚ 		return fmt.Errorf("%w: unsupported TLS version %q", ErrStartingServer, tlsConfig.MinVersion)
 137โ”‚ 	}
 138โ”‚ 
 139โ”‚ 	if tlsConfig.Certificates == nil && tlsConfig.GetCertificate == nil {
 140โ”‚ 		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
 141โ”‚ 		if err != nil {
 142โ”‚ 			return fmt.Errorf("%w: %s", ErrStartingServer, err)
 143โ”‚ 		}
 144โ”‚ 
 145โ”‚ 		tlsConfig.Certificates = []tls.Certificate{cert}
 146โ”‚ 	}
 147โ”‚ 
 148โ”‚ 	return s.Serve(tls.NewListener(l, tlsConfig))
 149โ”‚ }
 150โ”‚ 
 151โ”‚ // Serve start a server using the provided listener. The listener should support
 152โ”‚ // TLS.
 153โ”‚ func (s *Server) Serve(l net.Listener) error {
 154โ”‚ 	if s.shuttingDown() {
 155โ”‚ 		return fmt.Errorf("%w", ErrServerShutdown)
 156โ”‚ 	}
 157โ”‚ 
 158โ”‚ 	ln := &listener{inner: l}
 159โ”‚ 	defer s.closeAndLogOnError(ln.Close)
 160โ”‚ 
 161โ”‚ 	if s.listeners == nil {
 162โ”‚ 		s.listeners = &listeners{}
 163โ”‚ 	}
 164โ”‚ 
 165โ”‚ 	s.listeners.add(ln)
 166โ”‚ 	defer s.listeners.remove(ln)
 167โ”‚ 
 168โ”‚ 	baseCtx := context.Background()
 169โ”‚ 	if s.BaseContext != nil {
 170โ”‚ 		baseCtx = s.BaseContext(l)
 171โ”‚ 		if baseCtx == nil {
 172โ”‚ 			return fmt.Errorf("%w: BaseContext is nil", ErrStartingServer)
 173โ”‚ 		}
 174โ”‚ 	}
 175โ”‚ 
 176โ”‚ 	var tempDelay time.Duration // how long to sleep on accept failure
 177โ”‚ 
 178โ”‚ 	ctx := context.WithValue(baseCtx, serverContextKey, s)
 179โ”‚ 
 180โ”‚ 	for {
 181โ”‚ 		rw, err := ln.Accept()
 182โ”‚ 		if err != nil {
 183โ”‚ 			if s.inShutdown.isSet() {
 184โ”‚ 				return fmt.Errorf("%w", ErrServerShutdown)
 185โ”‚ 			}
 186โ”‚ 
 187โ”‚ 			var ne net.Error
 188โ”‚ 			if errors.As(err, &ne) && ne.Temporary() {
 189โ”‚ 				if tempDelay == 0 {
 190โ”‚ 					tempDelay = 5 * time.Millisecond
 191โ”‚ 				} else {
 192โ”‚ 					tempDelay *= 2
 193โ”‚ 				}
 194โ”‚ 
 195โ”‚ 				if max := 1 * time.Second; tempDelay > max {
 196โ”‚ 					tempDelay = max
 197โ”‚ 				}
 198โ”‚ 
 199โ”‚ 				time.Sleep(tempDelay)
 200โ”‚ 
 201โ”‚ 				continue
 202โ”‚ 			}
 203โ”‚ 
 204โ”‚ 			return fmt.Errorf("%w: %s", ErrServing, err)
 205โ”‚ 		}
 206โ”‚ 
 207โ”‚ 		connCtx := ctx
 208โ”‚ 
 209โ”‚ 		if s.ConnContext != nil {
 210โ”‚ 			connCtx = s.ConnContext(connCtx, rw)
 211โ”‚ 			if connCtx == nil {
 212โ”‚ 				return fmt.Errorf("%w: ConnContext is nil", ErrServing)
 213โ”‚ 			}
 214โ”‚ 		}
 215โ”‚ 
 216โ”‚ 		tempDelay = 0
 217โ”‚ 
 218โ”‚ 		go s.handle(connCtx, rw)
 219โ”‚ 	}
 220โ”‚ }
 221โ”‚ 
 222โ”‚ func (s *Server) handle(ctx context.Context, c *tls.Conn) {
 223โ”‚ 	if s.LogHandler != nil {
 224โ”‚ 		s.LogHandler(fmt.Sprintf("started processing request from %s", c.RemoteAddr()), false)
 225โ”‚ 		defer s.LogHandler(fmt.Sprintf("finished processing request from %s", c.RemoteAddr()), false)
 226โ”‚ 	}
 227โ”‚ 
 228โ”‚ 	if err := c.HandshakeContext(ctx); err != nil && s.LogHandler != nil {
 229โ”‚ 		s.LogHandler(err.Error(), true)
 230โ”‚ 	}
 231โ”‚ 
 232โ”‚ 	w := &response{conn: c}
 233โ”‚ 
 234โ”‚ 	defer s.closeAndLogOnError(c.Close)
 235โ”‚ 	defer w.Failure(ctx, StatusNotFound, StatusNotFound.Description())
 236โ”‚ 
 237โ”‚ 	r, err := ReadRequest(bufio.NewReader(c))
 238โ”‚ 	if err != nil {
 239โ”‚ 		if s.LogHandler != nil {
 240โ”‚ 			s.LogHandler(err.Error(), true)
 241โ”‚ 		}
 242โ”‚ 
 243โ”‚ 		w.Failure(ctx, StatusBadRequest, err.Error())
 244โ”‚ 
 245โ”‚ 		return
 246โ”‚ 	}
 247โ”‚ 
 248โ”‚ 	if !strings.EqualFold(r.URI.Host, c.ConnectionState().ServerName) {
 249โ”‚ 		w.Failure(ctx, StatusProxyRequestRefused, StatusProxyRequestRefused.Description())
 250โ”‚ 
 251โ”‚ 		return
 252โ”‚ 	}
 253โ”‚ 
 254โ”‚ 	if r.URI.url.Scheme != "gemini" {
 255โ”‚ 		w.Failure(ctx, StatusProxyRequestRefused, StatusProxyRequestRefused.Description())
 256โ”‚ 
 257โ”‚ 		return
 258โ”‚ 	}
 259โ”‚ 
 260โ”‚ 	if r.URI.Port != "" {
 261โ”‚ 		_, port, err := net.SplitHostPort(c.LocalAddr().String())
 262โ”‚ 		if err != nil {
 263โ”‚ 			if s.LogHandler != nil {
 264โ”‚ 				s.LogHandler(err.Error(), true)
 265โ”‚ 			}
 266โ”‚ 
 267โ”‚ 			w.Failure(ctx, StatusServerFailure, err.Error())
 268โ”‚ 
 269โ”‚ 			return
 270โ”‚ 		}
 271โ”‚ 
 272โ”‚ 		if r.URI.Port != port {
 273โ”‚ 			w.Failure(ctx, StatusProxyRequestRefused, StatusProxyRequestRefused.Description())
 274โ”‚ 
 275โ”‚ 			return
 276โ”‚ 		}
 277โ”‚ 	}
 278โ”‚ 
 279โ”‚ 	r.RemoteAddr = c.RemoteAddr().String()
 280โ”‚ 
 281โ”‚ 	if connState := c.ConnectionState(); len(connState.PeerCertificates) > 0 {
 282โ”‚ 		r.Subject = &connState.PeerCertificates[0].Subject
 283โ”‚ 	}
 284โ”‚ 
 285โ”‚ 	if s.Handler != nil {
 286โ”‚ 		s.Handler.ServeGemini(ctx, w, r)
 287โ”‚ 	}
 288โ”‚ }
 289โ”‚ 
 290โ”‚ func (s *Server) shuttingDown() bool {
 291โ”‚ 	return s.inShutdown.isSet()
 292โ”‚ }
 293โ”‚ 
 294โ”‚ // RegisterOnShutdown adds a function which will be called when the server shuts
 295โ”‚ // down. RegisterOnShutdown can be called more than once to stack functions.
 296โ”‚ func (s *Server) RegisterOnShutdown(f func()) {
 297โ”‚ 	if s.shutdownHooks == nil {
 298โ”‚ 		s.shutdownHooks = &hooks{}
 299โ”‚ 	}
 300โ”‚ 
 301โ”‚ 	s.shutdownHooks.register(f)
 302โ”‚ }
 303โ”‚ 
 304โ”‚ const shutdownPollIntervalMax = 500 * time.Millisecond
 305โ”‚ 
 306โ”‚ // Shutdown shuts the server down gracefully. The shutdown will stop waiting for
 307โ”‚ // requests to finish if the context cancels.
 308โ”‚ func (s *Server) Shutdown(ctx context.Context) error {
 309โ”‚ 	s.inShutdown.set()
 310โ”‚ 
 311โ”‚ 	if s.shutdownHooks != nil {
 312โ”‚ 		s.shutdownHooks.call()
 313โ”‚ 	}
 314โ”‚ 
 315โ”‚ 	if s.listeners == nil {
 316โ”‚ 		return nil
 317โ”‚ 	}
 318โ”‚ 
 319โ”‚ 	err := s.listeners.close()
 320โ”‚ 
 321โ”‚ 	interval := pollInterval(time.Millisecond)
 322โ”‚ 
 323โ”‚ 	timer := time.NewTimer(interval.next())
 324โ”‚ 	defer timer.Stop()
 325โ”‚ 
 326โ”‚ 	for {
 327โ”‚ 		if s.listeners.len() == 0 {
 328โ”‚ 			return err
 329โ”‚ 		}
 330โ”‚ 		select {
 331โ”‚ 		case <-ctx.Done():
 332โ”‚ 			return fmt.Errorf("%w: context done: %s", ErrServerShutdown, ctx.Err())
 333โ”‚ 		case <-timer.C:
 334โ”‚ 			timer.Reset(interval.next())
 335โ”‚ 		}
 336โ”‚ 	}
 337โ”‚ }
 338โ”‚ 
 339โ”‚ type pollInterval time.Duration
 340โ”‚ 
 341โ”‚ func (p *pollInterval) next() time.Duration {
 342โ”‚ 	const jitterPercent = 10
 343โ”‚ 
 344โ”‚ 	last := time.Duration(*p)
 345โ”‚ 	interval := last + time.Duration(rand.Intn(int(last/jitterPercent))) //nolint:gosec // this does not need to be sercure.
 346โ”‚ 	// Double and clamp for next time.
 347โ”‚ 	last *= 2
 348โ”‚ 	if last > shutdownPollIntervalMax {
 349โ”‚ 		last = shutdownPollIntervalMax
 350โ”‚ 	}
 351โ”‚ 
 352โ”‚ 	*p = pollInterval(last)
 353โ”‚ 
 354โ”‚ 	return interval
 355โ”‚ }
โ”€โ”€โ”€โ”€โ•ฏ

ยท ยท ยท

๐Ÿก Home

FAQs

Privacy Policy

Terms & Conditions

Official Gemlog

info@source.community

ยฉ 2024 source.community