70 lines
1.8 KiB
Go
70 lines
1.8 KiB
Go
// package scgi contains a minimal gemini SCGI framework
|
|
package scgi
|
|
|
|
import (
|
|
"net"
|
|
"bufio"
|
|
"strings"
|
|
"strconv"
|
|
"log"
|
|
)
|
|
|
|
// SCGIHeaders is a map representing the headers of an SCGI request.
|
|
type SCGIHeaders map[string]string
|
|
|
|
|
|
// HandlerFunc is the type for a function that will handle an SCGI request
|
|
type HandlerFunc func(net.Conn, SCGIHeaders) error
|
|
|
|
|
|
// handle gets an SCGI request, parses it, and then lets fn handle the response
|
|
func handle(c net.Conn, fn HandlerFunc) {
|
|
defer c.Close() // Connection has to be closed for gemini
|
|
reader := bufio.NewReader(c)
|
|
hl, err := reader.ReadString(':') // SCGI Spec: First bytes until a ':' represent the header length
|
|
if err != nil {
|
|
c.Write([]byte("42" + err.Error()))
|
|
log.Print(err.Error())
|
|
return
|
|
}
|
|
l, err := strconv.Atoi(hl[:len(hl)-1]) // Strip the trailing :
|
|
if err != nil {
|
|
c.Write([]byte("42" + err.Error()))
|
|
log.Print(err.Error())
|
|
return
|
|
}
|
|
buf := make([]byte, l)
|
|
_, err = reader.Read(buf)
|
|
if err != nil {
|
|
c.Write([]byte("42" + err.Error()))
|
|
log.Print(err.Error())
|
|
return
|
|
}
|
|
headerString := string(buf[:l])
|
|
headerData := strings.Split(headerString, "\x00") // split on NULLs
|
|
header := make(SCGIHeaders)
|
|
// convert header data (a slice) to a map
|
|
for i := 0; i < len(headerData); i +=2 {
|
|
if headerData[i] != "" {
|
|
header[headerData[i]] = headerData[i+1]
|
|
}
|
|
}
|
|
err = fn(c, header)
|
|
if err != nil {
|
|
log.Print(err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
// Serve runs a simple SCGI server on listener, handling requests with fn
|
|
func Serve(listener net.Listener, fn HandlerFunc) error {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
|
|
go handle(conn, fn)
|
|
}
|
|
}
|