filetransport.mx raw

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