diff --git a/src/lib/bufio.go b/src/lib/bufio.go index 6fed9d06e7fcbf4b2593826d553b1736fdebc0b5..9f368858859c953653d6722a37eb479fc6fd0963 100644 --- a/src/lib/bufio.go +++ b/src/lib/bufio.go @@ -16,6 +16,8 @@ import ( // - BufRead: ReadRune, UnreadRune ? // could make ReadRune generic if we dropped UnreadRune // - buffered output +// - would like to rename to Read, Write, but breaks +// embedding of these: would lose the Read, Write methods. const ( defaultBufSize = 4096 @@ -44,6 +46,7 @@ type BufRead struct { rd io.Read; r, w int; err *os.Error; + lastbyte int; } func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) { @@ -53,11 +56,17 @@ func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) { b = new(BufRead); b.buf = make([]byte, size); b.rd = rd; + b.lastbyte = -1; return b, nil } -func NewBufRead(rd io.Read) (b *BufRead, err *os.Error) { - return NewBufReadSize(rd, defaultBufSize); +func NewBufRead(rd io.Read) *BufRead { + b, err := NewBufReadSize(rd, defaultBufSize); + if err != nil { + // cannot happen - defaultBufSize is a valid size + panic("bufio: NewBufRead: ", err.String()); + } + return b; } // Read a new chunk into the buffer. @@ -94,6 +103,23 @@ func (b *BufRead) Read(p []byte) (nn int, err *os.Error) { for len(p) > 0 { n := len(p); if b.w == b.r { + if len(p) >= len(b.buf) { + // Large read, empty buffer. + // Read directly into p to avoid copy. + n, b.err = b.rd.Read(p); + if n > 0 { + b.lastbyte = int(p[n-1]); + } + p = p[n:len(p)]; + nn += n; + if b.err != nil { + return nn, b.err + } + if n == 0 { + return nn, EndOfFile + } + continue; + } b.Fill(); if b.err != nil { return nn, b.err @@ -108,6 +134,7 @@ func (b *BufRead) Read(p []byte) (nn int, err *os.Error) { copySlice(p[0:n], b.buf[b.r:b.r+n]); p = p[n:len(p)]; b.r += n; + b.lastbyte = int(b.buf[b.r-1]); nn += n } return nn, nil @@ -127,6 +154,7 @@ func (b *BufRead) ReadByte() (c byte, err *os.Error) { } c = b.buf[b.r]; b.r++; + b.lastbyte = int(c); return c, nil } @@ -135,10 +163,18 @@ func (b *BufRead) UnreadByte() *os.Error { if b.err != nil { return b.err } + if b.r == b.w && b.lastbyte >= 0 { + b.w = 1; + b.r = 0; + b.buf[0] = byte(b.lastbyte); + b.lastbyte = -1; + return nil; + } if b.r <= 0 { return PhaseError } b.r--; + b.lastbyte = -1; return nil } @@ -163,6 +199,7 @@ func (b *BufRead) ReadRune() (rune int, size int, err *os.Error) { rune, size = utf8.DecodeRune(b.buf[b.r:b.w]); } b.r += size; + b.lastbyte = int(b.buf[b.r-1]); return rune, size, nil } @@ -343,8 +380,13 @@ func NewBufWriteSize(wr io.Write, size int) (b *BufWrite, err *os.Error) { return b, nil } -func NewBufWrite(wr io.Write) (b *BufWrite, err *os.Error) { - return NewBufWriteSize(wr, defaultBufSize); +func NewBufWrite(wr io.Write) *BufWrite { + b, err := NewBufWriteSize(wr, defaultBufSize); + if err != nil { + // cannot happen - defaultBufSize is valid size + panic("bufio: NewBufWrite: ", err.String()); + } + return b; } // Flush the output buffer. @@ -393,6 +435,17 @@ func (b *BufWrite) Write(p []byte) (nn int, err *os.Error) { } n = b.Available() } + if b.Available() == 0 && len(p) >= len(b.buf) { + // Large write, empty buffer. + // Write directly from p to avoid copy. + n, b.err = b.wr.Write(p); + nn += n; + p = p[n:len(p)]; + if b.err != nil { + break; + } + continue; + } if n > len(p) { n = len(p) } @@ -416,3 +469,14 @@ func (b *BufWrite) WriteByte(c byte) *os.Error { return nil } +// buffered input and output + +type BufReadWrite struct { + *BufRead; + *BufWrite; +} + +func NewBufReadWrite(r *BufRead, w *BufWrite) *BufReadWrite { + return &BufReadWrite{r, w} +} + diff --git a/src/lib/bufio_test.go b/src/lib/bufio_test.go index 17fb379cb7647b7d2a495826d6e2889d1fa9fe72..9ffd6cbfd4c7d40603b85cb72bb05f5a3eb63dcf 100644 --- a/src/lib/bufio_test.go +++ b/src/lib/bufio_test.go @@ -174,12 +174,12 @@ var bufsizes = []int { } func TestBufReadSimple(t *testing.T) { - b, e := NewBufRead(newByteReader(io.StringBytes("hello world"))); + b := NewBufRead(newByteReader(io.StringBytes("hello world"))); if s := readBytes(b); s != "hello world" { t.Errorf("simple hello world test failed: got %q", s); } - b, e = NewBufRead(newRot13Reader(newByteReader(io.StringBytes("hello world")))); + b = NewBufRead(newRot13Reader(newByteReader(io.StringBytes("hello world")))); if s := readBytes(b); s != "uryyb jbeyq" { t.Error("rot13 hello world test failed: got %q", s); } diff --git a/src/lib/http/server.go b/src/lib/http/server.go index 970cd7e384550fba22041c2558ebfbebb3f3ea76..6747473c4727254bf5dd053cf3cbc026e980a0d2 100644 --- a/src/lib/http/server.go +++ b/src/lib/http/server.go @@ -16,12 +16,14 @@ import ( "fmt"; "http"; "io"; + "log"; "net"; "os"; "strconv"; ) var ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush") +var ErrHijacked = os.NewError("Conn has been hijacked") type Conn struct @@ -32,132 +34,30 @@ type Handler interface { // Active HTTP connection (server side). type Conn struct { - Fd io.ReadWriteClose; - RemoteAddr string; - Req *Request; - Br *bufio.BufRead; - - br *bufio.BufRead; - bw *bufio.BufWrite; - close bool; - chunking bool; - flushed bool; - header map[string] string; - wroteHeader bool; - handler Handler; -} - -// HTTP response codes. -// TODO(rsc): Maybe move these to their own file, so that -// clients can use them too. - -const ( - StatusContinue = 100; - StatusSwitchingProtocols = 101; - - StatusOK = 200; - StatusCreated = 201; - StatusAccepted = 202; - StatusNonAuthoritativeInfo = 203; - StatusNoContent = 204; - StatusResetContent = 205; - StatusPartialContent = 206; - - StatusMultipleChoices = 300; - StatusMovedPermanently = 301; - StatusFound = 302; - StatusSeeOther = 303; - StatusNotModified = 304; - StatusUseProxy = 305; - StatusTemporaryRedirect = 307; - - StatusBadRequest = 400; - StatusUnauthorized = 401; - StatusPaymentRequired = 402; - StatusForbidden = 403; - StatusNotFound = 404; - StatusMethodNotAllowed = 405; - StatusNotAcceptable = 406; - StatusProxyAuthRequired = 407; - StatusRequestTimeout = 408; - StatusConflict = 409; - StatusGone = 410; - StatusLengthRequired = 411; - StatusPreconditionFailed = 412; - StatusRequestEntityTooLarge = 413; - StatusRequestURITooLong = 414; - StatusUnsupportedMediaType = 415; - StatusRequestedRangeNotSatisfiable = 416; - StatusExpectationFailed = 417; - - StatusInternalServerError = 500; - StatusNotImplemented = 501; - StatusBadGateway = 502; - StatusServiceUnavailable = 503; - StatusGatewayTimeout = 504; - StatusHTTPVersionNotSupported = 505; -) - -var statusText = map[int]string { - StatusContinue: "Continue", - StatusSwitchingProtocols: "Switching Protocols", - - StatusOK: "OK", - StatusCreated: "Created", - StatusAccepted: "Accepted", - StatusNonAuthoritativeInfo: "Non-Authoritative Information", - StatusNoContent: "No Content", - StatusResetContent: "Reset Content", - StatusPartialContent: "Partial Content", - - StatusMultipleChoices: "Multiple Choices", - StatusMovedPermanently: "Moved Permanently", - StatusFound: "Found", - StatusSeeOther: "See Other", - StatusNotModified: "Not Modified", - StatusUseProxy: "Use Proxy", - StatusTemporaryRedirect: "Temporary Redirect", - - StatusBadRequest: "Bad Request", - StatusUnauthorized: "Unauthorized", - StatusPaymentRequired: "Payment Required", - StatusForbidden: "Forbidden", - StatusNotFound: "Not Found", - StatusMethodNotAllowed: "Method Not Allowed", - StatusNotAcceptable: "Not Acceptable", - StatusProxyAuthRequired: "Proxy Authentication Required", - StatusRequestTimeout: "Request Timeout", - StatusConflict: "Conflict", - StatusGone: "Gone", - StatusLengthRequired: "Length Required", - StatusPreconditionFailed: "Precondition Failed", - StatusRequestEntityTooLarge: "Request Entity Too Large", - StatusRequestURITooLong: "Request URI Too Long", - StatusUnsupportedMediaType: "Unsupported Media Type", - StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", - StatusExpectationFailed: "Expectation Failed", - - StatusInternalServerError: "Internal Server Error", - StatusNotImplemented: "Not Implemented", - StatusBadGateway: "Bad Gateway", - StatusServiceUnavailable: "Service Unavailable", - StatusGatewayTimeout: "Gateway Timeout", - StatusHTTPVersionNotSupported: "HTTP Version Not Supported", + RemoteAddr string; // network address of remote side + Req *Request; // current HTTP request + + fd io.ReadWriteClose; // i/o connection + buf *bufio.BufReadWrite; // buffered fd + handler Handler; // request handler + hijacked bool; // connection has been hijacked by handler + + // state for the current reply + closeAfterReply bool; // close connection after this reply + chunking bool; // using chunked transfer encoding for reply body + wroteHeader bool; // reply header has been written + header map[string] string; // reply header parameters } // Create new connection from rwc. func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) { c = new(Conn); - c.Fd = rwc; c.RemoteAddr = raddr; c.handler = handler; - if c.br, err = bufio.NewBufRead(rwc.(io.Read)); err != nil { - return nil, err - } -c.Br = c.br; - if c.bw, err = bufio.NewBufWrite(rwc); err != nil { - return nil, err - } + c.fd = rwc; + br := bufio.NewBufRead(rwc); + bw := bufio.NewBufWrite(rwc); + c.buf = bufio.NewBufReadWrite(br, bw); return c, nil } @@ -165,14 +65,16 @@ func (c *Conn) SetHeader(hdr, val string) // Read next request from connection. func (c *Conn) readRequest() (req *Request, err *os.Error) { - if req, err = ReadRequest(c.br); err != nil { + if c.hijacked { + return nil, ErrHijacked + } + if req, err = ReadRequest(c.buf.BufRead); err != nil { return nil, err } // Reset per-request connection state. c.header = make(map[string] string); c.wroteHeader = false; - c.flushed = false; c.Req = req; // Default output is HTML encoded in UTF-8. @@ -190,7 +92,7 @@ func (c *Conn) readRequest() (req *Request, err *os.Error) { // a Content-Length: header in the response, // but everyone who expects persistent connections // does HTTP/1.1 now. - c.close = true; + c.closeAfterReply = true; c.chunking = false; } @@ -203,8 +105,12 @@ func (c *Conn) SetHeader(hdr, val string) { // Write header. func (c *Conn) WriteHeader(code int) { + if c.hijacked { + log.Stderr("http: Conn.WriteHeader on hijacked connection"); + return + } if c.wroteHeader { - // TODO(rsc): log + log.Stderr("http: multiple Conn.WriteHeader calls"); return } c.wroteHeader = true; @@ -220,19 +126,20 @@ func (c *Conn) WriteHeader(code int) { if !ok { text = "status code " + codestring; } - io.WriteString(c.bw, proto + " " + codestring + " " + text + "\r\n"); + io.WriteString(c.buf, proto + " " + codestring + " " + text + "\r\n"); for k,v := range c.header { - io.WriteString(c.bw, k + ": " + v + "\r\n"); + io.WriteString(c.buf, k + ": " + v + "\r\n"); } - io.WriteString(c.bw, "\r\n"); + io.WriteString(c.buf, "\r\n"); } // TODO(rsc): BUG in 6g: must return "nn int" not "n int" // so that the implicit struct assignment in -// return c.bw.Write(data) works. oops +// return c.buf.Write(data) works. oops func (c *Conn) Write(data []byte) (nn int, err *os.Error) { - if c.flushed { - return 0, ErrWriteAfterFlush + if c.hijacked { + log.Stderr("http: Conn.Write on hijacked connection"); + return 0, ErrHijacked } if !c.wroteHeader { c.WriteHeader(StatusOK); @@ -242,38 +149,35 @@ func (c *Conn) Write(data []byte) (nn int, err *os.Error) { } // TODO(rsc): if chunking happened after the buffering, - // then there would be fewer chunk headers + // then there would be fewer chunk headers. + // On the other hand, it would make hijacking more difficult. if c.chunking { - fmt.Fprintf(c.bw, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt + fmt.Fprintf(c.buf, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt } - return c.bw.Write(data); + return c.buf.Write(data); } -func (c *Conn) Flush() { - if c.flushed { - return - } +func (c *Conn) flush() { if !c.wroteHeader { c.WriteHeader(StatusOK); } if c.chunking { - io.WriteString(c.bw, "0\r\n"); + io.WriteString(c.buf, "0\r\n"); // trailer key/value pairs, followed by blank line - io.WriteString(c.bw, "\r\n"); + io.WriteString(c.buf, "\r\n"); } - c.bw.Flush(); - c.flushed = true; + c.buf.Flush(); } // Close the connection. -func (c *Conn) Close() { - if c.bw != nil { - c.bw.Flush(); - c.bw = nil; +func (c *Conn) close() { + if c.buf != nil { + c.buf.Flush(); + c.buf = nil; } - if c.Fd != nil { - c.Fd.Close(); - c.Fd = nil; + if c.fd != nil { + c.fd.Close(); + c.fd = nil; } } @@ -288,18 +192,32 @@ func (c *Conn) serve() { // Until the server replies to this request, it can't read another, // so we might as well run the handler in this thread. c.handler.ServeHTTP(c, req); - if c.Fd == nil { - // Handler took over the connection. + if c.hijacked { return; } - if !c.flushed { - c.Flush(); - } - if c.close { + c.flush(); + if c.closeAfterReply { break; } } - c.Close(); + c.close(); +} + +// Allow client to take over the connection. +// After a handler calls c.Hijack(), the HTTP server library +// will never touch the connection again. +// It is the caller's responsibility to manage and close +// the connection. +func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error) { + if c.hijacked { + return nil, nil, ErrHijacked; + } + c.hijacked = true; + fd = c.fd; + buf = c.buf; + c.fd = nil; + c.buf = nil; + return; } // Adapter: can use RequestFunction(f) as Handler diff --git a/src/lib/http/status.go b/src/lib/http/status.go new file mode 100644 index 0000000000000000000000000000000000000000..82a8b214c4625da0560a58054307d76051ae654a --- /dev/null +++ b/src/lib/http/status.go @@ -0,0 +1,102 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// HTTP status codes. See RFC 2616. + +package http + +const ( + StatusContinue = 100; + StatusSwitchingProtocols = 101; + + StatusOK = 200; + StatusCreated = 201; + StatusAccepted = 202; + StatusNonAuthoritativeInfo = 203; + StatusNoContent = 204; + StatusResetContent = 205; + StatusPartialContent = 206; + + StatusMultipleChoices = 300; + StatusMovedPermanently = 301; + StatusFound = 302; + StatusSeeOther = 303; + StatusNotModified = 304; + StatusUseProxy = 305; + StatusTemporaryRedirect = 307; + + StatusBadRequest = 400; + StatusUnauthorized = 401; + StatusPaymentRequired = 402; + StatusForbidden = 403; + StatusNotFound = 404; + StatusMethodNotAllowed = 405; + StatusNotAcceptable = 406; + StatusProxyAuthRequired = 407; + StatusRequestTimeout = 408; + StatusConflict = 409; + StatusGone = 410; + StatusLengthRequired = 411; + StatusPreconditionFailed = 412; + StatusRequestEntityTooLarge = 413; + StatusRequestURITooLong = 414; + StatusUnsupportedMediaType = 415; + StatusRequestedRangeNotSatisfiable = 416; + StatusExpectationFailed = 417; + + StatusInternalServerError = 500; + StatusNotImplemented = 501; + StatusBadGateway = 502; + StatusServiceUnavailable = 503; + StatusGatewayTimeout = 504; + StatusHTTPVersionNotSupported = 505; +) + +var statusText = map[int]string { + StatusContinue: "Continue", + StatusSwitchingProtocols: "Switching Protocols", + + StatusOK: "OK", + StatusCreated: "Created", + StatusAccepted: "Accepted", + StatusNonAuthoritativeInfo: "Non-Authoritative Information", + StatusNoContent: "No Content", + StatusResetContent: "Reset Content", + StatusPartialContent: "Partial Content", + + StatusMultipleChoices: "Multiple Choices", + StatusMovedPermanently: "Moved Permanently", + StatusFound: "Found", + StatusSeeOther: "See Other", + StatusNotModified: "Not Modified", + StatusUseProxy: "Use Proxy", + StatusTemporaryRedirect: "Temporary Redirect", + + StatusBadRequest: "Bad Request", + StatusUnauthorized: "Unauthorized", + StatusPaymentRequired: "Payment Required", + StatusForbidden: "Forbidden", + StatusNotFound: "Not Found", + StatusMethodNotAllowed: "Method Not Allowed", + StatusNotAcceptable: "Not Acceptable", + StatusProxyAuthRequired: "Proxy Authentication Required", + StatusRequestTimeout: "Request Timeout", + StatusConflict: "Conflict", + StatusGone: "Gone", + StatusLengthRequired: "Length Required", + StatusPreconditionFailed: "Precondition Failed", + StatusRequestEntityTooLarge: "Request Entity Too Large", + StatusRequestURITooLong: "Request URI Too Long", + StatusUnsupportedMediaType: "Unsupported Media Type", + StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", + StatusExpectationFailed: "Expectation Failed", + + StatusInternalServerError: "Internal Server Error", + StatusNotImplemented: "Not Implemented", + StatusBadGateway: "Bad Gateway", + StatusServiceUnavailable: "Service Unavailable", + StatusGatewayTimeout: "Gateway Timeout", + StatusHTTPVersionNotSupported: "HTTP Version Not Supported", +} + diff --git a/src/lib/io/io.go b/src/lib/io/io.go index cbacbe095e011eb48f500aa88b6083f4f936fa97..54f18fb9591efed76e9c0f816237f523d667917a 100644 --- a/src/lib/io/io.go +++ b/src/lib/io/io.go @@ -30,6 +30,10 @@ type ReadWriteClose interface { Close() *os.Error; } +type Close interface { + Close() *os.Error; +} + func WriteString(w Write, s string) (n int, err *os.Error) { b := make([]byte, len(s)+1); if !syscall.StringToBytes(b, s) { diff --git a/src/lib/log_test.go b/src/lib/log_test.go index d813941bb818b72d1e82f6cc4bdc778cf728337c..922cbb4fc182642a327bb78e566e235dc404656f 100644 --- a/src/lib/log_test.go +++ b/src/lib/log_test.go @@ -51,10 +51,7 @@ func testLog(t *testing.T, flag int, prefix string, pattern string, useLogf bool if err1 != nil { t.Fatal("pipe", err1); } - buf, err2 := bufio.NewBufRead(fd0); - if err2 != nil { - t.Fatal("bufio.NewBufRead", err2); - } + buf := bufio.NewBufRead(fd0); l := NewLogger(fd1, nil, prefix, flag); if useLogf { l.Logf("hello %d world", 23); diff --git a/src/lib/net/parse_test.go b/src/lib/net/parse_test.go index 633a45718dcf0381d4571447bc8ebce6f99d27be..57a68ee8040da575a72aa1eb1188d5d5fdc5c3b9 100644 --- a/src/lib/net/parse_test.go +++ b/src/lib/net/parse_test.go @@ -18,10 +18,7 @@ func TestReadLine(t *testing.T) { if err != nil { t.Fatalf("open %s: %v", filename, err); } - br, err1 := bufio.NewBufRead(fd); - if err1 != nil { - t.Fatalf("bufio.NewBufRead: %v", err1); - } + br := bufio.NewBufRead(fd); file := _Open(filename); if file == nil { diff --git a/src/lib/strconv/fp_test.go b/src/lib/strconv/fp_test.go index 6738ed75e11641c379bc986de225b0a77c5e7ab5..c6f67155c22861b1a839b86af79dbd4c21a0d554 100644 --- a/src/lib/strconv/fp_test.go +++ b/src/lib/strconv/fp_test.go @@ -98,10 +98,7 @@ func TestFp(t *testing.T) { panicln("testfp: open testfp.txt:", err.String()); } - b, err1 := bufio.NewBufRead(fd); - if err1 != nil { - panicln("testfp NewBufRead:", err1.String()); - } + b := bufio.NewBufRead(fd); lineno := 0; for {