This repository has been archived on 2023-05-01. You can view files and clone it, but cannot push or open issues or pull requests.
gus/gemini/serve.go

123 lines
2.5 KiB
Go

package gemini
import (
"bufio"
"context"
"crypto/tls"
"errors"
"io"
"net"
"strconv"
"strings"
"tildegit.org/tjp/gus"
"tildegit.org/tjp/gus/internal"
"tildegit.org/tjp/gus/logging"
)
type titanRequestBodyKey struct{}
// TitanRequestBody is the key set in a handler's context for titan requests.
//
// When this key is present in the context (request.URL.Scheme will be "titan"), the
// corresponding value is a *bufio.Reader from which the request body can be read.
var TitanRequestBody = titanRequestBodyKey{}
type server struct {
internal.Server
handler gus.Handler
}
func (s server) Protocol() string { return "GEMINI" }
// NewServer builds a gemini server.
func NewServer(
ctx context.Context,
hostname string,
network string,
address string,
handler gus.Handler,
errorLog logging.Logger,
tlsConfig *tls.Config,
) (gus.Server, error) {
s := &server{handler: handler}
if strings.IndexByte(hostname, ':') < 0 {
hostname = net.JoinHostPort(hostname, "1965")
}
internalServer, err := internal.NewServer(ctx, hostname, network, address, errorLog, s.handleConn)
if err != nil {
return nil, err
}
s.Server = internalServer
s.Listener = tls.NewListener(s.Listener, tlsConfig)
return s, nil
}
func (s *server) handleConn(conn net.Conn) {
buf := bufio.NewReader(conn)
var response *gus.Response
request, err := ParseRequest(buf)
if err != nil {
response = BadRequest(err.Error())
} else {
request.Server = s
request.RemoteAddr = conn.RemoteAddr()
if tlsconn, ok := conn.(*tls.Conn); ok {
state := tlsconn.ConnectionState()
request.TLSState = &state
}
ctx := s.Ctx
if request.Scheme == "titan" {
len, err := sizeParam(request.Path)
if err == nil {
ctx = context.WithValue(
ctx,
TitanRequestBody,
io.LimitReader(buf, int64(len)),
)
}
}
/*
defer func() {
if r := recover(); r != nil {
err := fmt.Errorf("%s", r)
_ = s.LogError("msg", "panic in handler", "err", err)
_, _ = io.Copy(conn, NewResponseReader(Failure(err)))
}
}()
*/
response = s.handler(ctx, request)
if response == nil {
response = NotFound("Resource does not exist.")
}
}
defer response.Close()
_, _ = io.Copy(conn, NewResponseReader(response))
}
func sizeParam(path string) (int, error) {
_, rest, found := strings.Cut(path, ";")
if !found {
return 0, errors.New("no params in path")
}
for _, piece := range strings.Split(rest, ";") {
key, val, _ := strings.Cut(piece, "=")
if key == "size" {
return strconv.Atoi(val)
}
}
return 0, errors.New("no size param found")
}