summaryrefslogtreecommitdiff
path: root/gemini/serve.go
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-01-09 16:40:24 -0700
committertjpcc <tjp@ctrl-c.club>2023-01-09 16:40:24 -0700
commitff05d62013906f3086b452bfeda3e0d5b9b7a541 (patch)
tree3be29de0b1bc7c273041c6d89b71ca447c940556 /gemini/serve.go
Initial commit.
some basics: - minimal README - some TODOs - server and request handler framework - contribs: file serving, request logging - server examples - CI setup
Diffstat (limited to 'gemini/serve.go')
-rw-r--r--gemini/serve.go89
1 files changed, 89 insertions, 0 deletions
diff --git a/gemini/serve.go b/gemini/serve.go
new file mode 100644
index 0000000..d439472
--- /dev/null
+++ b/gemini/serve.go
@@ -0,0 +1,89 @@
+package gemini
+
+import (
+ "context"
+ "crypto/tls"
+ "io"
+ "net"
+ "sync"
+)
+
+type Server struct {
+ ctx context.Context
+ cancel context.CancelFunc
+ wg *sync.WaitGroup
+ listener net.Listener
+ handler Handler
+}
+
+func NewServer(ctx context.Context, tlsConfig *tls.Config, listener net.Listener, handler Handler) *Server {
+ ctx, cancel := context.WithCancel(ctx)
+
+ s := &Server{
+ ctx: ctx,
+ cancel: cancel,
+ wg: &sync.WaitGroup{},
+ listener: tls.NewListener(listener, tlsConfig),
+ handler: handler,
+ }
+ go s.propagateCancel()
+
+ return s
+}
+
+func (s *Server) Close() {
+ s.cancel()
+ s.wg.Wait()
+}
+
+func (s *Server) Serve() {
+ s.wg.Add(1)
+ defer s.wg.Done()
+
+ for {
+ conn, err := s.listener.Accept()
+ if err != nil {
+ return
+ }
+
+ s.wg.Add(1)
+ go s.handleConn(conn)
+ }
+}
+
+func (s *Server) handleConn(conn net.Conn) {
+ defer s.wg.Done()
+ defer conn.Close()
+
+ req, err := ParseRequest(conn)
+ if tlsconn, ok := conn.(*tls.Conn); req != nil && ok {
+ state := tlsconn.ConnectionState()
+ req.TLSState = &state
+ }
+
+ var resp *Response
+ if err == nil {
+ resp = s.handler(s.ctx, req)
+ } else {
+ resp = BadRequest(err.Error())
+ }
+ defer resp.Close()
+
+ _, _ = io.Copy(conn, resp)
+}
+
+func (s *Server) propagateCancel() {
+ go func() {
+ <-s.ctx.Done()
+ _ = s.listener.Close()
+ }()
+}
+
+func (s *Server) closed() bool {
+ select {
+ case <-s.ctx.Done():
+ return true
+ default:
+ return false
+ }
+}