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 5package http 6 7import ( 8 "fmt" 9 "io" 10 "io/fs" 11) 12 13// fileTransport implements RoundTripper for the 'file' protocol. 14type 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// ... 31func 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// ... 49func NewFileTransportFS(fsys fs.FS) RoundTripper { 50 return NewFileTransport(FS(fsys)) 51} 52 53func (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 go func() { 62 t.fh.ServeHTTP(rw, req) 63 rw.finish() 64 }() 65 return <-resc, nil 66} 67 68func newPopulateResponseWriter() (*populateResponse, <-chan *Response) { 69 pr, pw := io.Pipe() 70 rw := &populateResponse{ 71 ch: make(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. 88type populateResponse struct { 89 res *Response 90 ch chan *Response 91 wroteHeader bool 92 hasContent bool 93 sentResponse bool 94 pw *io.PipeWriter 95} 96 97func (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 107func (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 119func (pr *populateResponse) Header() Header { 120 return pr.res.Header 121} 122 123func (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 133func (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