@@ 2,20 2,21 @@ package scgi
import (
"net"
-// "time"
"io"
- "io/ioutil"
"fmt"
"strconv"
"bytes"
"net/http"
-// "log"
+ "net/http/cgi"
)
+// Listener implements net.Listener, converting incoming SCGI requests into
+// a connection which presents a http.Request if read.
type Listener struct {
net.Listener
}
+// NewListener wraps a net.Listener.
func NewListener(l net.Listener) *Listener {
return &Listener{Listener: l}
}
@@ 29,19 30,13 @@ func (l *Listener) Accept() (net.Conn, error) {
return NewConn(c)
}
-//func (l *Listener) Close() error {
-// return l.Listener.Close()
-//}
-//
-//func (l *Listener) Addr() net.Addr {
-// return l.Listener.Addr()
-//}
-
+// Conn converts an incoming SCGI-connection into a connection presenting a http.Request.
type Conn struct {
buf io.Reader
net.Conn
}
+// netstring reads a netstring from an io.Reader.
func netstring(r io.Reader) ([]byte, error) {
lbuf := []byte{}
x := make([]byte, 1)
@@ 81,110 76,78 @@ func netstring(r io.Reader) ([]byte, error) {
return buf, nil
}
-func headers(c net.Conn) (http.Header, string, string, int64, error) {
- buf, err := netstring(c)
+// headerMap reads netstring formatted headers from a reader and converts them
+// to a map usable with cgi.RequestFromMap. Basic sanity checks for the presence
+// of the SCGI and CONTENT_LENGTH headers are performed.
+func headerMap(r io.Reader) (map[string]string, error) {
+ buf, err := netstring(r)
if err != nil {
- return nil, "", "", 0, fmt.Errorf("error reading netstring: %w", err)
+ return nil, fmt.Errorf("error reading netstring: %w", err)
}
fs := bytes.Split(buf, []byte{0x0})
fs = fs[0:len(fs)-1] // remove final field due to zero terminated strings
if len(fs) % 2 != 0 {
- return nil, "", "", 0, fmt.Errorf("header fields count not even: %v", len(fs))
+ return nil, fmt.Errorf("header fields count not even: %v", len(fs))
}
- hs := make(http.Header)
- var url string
- var method string
- contentLength := int64(-1)
- scgi := false
+ headers := make(map[string]string)
for i := 0; i < len(fs); i+=2 {
key := string(fs[i])
value := string(fs[i+1])
- switch key {
- case "REQUEST_URI":
- url = value
- case "REQUEST_METHOD":
- method = value
- case "SCGI":
- scgi = true
- case "CONTENT_LENGTH":
- contentLength, err = strconv.ParseInt(value, 10, 64)
- if err != nil {
- return nil, "", "", 0, fmt.Errorf("error reading content length header: %w", err)
- }
- default:
- hs.Add(key, value)
- }
+ headers[key] = value
}
- if scgi == false {
- return nil, "", "", 0, fmt.Errorf("SCGI header must be set to 1")
+ if s, ok := headers["SCGI"]; !ok || s != "1" {
+ return nil, fmt.Errorf("SCGI header has to be set and 1")
}
- if contentLength == -1 {
- return nil, "", "", 0, fmt.Errorf("CONTENT_LENGTH header must be set")
+ if _, ok := headers["CONTENT_LENGTH"]; !ok {
+ return nil, fmt.Errorf("CONTENT_LENGTH not set")
}
- return hs, method, url, contentLength, nil
+ return headers, nil
}
-func NewConn(c net.Conn) (*Conn, error) {
- hs, method, url, contentLength, err := headers(c)
+// Request reads an SCGI request from an io.Reader, returning a http.Request.
+func Request(r io.Reader) (*http.Request, error) {
+ headers, err := headerMap(r)
if err != nil {
return nil, err
}
- var body bytes.Buffer
- body.ReadFrom(io.LimitReader(c, int64(contentLength)))
+ req, err := cgi.RequestFromMap(headers)
+ if err != nil {
+ return nil, err
+ }
- req, err := http.NewRequest(method, url, nil)
+ if req.ContentLength > 0 {
+ req.Body = io.NopCloser(io.LimitReader(r, req.ContentLength))
+ }
+
+ return req, nil
+}
+
+// NewConn wraps a net.Conn, converting SCGI requests to http.Requests.
+// Note that the full request is read into a buffer which is then served
+// from.
+func NewConn(c net.Conn) (*Conn, error) {
+ req, err := Request(c)
if err != nil {
return nil, err
}
- req.Header = hs
- req.Body = ioutil.NopCloser(&body)
- req.TransferEncoding = append(req.TransferEncoding, "identity")
- var reqBuf bytes.Buffer
- err = req.Write(&reqBuf)
+ var buf bytes.Buffer
+ err = req.Write(&buf)
if err != nil {
return nil, err
}
- return &Conn{Conn: c, buf: &reqBuf}, nil
+ return &Conn{Conn: c, buf: &buf}, nil
}
func (c *Conn) Read(b []byte) (n int, err error) {
return c.buf.Read(b)
}
-
-//func (c *Conn) Write(b []byte) (n int, err error) {
-// return c.Conn.Write(b)
-//}
-//
-//func (c *Conn) Close() error {
-// return c.Conn.Close()
-//}
-//
-//func (c *Conn) LocalAddr() net.Addr {
-// return c.Conn.LocalAddr()
-//}
-//
-//func (c *Conn) RemoteAddr() net.Addr {
-// return c.Conn.RemoteAddr()
-//}
-//
-//func (c *Conn) SetDeadline(t time.Time) error {
-// return c.Conn.SetDeadline(t)
-//}
-//
-//func (c *Conn) SetReadDeadline(t time.Time) error {
-// return c.Conn.SetReadDeadline(t)
-//}
-//
-//func (c *Conn) SetWriteDeadline(t time.Time) error {
-// return c.Conn.SetWriteDeadline(t)
-//}