๐พ Archived View for source.community โบ ckaznocha โบ gemini โบ blob โบ main โบ server.go captured on 2024-05-10 at 11:03:48. Gemini links have been rewritten to link to archived content
โฌ ๏ธ Previous capture (2024-02-05)
-=-=-=-=-=-=-
. ,-. ,-. . . ,-. ,-. ,-. ,-. ,-. ,-,-. ,-,-. . . ,-. . |- . . `-. | | | | | | |-' | | | | | | | | | | | | | | | | | `-' `-' `-^ ' `-' `-' :: `-' `-' ' ' ' ' ' ' `-^ ' ' ' `' `-| /| `-'
git clone https://source.community/ckaznocha/gemini.git
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โ } โโโโโฏ
ยท ยท ยท
ยฉ 2024 source.community