Home | History | Annotate | Download | only in http
      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package http
      6 
      7 import (
      8 	"fmt"
      9 	"io"
     10 )
     11 
     12 // fileTransport implements RoundTripper for the 'file' protocol.
     13 type fileTransport struct {
     14 	fh fileHandler
     15 }
     16 
     17 // NewFileTransport returns a new RoundTripper, serving the provided
     18 // FileSystem. The returned RoundTripper ignores the URL host in its
     19 // incoming requests, as well as most other properties of the
     20 // request.
     21 //
     22 // The typical use case for NewFileTransport is to register the "file"
     23 // protocol with a Transport, as in:
     24 //
     25 //   t := &http.Transport{}
     26 //   t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
     27 //   c := &http.Client{Transport: t}
     28 //   res, err := c.Get("file:///etc/passwd")
     29 //   ...
     30 func NewFileTransport(fs FileSystem) RoundTripper {
     31 	return fileTransport{fileHandler{fs}}
     32 }
     33 
     34 func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
     35 	// We start ServeHTTP in a goroutine, which may take a long
     36 	// time if the file is large. The newPopulateResponseWriter
     37 	// call returns a channel which either ServeHTTP or finish()
     38 	// sends our *Response on, once the *Response itself has been
     39 	// populated (even if the body itself is still being
     40 	// written to the res.Body, a pipe)
     41 	rw, resc := newPopulateResponseWriter()
     42 	go func() {
     43 		t.fh.ServeHTTP(rw, req)
     44 		rw.finish()
     45 	}()
     46 	return <-resc, nil
     47 }
     48 
     49 func newPopulateResponseWriter() (*populateResponse, <-chan *Response) {
     50 	pr, pw := io.Pipe()
     51 	rw := &populateResponse{
     52 		ch: make(chan *Response),
     53 		pw: pw,
     54 		res: &Response{
     55 			Proto:      "HTTP/1.0",
     56 			ProtoMajor: 1,
     57 			Header:     make(Header),
     58 			Close:      true,
     59 			Body:       pr,
     60 		},
     61 	}
     62 	return rw, rw.ch
     63 }
     64 
     65 // populateResponse is a ResponseWriter that populates the *Response
     66 // in res, and writes its body to a pipe connected to the response
     67 // body. Once writes begin or finish() is called, the response is sent
     68 // on ch.
     69 type populateResponse struct {
     70 	res          *Response
     71 	ch           chan *Response
     72 	wroteHeader  bool
     73 	hasContent   bool
     74 	sentResponse bool
     75 	pw           *io.PipeWriter
     76 }
     77 
     78 func (pr *populateResponse) finish() {
     79 	if !pr.wroteHeader {
     80 		pr.WriteHeader(500)
     81 	}
     82 	if !pr.sentResponse {
     83 		pr.sendResponse()
     84 	}
     85 	pr.pw.Close()
     86 }
     87 
     88 func (pr *populateResponse) sendResponse() {
     89 	if pr.sentResponse {
     90 		return
     91 	}
     92 	pr.sentResponse = true
     93 
     94 	if pr.hasContent {
     95 		pr.res.ContentLength = -1
     96 	}
     97 	pr.ch <- pr.res
     98 }
     99 
    100 func (pr *populateResponse) Header() Header {
    101 	return pr.res.Header
    102 }
    103 
    104 func (pr *populateResponse) WriteHeader(code int) {
    105 	if pr.wroteHeader {
    106 		return
    107 	}
    108 	pr.wroteHeader = true
    109 
    110 	pr.res.StatusCode = code
    111 	pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code))
    112 }
    113 
    114 func (pr *populateResponse) Write(p []byte) (n int, err error) {
    115 	if !pr.wroteHeader {
    116 		pr.WriteHeader(StatusOK)
    117 	}
    118 	pr.hasContent = true
    119 	if !pr.sentResponse {
    120 		pr.sendResponse()
    121 	}
    122 	return pr.pw.Write(p)
    123 }
    124