Browse Source

Add preview support.

Andy Balholm 7 years ago
parent
commit
a70c4f2b29
4 changed files with 61 additions and 91 deletions
  1. 54 6
      request.go
  2. 0 84
      request_test.go
  3. 1 1
      server.go
  4. 6 0
      status_test.go

+ 54 - 6
request.go

@@ -36,6 +36,7 @@ type Request struct {
 	Proto      string               // The protocol version.
 	Header     textproto.MIMEHeader // The ICAP header
 	RemoteAddr string               // the address of the computer sending the request
+	Preview    []byte               // the body data for an ICAP preview
 
 	// The HTTP messages.
 	Request  *http.Request
@@ -43,8 +44,8 @@ type Request struct {
 }
 
 // ReadRequest reads and parses a request from b.
-func ReadRequest(b *bufio.Reader) (req *Request, err error) {
-	tp := textproto.NewReader(b)
+func ReadRequest(b *bufio.ReadWriter) (req *Request, err error) {
+	tp := textproto.NewReader(b.Reader)
 	req = new(Request)
 
 	// Read first line.
@@ -141,6 +142,30 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
 		}
 	}
 
+	var bodyReader io.ReadCloser = emptyReader(0)
+	if hasBody {
+		if p := req.Header.Get("Preview"); p != "" {
+			moreBody := true
+			req.Preview, err = ioutil.ReadAll(httputil.NewChunkedReader(b))
+			if err != nil {
+				if strings.Contains(err.Error(), "ieof") {
+					// The data ended with "0; ieof", which the HTTP chunked reader doesn't understand.
+					moreBody = false
+					err = nil
+				} else {
+					return nil, err
+				}
+			}
+			var r io.Reader = bytes.NewBuffer(req.Preview)
+			if moreBody {
+				r = io.MultiReader(r, &continueReader{buf: b})
+			}
+			bodyReader = ioutil.NopCloser(r)
+		} else {
+			bodyReader = ioutil.NopCloser(httputil.NewChunkedReader(b))
+		}
+	}
+
 	// Construct the http.Request.
 	if rawReqHdr != nil {
 		req.Request, err = http.ReadRequest(bufio.NewReader(bytes.NewBuffer(rawReqHdr)))
@@ -148,8 +173,8 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
 			return nil, fmt.Errorf("error while parsing HTTP request: %v", err)
 		}
 
-		if hasBody && req.Method == "REQMOD" {
-			req.Request.Body = ioutil.NopCloser(httputil.NewChunkedReader(b))
+		if req.Method == "REQMOD" {
+			req.Request.Body = bodyReader
 		} else {
 			req.Request.Body = emptyReader(0)
 		}
@@ -166,8 +191,8 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
 			return nil, fmt.Errorf("error while parsing HTTP response: %v", err)
 		}
 
-		if hasBody && req.Method == "RESPMOD" {
-			req.Response.Body = ioutil.NopCloser(httputil.NewChunkedReader(b))
+		if req.Method == "RESPMOD" {
+			req.Response.Body = bodyReader
 		} else {
 			req.Response.Body = emptyReader(0)
 		}
@@ -186,3 +211,26 @@ func (emptyReader) Read(p []byte) (n int, err error) {
 func (emptyReader) Close() error {
 	return nil
 }
+
+// A continueReader sends a "100 Continue" message the first time Read
+// is called, creates a ChunkedReader, and reads from that.
+type continueReader struct {
+	buf *bufio.ReadWriter // the underlying connection
+	cr  io.Reader         // the ChunkedReader
+}
+
+func (c *continueReader) Read(p []byte) (n int, err error) {
+	if c.cr == nil {
+		_, err := c.buf.WriteString("ICAP/1.0 100 Continue\r\n\r\n")
+		if err != nil {
+			return 0, err
+		}
+		err = c.buf.Flush()
+		if err != nil {
+			return 0, err
+		}
+		c.cr = httputil.NewChunkedReader(c.buf)
+	}
+
+	return c.cr.Read(p)
+}

+ 0 - 84
request_test.go

@@ -1,84 +0,0 @@
-// Copyright 2011 Andy Balholm. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package icap
-
-import (
-	"testing"
-	"bufio"
-	"strings"
-	"io/ioutil"
-)
-
-func checkString(description, is, shouldBe string, t *testing.T) {
-	if is != shouldBe {
-		t.Fatalf("%s is %s (should be %s)", description, is, shouldBe)
-	}
-}
-
-func TestParserREQMOD(t *testing.T) {
-	buf := strings.NewReader(
-		"REQMOD icap://icap-server.net/server?arg=87 ICAP/1.0\r\n" +
-			"Host: icap-server.net\r\n" +
-			"Encapsulated: req-hdr=0, null-body=170\r\n\r\n" +
-			"GET / HTTP/1.1\r\n" +
-			"Host: www.origin-server.com\r\n" +
-			"Accept: text/html, text/plain\r\n" +
-			"Accept-Encoding: compress\r\n" +
-			"Cookie: ff39fk3jur@4ii0e02i\r\n" +
-			"If-None-Match: \"xyzzy\", \"r2d2xxxx\"\r\n\r\n")
-	r := bufio.NewReader(buf)
-	req, err := ReadRequest(r)
-
-	if err != nil {
-		t.Fatalf("Error while decoding request: %v", err)
-	}
-
-	checkString("Method", req.Method, "REQMOD", t)
-	checkString("Protocol", req.Proto, "ICAP/1.0", t)
-	checkString("Scheme", req.URL.Scheme, "icap", t)
-	checkString("Host", req.URL.Host, "icap-server.net", t)
-	checkString("Path", req.URL.Path, "/server", t)
-	checkString("Query", req.URL.RawQuery, "arg=87", t)
-	checkString("Host header", req.Header.Get("host"), "icap-server.net", t)
-	checkString("Encapsulated header", req.Header.Get("encapsulated"), "req-hdr=0, null-body=170", t)
-	checkString("Request method", req.Request.Method, "GET", t)
-	checkString("Request host", req.Request.Host, "www.origin-server.com", t)
-	checkString("Request Accept-Encoding header", req.Request.Header.Get("Accept-Encoding"), "compress", t)
-}
-
-func TestParserRESPMOD(t *testing.T) {
-	buf := strings.NewReader(
-		"RESPMOD icap://icap.example.org/satisf ICAP/1.0\r\n" +
-			"Host: icap.example.org\r\n" +
-			"Encapsulated: req-hdr=0, res-hdr=137, res-body=296\r\n\r\n" +
-			"GET /origin-resource HTTP/1.1\r\n" +
-			"Host: www.origin-server.com\r\n" +
-			"Accept: text/html, text/plain, image/gif\r\n" +
-			"Accept-Encoding: gzip, compress\r\n\r\n" +
-			"HTTP/1.1 200 OK\r\n" +
-			"Date: Mon, 10 Jan 2000 09:52:22 GMT\r\n" +
-			"Server: Apache/1.3.6 (Unix)\r\n" +
-			"ETag: \"63840-1ab7-378d415b\"\r\n" +
-			"Content-Type: text/html\r\n" +
-			"Content-Length: 51\r\n\r\n" +
-			"33\r\n" +
-			"This is data that was returned by an origin server.\r\n" +
-			"0\r\n\r\n")
-	r := bufio.NewReader(buf)
-	req, err := ReadRequest(r)
-
-	if err != nil {
-		t.Fatalf("Error while decoding request: %v", err)
-	}
-
-	checkString("Request host", req.Request.Host, "www.origin-server.com", t)
-	checkString("Response Server header", req.Response.Header.Get("Server"), "Apache/1.3.6 (Unix)", t)
-
-	body, err := ioutil.ReadAll(req.Response.Body)
-	if err != nil {
-		t.Fatalf("Error while reading response body: %v", err)
-	}
-	checkString("Response body", string(body), "This is data that was returned by an origin server.", t)
-}

+ 1 - 1
server.go

@@ -60,7 +60,7 @@ func newConn(rwc net.Conn, handler Handler) (c *conn, err error) {
 // Read next request from connection.
 func (c *conn) readRequest() (w *respWriter, err error) {
 	var req *Request
-	if req, err = ReadRequest(c.buf.Reader); err != nil {
+	if req, err = ReadRequest(c.buf); err != nil {
 		return nil, err
 	}
 

+ 6 - 0
status_test.go

@@ -8,6 +8,12 @@ import (
 	"testing"
 )
 
+func checkString(description, is, shouldBe string, t *testing.T) {
+	if is != shouldBe {
+		t.Fatalf("%s is %s (should be %s)", description, is, shouldBe)
+	}
+}
+
 func TestStatusCodes(t *testing.T) {
 	checkString("Message", StatusText(100), "Continue", t)
 	checkString("Message", StatusText(401), "Unauthorized", t)