]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/http/filetransport.go
net/http: add ServeFileFS, FileServerFS, NewFileTransportFS
[gostls13.git] / src / net / http / filetransport.go
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.
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         go 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: 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.
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 }