Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

cleaned up. passing tests.

  • Loading branch information...
commit 981a8d499b6d41f41c6d88cfb680abbaa1ae1fab 1 parent b9c87b2
Jesse Dailey authored
View
2  fcgi.go
@@ -200,7 +200,7 @@ func (self *fcgiHeader) readContent(r io.Reader) (b []byte, err os.Error) {
}
// so we dont have to allocate new ones all the time, these are always zero, and we write slices of it for padding
-var paddingSource = make([]byte, 7) // packets are padded to 8-bytes, so if we are padding more than 7 its a whole wasted packet
+var paddingSource = make([]byte, 7) // packets are padded to 8-bytes, so if we are padding more than 7 its wasted
func (self *fcgiHeader) writePadding(w io.Writer) os.Error {
p := self.PaddingLength
if p > 0 {
View
80 fcgi_test.go
@@ -68,7 +68,7 @@ var tests = []testRecord{
},
}
// each test is repeated N times
-var repeatCount = 3 // should be equal to len(responders) inside the handler, so that we test every responder in the list equally
+var repeatCount = 6 // should be an even multiple of len(responders) inside the handler, so that we test every responder equally
func registerFcgiMux() {
// for hello world test
@@ -89,16 +89,27 @@ func registerFcgiMux() {
func registerWebMux() {
// define the muxer for the http server to use
- webMux.Handle("/", fcgi.Handler([]string{
- "tcp://127.0.0.1:7134", // launched above
- "unix:///tmp/fcgi_test.sock", // launched above
- "exec:///opt/go/src/pkg/http/listener_test_exec.out", // will be ForkExec'd by the Handler (right now)
- }))
+ // (all requests go to the pool of listeners)
+ if wd, err := os.Getwd(); err == nil {
+ webMux.Handle("/", fcgi.Handler([]string{
+ "tcp://127.0.0.1:7134",
+ "unix:///tmp/fcgi_test.sock",
+ "exec://" + wd + "/listener_test_exec.out", // will be ForkExec'd by the Handler (right now)
+ }))
+ } else {
+ webMux.Handle("/", fcgi.Handler([]string{
+ "tcp://127.0.0.1:7134",
+ "unix:///tmp/fcgi_test.sock",
+ }))
+ }
}
+// Build the test executable for this part.
+// gotest: make listener_test_exec.out
func TestStartTcpListener(t *testing.T) {
once.Do(registerFcgiMux)
- if tcplisten, err := fcgi.Listen("tcp", "0.0.0.0:7134"); err == nil {
+ var err os.Error
+ if tcplisten, err = fcgi.Listen("tcp", "0.0.0.0:7134"); err == nil {
go http.Serve(tcplisten, fcgiMux)
} else {
t.Fatal(err)
@@ -107,6 +118,21 @@ func TestStartTcpListener(t *testing.T) {
func TestStartUnixListener(t *testing.T) {
once.Do(registerFcgiMux)
+ var err os.Error
+ if unixlisten, err = fcgi.Listen("unix", "/tmp/fcgi_test.sock"); err == nil {
+ go http.Serve(unixlisten, fcgiMux)
+ } else {
+ t.Fatal(err)
+ }
+}
+func TestStopUnixListener(t *testing.T) {
+ t.Log("Stopping unix", unixlisten)
+ if err := unixlisten.Close(); err != nil {
+ t.Error(err)
+ }
+}
+func TestStartUnixListenerAgain(t *testing.T) {
+ once.Do(registerFcgiMux)
if unixlisten, err := fcgi.Listen("unix", "/tmp/fcgi_test.sock"); err == nil {
go http.Serve(unixlisten, fcgiMux)
} else {
@@ -116,7 +142,8 @@ func TestStartUnixListener(t *testing.T) {
func TestStartWebServer(t *testing.T) {
once.Do(registerWebMux)
- if weblisten, err := net.Listen("tcp", ":8181"); err == nil {
+ var err os.Error
+ if weblisten, err = net.Listen("tcp", ":8181"); err == nil {
go http.Serve(weblisten, webMux)
} else {
t.Fatal(err)
@@ -128,25 +155,23 @@ func TestRunTests(t *testing.T) {
for j := 0; j < repeatCount; j++ {
if response, _, err := http.Get(test.URL); err == nil {
if response.StatusCode != test.StatusCode {
- t.Error(test.URL, "Response had wrong status code:", response.StatusCode)
+ t.Error(test.URL, j, "Response had wrong status code:", response.StatusCode)
}
if len(test.BodyPrefix) > 0 {
prefix := make([]byte, len(test.BodyPrefix))
if n, err := response.Body.Read(prefix); err == nil {
- if n < len(prefix) {
- t.Error(test.URL, "Short read")
- }
- if string(prefix) != test.BodyPrefix {
- t.Error(test.URL, "Bad body, expected prefix:", test.BodyPrefix, "got:", string(prefix))
+ p := string(prefix[0:n])
+ if p != test.BodyPrefix {
+ t.Error(test.URL, j, "Bad body, expected prefix:", test.BodyPrefix, "got:", p)
}
} else {
- t.Error(test.URL, "Error reading response.Body:", err)
+ t.Error(test.URL, j, "Error reading response.Body:", err)
}
}
if test.Headers != nil {
for _, hdr := range test.Headers {
if v := response.GetHeader(hdr.Key); v != hdr.Val {
- t.Error(test.URL, "Header value in response:", strconv.Quote(v), "did not match", strconv.Quote(hdr.Val))
+ t.Error(test.URL, j, "Header value in response:", strconv.Quote(v), "did not match", strconv.Quote(hdr.Val))
}
}
}
@@ -157,25 +182,28 @@ func TestRunTests(t *testing.T) {
}
}
-func TestRemoveTmpFile(t *testing.T) { os.Remove("/tmp/fcgi_test.html") }
+func TestRemoveTmpFile(t *testing.T) {
+ os.Remove("/tmp/fcgi_test.html")
+ os.Remove("listener_test_exec.out")
+}
-func TestStopWebServer(t *testing.T) {
- if weblisten == nil {
- t.Error("weblisten is already nil?")
- }
- if err := weblisten.Close(); err != nil {
+func TestStopUnixListenerAgain(t *testing.T) {
+ t.Log("Stopping unix", unixlisten)
+ if err := unixlisten.Close(); err != nil {
t.Error(err)
}
}
-func TestStopTcpListener(t *testing.T) {
- if err := tcplisten.Close(); err != nil {
+func TestStopWebServer(t *testing.T) {
+ t.Log("Stopping web", weblisten)
+ if err := weblisten.Close(); err != nil {
t.Error(err)
}
}
-func TestStopUnixListener(t *testing.T) {
- if err := unixlisten.Close(); err != nil {
+func TestStopTcpListener(t *testing.T) {
+ t.Log("Stopping TCP", tcplisten)
+ if err := tcplisten.Close(); err != nil {
t.Error(err)
}
}
View
78 handler.go
@@ -20,7 +20,6 @@ import (
"path"
)
-
// Handler returns an http.Handler that will dispatch requests to FastCGI Responders
// addrs are a list of addresses that include a prefix for what kind of network they are on
// e.g. tcp://127.0.0.1:1234, unix:///tmp/some.sock, exec:///usr/bin/php
@@ -37,18 +36,18 @@ func Handler(addrs []string) http.Handler {
}
switch strings.ToLower(s[0]) {
case "tcp":
- if responders[i], err = fcgiDialTcp(s[1]); err != nil {
+ if responders[i], err = fcgiDialTcp(s[1]); err != nil || responders[i] == nil {
Log("Handler: failed to connect to tcp responder:", s[1], err)
// on non-fatal (to the whole Handler) errors just make a note for a little later
e += 1
}
case "unix":
- if responders[i], err = fcgiDialUnix(s[1]); err != nil {
+ if responders[i], err = fcgiDialUnix(s[1]); err != nil || responders[i] == nil {
Log("Handler: failed to connect to responder on unix socket:", s[1], err)
e += 1
}
case "exec":
- if responders[i], err = fcgiDialExec(s[1]); err != nil {
+ if responders[i], err = fcgiDialExec(s[1]); err != nil || responders[i] == nil {
Log("Handler: failed to exec fcgi responder program:", s[1], err)
e += 1
}
@@ -134,6 +133,7 @@ func errorHandler(status int, msg string) http.Handler {
// wsConn is the webserver-side of a connection to a FastCGI Responder
type wsConn struct {
addr string
+ pid int // only meaningful when addr is exec://...
conn io.ReadWriteCloser
buffers []*closeBuffer // a closable bytes.Buffer
signals []chan bool // used to signal ReadResponse
@@ -141,11 +141,14 @@ type wsConn struct {
}
func newWsConn(conn io.ReadWriteCloser, addr string) *wsConn {
+ if conn == nil {
+ return nil
+ }
self := &wsConn{
addr: addr,
conn: conn,
- buffers: make([]*closeBuffer, 65535),
- signals: make([]chan bool, 65535),
+ buffers: make([]*closeBuffer, 256),
+ signals: make([]chan bool, 256),
nextId: 1,
}
for i, _ := range self.signals {
@@ -164,7 +167,7 @@ func fcgiDialTcp(addr string) (self *wsConn, err os.Error) {
return self, err
}
-// fcgiDialTcp connects to a FastCGI Responder over TCP, and returns the wsConn for the connection
+// fcgiDialUnix connects to a FastCGI Responder over a Unix socket, and returns the wsConn for the connection
func fcgiDialUnix(addr string) (self *wsConn, err os.Error) {
if conn, err := dialUnixAddr(addr); err == nil {
self = newWsConn(conn, addr)
@@ -174,32 +177,30 @@ func fcgiDialUnix(addr string) (self *wsConn, err os.Error) {
// fcgiDialExec will ForkExec a new process, and returns a wsConn that connects to its stdin
func fcgiDialExec(binpath string) (self *wsConn, err os.Error) {
- // TODO: in the future, each binpath should map to a list of launched processes, so they can be reused
-
- // to do the basic operation, lighttpd opens a unix socket, calls listen() on it, then forks and gives that fd to the child process as their stdin
+ listenBacklog := 1024
+ socketPath := "/tmp/fcgiauto.sock" // should be computed
+ if err := os.Remove(socketPath); err != nil {
+ switch err.String() {
+ case "remove " + socketPath + ": no such file or directory": // ignore, it will be created
+ default:
+ return nil, err
+ }
+ }
// we can almost use UnixListener to do this except it doesn't expose the fd it's listening on
Log("Handler: trying to ForkExec a new responder.")
- Log("Handler: creating Socket")
if fd, errno := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0); errno == 0 {
- Log("Handler: calling Setsockopt")
if errno := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); errno == 0 {
- Log("Handler: calling Bind")
- if errno := syscall.Bind(fd, &syscall.SockaddrUnix{Name: "/tmp/fcgiauto.sock"}); errno == 0 {
- Log("Handler: calling Listen")
- if errno := syscall.Listen(fd, 1024); errno == 0 {
+ if errno := syscall.Bind(fd, &syscall.SockaddrUnix{Name: socketPath}); errno == 0 {
+ if errno := syscall.Listen(fd, listenBacklog); errno == 0 {
dir, file := path.Split(binpath)
- Log("Handler: split path into", dir, file)
- Log("Handler: calling ForkExec")
- if _, errno := syscall.ForkExec(file, []string{}, []string{}, dir, []int{fd}); errno == 0 {
- Log("Handler: creating a client socket for us")
+ if pid, errno := syscall.ForkExec(file, []string{}, []string{}, dir, []int{fd}); errno == 0 {
if cfd, errno := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0); errno == 0 {
- Log("Handler: calling Getsockname(fd)")
if sa, errno := syscall.Getsockname(fd); errno == 0 {
- Log("Handler: sa = ", sa)
- Log("Handler: calling Connect()")
if errno := syscall.Connect(cfd, sa); errno == 0 {
Log("Handler: returning new wsConn connected to process", binpath)
- return newWsConn(os.NewFile(cfd, "exec://"+binpath), "exec://"+binpath), nil
+ ws := newWsConn(os.NewFile(cfd, "exec://"+binpath), "exec://"+binpath)
+ ws.pid = pid
+ return ws, nil
} else {
Log("Connect failed:", syscall.Errstr(errno))
return nil, os.NewError(fmt.Sprint("Connect failed:", syscall.Errstr(errno)))
@@ -249,7 +250,7 @@ func (self *wsConn) fcgiWrite(kind uint8, id uint16, data []byte) (n int, err os
}
// readAllPackets is a goroutine that reads everything from the connection
-// and dispatches responses when they are complete (FCGI_END_REQUEST) is recieved
+// and dispatches responses when they are complete (FCGI_END_REQUEST is recieved).
func (self *wsConn) readAllPackets() {
h := &fcgiHeader{}
for {
@@ -293,7 +294,7 @@ func (self *wsConn) readAllPackets() {
Log("wsConn: got END_REQUEST")
e := new(fcgiEndRequest)
readStruct(self.conn, e)
- Log("wsConn: appStatus ", e.AppStatus, " protocolStatus ", e.ProtocolStatus)
+ Log("wsConn: appStatus", e.AppStatus, "protocolStatus", e.ProtocolStatus)
buf := self.buffers[h.ReqId]
switch e.ProtocolStatus {
case FCGI_REQUEST_COMPLETE:
@@ -319,10 +320,20 @@ close:
self.Close()
}
-// Close closes the underlying connection to the FastCGI responder
-func (self *wsConn) Close() os.Error { return self.conn.Close() }
+// Close closes the underlying connection to the FastCGI responder.
+func (self *wsConn) Close() os.Error {
+ if self.pid > 0 {
+ // send the process a SIGTERM
+ Log("wsConn: sending child proc a SIGTERM")
+ syscall.Syscall(syscall.SYS_KILL, uintptr(self.pid), syscall.SIGTERM, 0)
+ if _, err := os.Wait(self.pid, 0); err != nil {
+ return err
+ }
+ }
+ return self.conn.Close()
+}
-// getNextReqId is an iterator that produces un-used request ids
+// getNextReqId is an iterator that produces un-used request ids.
func (self *wsConn) getNextReqId() (reqid uint16) {
start := self.nextId
for self.buffers[self.nextId] != nil {
@@ -337,7 +348,7 @@ func (self *wsConn) getNextReqId() (reqid uint16) {
return self.nextId
}
-// freeReqId marks a reqId as usable for another request on this connection
+// freeReqId marks a reqId as usable for another request on this connection.
func (self *wsConn) freeReqId(reqid uint16) {
self.buffers[reqid] = nil
self.nextId = reqid
@@ -421,22 +432,25 @@ func (self *wsConn) ReadResponse(reqid uint16, method string) (ret *http.Respons
return ret, err
}
-// closeBuffer is a closable buffer, after .Close(), .Write() returns EOF error
+// closeBuffer is a closable buffer, after .Close(), .Write() returns EOF error.
type closeBuffer struct {
bytes.Buffer
closed bool
}
+
// Reset() is the same as Truncate(0) but also re-opens the buffer if it was closed.
func (self *closeBuffer) Reset() {
self.closed = false
self.Buffer.Reset()
}
+
// Close causes any future writes to return EOF. Reads can continue until the buffer is empty.
func (self *closeBuffer) Close() os.Error {
self.closed = true
return nil
}
-// Write works like normal except that if the buffer has been closed, it returns (0, os.EOF)
+
+// Write works like normal except that if the buffer has been closed, it returns (0, os.EOF).
func (self *closeBuffer) Write(p []byte) (n int, err os.Error) {
if self.closed {
return 0, os.EOF
View
53 listener.go
@@ -17,6 +17,7 @@ import (
"syscall"
"strconv"
"log"
+ "fmt"
)
// fcgiRequest holds the state for an in-progress request,
@@ -163,7 +164,7 @@ type fcgiListener struct {
// For tcp, laddr is like "127.0.0.1:1234".
// For unix, laddr is the absolute path to a socket.
// For exec, laddr is ignored (input is read from stdin).
-func Listen(net string, laddr string) (net.Listener, os.Error) {
+func Listen(net string, laddr string) (*fcgiListener, os.Error) {
switch net {
case "tcp", "tcp4", "tcp6":
return ListenTCP(laddr)
@@ -172,12 +173,12 @@ func Listen(net string, laddr string) (net.Listener, os.Error) {
case "exec":
return ListenFD(FCGI_LISTENSOCK_FILENO)
}
- return nil, os.NewError("Invalid network type.")
+ return nil, os.NewError(fmt.Sprint("Invalid network type.", net))
}
// ListenTCP() creates a new fcgiListener on a tcp socket.
// listenAddress can be any resolvable local interface and port.
-func ListenTCP(listenAddress string) (net.Listener, os.Error) {
+func ListenTCP(listenAddress string) (*fcgiListener, os.Error) {
var err os.Error
if l, err := net.Listen("tcp", listenAddress); err == nil {
return listen(l)
@@ -187,29 +188,39 @@ func ListenTCP(listenAddress string) (net.Listener, os.Error) {
// ListenUnix creates a new fcgiListener on a unix socket.
// socketPath should be the absolute path to the socket file.
-func ListenUnix(socketPath string) (net.Listener, os.Error) {
+func ListenUnix(socketPath string) (*fcgiListener, os.Error) {
+ if err := os.Remove(socketPath); err != nil {
+ // there has to be a better way...
+ switch err.String() {
+ case "remove " + socketPath + ": no such file or directory":
+ default:
+ return nil, err
+ }
+ }
if l, err := net.Listen("unix", socketPath); err == nil {
- if l, err = listen(l); err == nil {
+ if ll, err := listen(l); err == nil {
log.Stderr("ListenUnix returning", l, nil)
- return l, nil
+ return ll, nil
} else {
return nil, err
}
- return l, err
} else {
return nil, err
}
- panic()
+ panic("ListenUnix should not fall-through")
}
// ListenFD creates a new fcgiListener on an already open socket.
// fd is the file descriptor of the open socket.
-func ListenFD(fd int) (net.Listener, os.Error) {
+func ListenFD(fd int) (*fcgiListener, os.Error) {
return listen(NewFDListener(fd))
}
// listen() is the private listener factory behind the different net types
-func listen(listener net.Listener) (net.Listener, os.Error) {
+func listen(listener net.Listener) (*fcgiListener, os.Error) {
+ if listener == nil {
+ return nil, os.NewError("listener cannot be nil")
+ }
self := &fcgiListener{
Listener: listener,
c: make(chan *rsConn),
@@ -321,16 +332,26 @@ func (self *fcgiListener) Accept() (net.Conn, os.Error) {
Log("Listener: Accept() releasing connection", c)
return net.Conn(c), nil
case err := <-self.err:
+ if err == nil {
+ Log("Listener: Accept() read a nil error, and a nil Conn")
+ return nil, os.NewError("Unknown error in Accept()")
+ }
return nil, err
}
- return nil, nil
+ panic("Accept should never fall through")
}
-// rsConn is the responder-side of a connection to the webserver, it looks like a net.Conn
+func (self *fcgiListener) Close() os.Error {
+ Log("Listener: Close()")
+ return self.Listener.Close()
+}
+
+
+// rsConn is the responder-side of a connection to the webserver, it looks like a net.Conn.
// It is created automatically by readAllPackets() and returned by fcgiListener.Accept() from inside http.Serve().
// It won't be created until a complete request has been buffered off a real socket.
// Read() here will read from that request buffer only, never a real socket.
-// - Its possible that in the future calls to Read() would block waiting for chunks of FCGI_STDIN,etc to arrive
+// - Its possible that in the future calls to Read() would be unbuffered and block waiting for chunks of FCGI_STDIN,etc to arrive.
// Write() will send FCGI_STDOUT records back to the web server.
type rsConn struct {
reqId uint16 // the request id to put in the headers of the output packets
@@ -426,6 +447,9 @@ func (self *rsConn) SetReadTimeout(nsec int64) os.Error {
func (self *rsConn) SetWriteTimeout(nsec int64) os.Error {
return nil
}
+func (self *rsConn) String() string {
+ return fmt.Sprint("{rsConn@", self.localAddr.String(), " reqId:", self.reqId, " buffered:", self.buf.Len(), "}")
+}
// FDListener is a net.Listener that uses syscall.Accept(fd)
// to accept new connections directly on an already open fd.
@@ -479,6 +503,9 @@ func (f FileConn) SetReadTimeout(ns int64) os.Error {
func (f FileConn) SetWriteTimeout(ns int64) os.Error {
return nil
}
+func (f FileConn) String() string {
+ return fmt.Sprint("{fileConn@ fd:", f.Fd(), " name:", f.Name(), "}")
+}
// FileAddr is the "address" when we are connected to a FileConn,
// the path to the file (or the name passed to Open() if an fd file)
View
16 listener_test_exec.go
@@ -7,11 +7,23 @@ import (
)
func HelloServer(con *http.Conn, req *http.Request) {
- io.WriteString(con, "hello, world\n")
+ io.WriteString(con, "hello, world\r\n")
}
func main() {
- http.Handle("/", http.HandlerFunc(HelloServer))
+ // the hello world test from the http docs
+ http.Handle("/hello/", http.HandlerFunc(HelloServer))
+ // for testing response status codes
+ http.Handle("/notfound/", http.HandlerFunc(http.NotFound))
+ // for testing does the header make it all the way back (does not test that the connection actually stays open, which is a known limitation of http)
+ http.Handle("/connection/", http.HandlerFunc(func(conn *http.Conn, req *http.Request) {
+ conn.SetHeader("Connection", "keep-alive")
+ io.WriteString(conn, "connection test")
+ }))
+ // for testing the serving of static files
+ http.Handle("/static/", http.FileServer("/tmp", "/static"))
+ // this process will be a FastCGI responder, accepting connections on stdin
+ // so we have to give it the same handlers as the fcgiMux from the test
if fdlisten, err := fcgi.Listen("exec", ""); err == nil {
http.Serve(fdlisten, nil)
}
Please sign in to comment.
Something went wrong with that request. Please try again.