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