Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various fixes #4

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ type Dialer interface {
Dial() (net.Conn, error)
}

type TCPDialer struct {
type NetDialer struct {
net string
addr string
}

func (d TCPDialer) Dial() (net.Conn, error) {
return net.Dial("tcp", d.addr)
func (d NetDialer) Dial() (net.Conn, error) {
return net.Dial(d.net, d.addr)
}

// StdinDialer managers an app as a child process, creating a socket and passing it through stdin.
Expand All @@ -40,7 +41,7 @@ func (sd *StdinDialer) Dial() (net.Conn, error) {
}

func (sd *StdinDialer) Start() error {
// Create a socket.
// Create a socket.
// We'll use the high-level net API, creating a listener that does all sorts
// of socket stuff, getting its file, and passing that (really just for its FD)
// to the child process.
Expand Down
18 changes: 10 additions & 8 deletions gofcgisrv.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ type FCGIRequester struct {
MaxRequests int
}

// NewServer creates a server that will attempt to connect to the application at the given address over TCP.
func NewFCGI(applicationAddr string) *FCGIRequester {
// NewServer creates a server that will attempt to connect to the application at the given address
// over the specified network (typically 'tcp' or 'unix')
func NewFCGI(net, applicationAddr string) *FCGIRequester {
s := &FCGIRequester{}
s.dialer = TCPDialer{addr: applicationAddr}
s.dialer = NetDialer{net: net, addr: applicationAddr}
s.MaxConns = 1
s.MaxRequests = 1
s.reqCond = sync.NewCond(&s.reqLock)
Expand Down Expand Up @@ -124,7 +125,7 @@ func (s *FCGIRequester) GetValues() error {
// env should be a slice of name=value pairs. It blocks until the application has finished.
func (s *FCGIRequester) Request(env []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
// Get a request. We may have to wait for one to free up.
r, err := s.newRequest()
r, err := s.newRequest(stdout, stderr)
if err != nil {
return err
}
Expand All @@ -142,8 +143,6 @@ func (s *FCGIRequester) Request(env []string, stdin io.Reader, stdout io.Writer,
}
params.Close()

r.Stdout = stdout
r.Stderr = stderr
// Send stdin.
reqStdin := newStreamWriter(r.conn.netconn, fcgiStdin, r.id)
io.Copy(reqStdin, stdin)
Expand Down Expand Up @@ -173,7 +172,7 @@ func (s *FCGIRequester) numRequests() int {
return n
}

func (s *FCGIRequester) newRequest() (*request, error) {
func (s *FCGIRequester) newRequest(stdout io.Writer, stderr io.Writer) (*request, error) {
// We may have to wait for one to become available
s.reqLock.Lock()
defer s.reqLock.Unlock()
Expand All @@ -186,8 +185,11 @@ func (s *FCGIRequester) newRequest() (*request, error) {
return nil, err
}
conn := newConn(s, netconn)
r := conn.newRequest()
r.Stdout = stdout
r.Stderr = stderr
go conn.Run()
return conn.newRequest(), nil
return r, nil
}

func (s *FCGIRequester) releaseRequest(r *request) {
Expand Down
102 changes: 77 additions & 25 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"io"
"net"
"net/http"
"net/textproto"
"strconv"
"strings"
)

Expand Down Expand Up @@ -41,6 +41,7 @@ func HTTPEnv(start []string, r *http.Request) []string {
appendEnv("SERVER_PROTOCOL", "HTTP/1.1")
appendEnv("GATEWAY_INTERFACE", "CGI/1.1")
appendEnv("REQUEST_URI", r.URL.String())
appendEnv("HTTP_HOST=", r.Host)

host, port, err := net.SplitHostPort(r.Host)
if err != nil {
Expand All @@ -57,7 +58,7 @@ func HTTPEnv(start []string, r *http.Request) []string {

for key := range r.Header {
upper := strings.ToUpper(key)
cgikey := "HTTP_" + strings.Replace(upper, "-", "_", 1)
cgikey := "HTTP_" + strings.Replace(upper, "-", "_", -1)
appendEnv(cgikey, r.Header.Get(key))
}
return env
Expand Down Expand Up @@ -104,32 +105,83 @@ func ServeHTTP(s Requester, env []string, w http.ResponseWriter, r *http.Request

// ProcessResponse adds any returned header data to the response header and sends the rest
// to the response body.
func ProcessResponse(stdout io.Reader, w http.ResponseWriter, r *http.Request) error {
bufReader := bufio.NewReader(stdout)
mimeReader := textproto.NewReader(bufReader)
hdr, err := mimeReader.ReadMIMEHeader()
if err != nil {
// We got nothing! Assume there is an error. Should be more robust.
return err
}
if err == nil {
for k, vals := range hdr {
for _, v := range vals {
w.Header().Add(k, v)
func ProcessResponse(stdoutRead io.Reader, rw http.ResponseWriter, req *http.Request) {
linebody := bufio.NewReaderSize(stdoutRead, 1024)
headers := make(http.Header)
statusCode := 0
for {
line, isPrefix, err := linebody.ReadLine()
if isPrefix {
rw.WriteHeader(http.StatusInternalServerError)
logger.Printf("fcgi: long header line from subprocess.")
return
}
if err == io.EOF {
break
}
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
logger.Printf("fcgi: error reading headers: %v", err)
return
}
if len(line) == 0 {
break
}
parts := strings.SplitN(string(line), ":", 2)
if len(parts) < 2 {
logger.Printf("fcgi: bogus header line: %s", string(line))
continue
}
header, val := parts[0], parts[1]
header = strings.TrimSpace(header)
val = strings.TrimSpace(val)
switch {
case header == "Status":
if len(val) < 3 {
logger.Printf("fcgi: bogus status (short): %q", val)
return
}
code, err := strconv.Atoi(val[0:3])
if err != nil {
logger.Printf("fcgi: bogus status: %q", val)
logger.Printf("fcgi: line was %q", line)
return
}
statusCode = code
default:
headers.Add(header, val)
}
}

/* // TODO : handle internal redirect ?
if loc := headers.Get("Location"); loc != "" {
if strings.HasPrefix(loc, "/") && h.PathLocationHandler != nil {
h.handleInternalRedirect(rw, req, loc)
return
}
if statusCode == 0 {
statusCode = http.StatusFound
}
}
statusCode := http.StatusOK
if status := hdr.Get("Status"); status != "" {
delete(w.Header(), "Status")
// Parse the status code
var code int
if n, _ := fmt.Sscanf(status, "%d", &code); n == 1 {
statusCode = int(code)
*/

if statusCode == 0 {
statusCode = http.StatusOK
}

// Copy headers to rw's headers, after we've decided not to
// go into handleInternalRedirect, which won't want its rw
// headers to have been touched.
for k, vv := range headers {
for _, v := range vv {
rw.Header().Add(k, v)
}
}
// Are there other fields we need to rewrite? Probably!
w.WriteHeader(statusCode)
io.Copy(w, bufReader)
return nil

rw.WriteHeader(statusCode)

_, err := io.Copy(rw, linebody)
if err != nil {
logger.Printf("cgi: copy error: %v", err)
}
}
10 changes: 5 additions & 5 deletions protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type requestId uint16
type recordType uint8

const fcgiVersion byte = 1
const fcgiVersion uint8 = 1

// Request types.
const (
Expand Down Expand Up @@ -50,7 +50,7 @@ const (
fcgiMpxsConns = "FCGI_MPXS_CONNS"
)

var pad [7]byte
var pad [255]byte

type record struct {
Type recordType
Expand Down Expand Up @@ -90,7 +90,7 @@ func _read(r io.Reader, data interface{}) error {

func readRecord(r io.Reader) (record, error) {
var rec record
var version byte
var version uint8
var clength uint16
var plength uint8
if err := _read(r, &version); err != nil {
Expand All @@ -116,12 +116,12 @@ func readRecord(r io.Reader) (record, error) {
}
if clength != 0 {
rec.Content = make([]byte, clength)
if _, err := r.Read(rec.Content); err != nil {
if _, err := io.ReadFull(r, rec.Content); err != nil {
return rec, err
}
}
if plength != 0 {
if _, err := r.Read(pad[:plength]); err != nil {
if _, err := io.ReadFull(r, pad[:plength]); err != nil {
return rec, err
}
}
Expand Down
2 changes: 1 addition & 1 deletion py_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestPyServer(t *testing.T) {
defer cmd.Process.Kill()

waitForConn("127.0.0.1:9001", time.Second)
s := NewFCGI("127.0.0.1:9001")
s := NewFCGI("tcp", "127.0.0.1:9001")
testRequester(t, httpTestData{
name: "py fastcgi",
f: s,
Expand Down
2 changes: 1 addition & 1 deletion server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestFCGI(t *testing.T) {
defer l.Close()

// Now start an http server.
s := NewFCGI(addr)
s := NewFCGI("tcp", addr)
http.Handle("/", s)
server := httptest.NewServer(nil)
defer server.Close()
Expand Down