diff options
Diffstat (limited to 'spartan/serve.go')
-rw-r--r-- | spartan/serve.go | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/spartan/serve.go b/spartan/serve.go new file mode 100644 index 0000000..677d76c --- /dev/null +++ b/spartan/serve.go @@ -0,0 +1,95 @@ +package spartan + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "net" + "strings" + + "tildegit.org/tjp/gus" + "tildegit.org/tjp/gus/internal" + "tildegit.org/tjp/gus/logging" +) + +type spartanRequestBodyKey struct{} +type spartanRequestBodyLenKey struct{} + +// SpartanRequestBody is the key set in a handler's context for spartan request bodies. +// +// The corresponding value is a *bufio.Reader from which the request body can be read. +var SpartanRequestBody = spartanRequestBodyKey{} + +// SpartanRequestBodyLen is the key set in a handler's context for the content-length of the request. +// +// The corresponding value is an int. +var SpartanRequestBodyLen = spartanRequestBodyLenKey{} + +type spartanServer struct { + internal.Server + handler gus.Handler +} + +func (ss spartanServer) Protocol() string { return "SPARTAN" } + +// NewServer builds a spartan server. +func NewServer( + ctx context.Context, + hostname string, + network string, + address string, + handler gus.Handler, + errLog logging.Logger, +) (gus.Server, error) { + ss := &spartanServer{handler: handler} + + if strings.IndexByte(hostname, ':') < 0 { + hostname = net.JoinHostPort(hostname, "300") + } + + var err error + ss.Server, err = internal.NewServer(ctx, hostname, network, address, errLog, ss.handleConn) + if err != nil { + return nil, err + } + + return ss, nil +} + +func (ss *spartanServer) handleConn(conn net.Conn) { + buf := bufio.NewReader(conn) + + var response *gus.Response + request, clen, err := ParseRequest(buf) + if err != nil { + response = ClientError(err) + } else { + request.Server = ss + request.RemoteAddr = conn.RemoteAddr() + + var body *bufio.Reader = nil + if clen > 0 { + body = bufio.NewReader(io.LimitReader(buf, int64(clen))) + } + ctx := context.WithValue(ss.Ctx, SpartanRequestBody, body) + ctx = context.WithValue(ctx, SpartanRequestBodyLen, clen) + + defer func() { + if r := recover(); r != nil { + err := fmt.Errorf("%s", r) + _ = ss.LogError("msg", "panic in handler", "err", err) + rdr := NewResponseReader(ServerError(errors.New("Server error"))) + _, _ = io.Copy(conn, rdr) + } + }() + response = ss.handler.Handle(ctx, request) + if response == nil { + response = ClientError(errors.New("Resource does not exist.")) + } + } + + defer response.Close() + _, _ = io.Copy(conn, NewResponseReader(response)) +} |