// 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) } }