]> Cypherpunks repositories - gostls13.git/commitdiff
bufio:
authorRuss Cox <rsc@golang.org>
Tue, 3 Feb 2009 22:16:22 +0000 (14:16 -0800)
committerRuss Cox <rsc@golang.org>
Tue, 3 Feb 2009 22:16:22 +0000 (14:16 -0800)
* avoid large copies
* NewBufRead, NewBufWrite never fail
* add BufReadWrite

io:
* add io.Close

http, google/net/rpc:
* add, use http.Conn.Hijack

R=r
DELTA=416  (202 added, 123 deleted, 91 changed)
OCL=24153
CL=24238

src/lib/bufio.go
src/lib/bufio_test.go
src/lib/http/server.go
src/lib/http/status.go [new file with mode: 0644]
src/lib/io/io.go
src/lib/log_test.go
src/lib/net/parse_test.go
src/lib/strconv/fp_test.go

index 6fed9d06e7fcbf4b2593826d553b1736fdebc0b5..9f368858859c953653d6722a37eb479fc6fd0963 100644 (file)
@@ -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}
+}
+
index 17fb379cb7647b7d2a495826d6e2889d1fa9fe72..9ffd6cbfd4c7d40603b85cb72bb05f5a3eb63dcf 100644 (file)
@@ -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);
        }
index 970cd7e384550fba22041c2558ebfbebb3f3ea76..6747473c4727254bf5dd053cf3cbc026e980a0d2 100644 (file)
@@ -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 (file)
index 0000000..82a8b21
--- /dev/null
@@ -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",
+}
+
index cbacbe095e011eb48f500aa88b6083f4f936fa97..54f18fb9591efed76e9c0f816237f523d667917a 100644 (file)
@@ -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) {
index d813941bb818b72d1e82f6cc4bdc778cf728337c..922cbb4fc182642a327bb78e566e235dc404656f 100644 (file)
@@ -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);
index 633a45718dcf0381d4571447bc8ebce6f99d27be..57a68ee8040da575a72aa1eb1188d5d5fdc5c3b9 100644 (file)
@@ -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 {
index 6738ed75e11641c379bc986de225b0a77c5e7ab5..c6f67155c22861b1a839b86af79dbd4c21a0d554 100644 (file)
@@ -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 {