blob: 94684b07a1395985102a302980f0f9c426f295b3 [file] [log] [blame]
Brad Fitzpatrick7349d212011-08-30 21:47:41 -07001// 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"
Brad Fitzpatrick7349d212011-08-30 21:47:41 -070010)
11
12// fileTransport implements RoundTripper for the 'file' protocol.
13type 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//
Russ Cox19309772022-02-03 14:12:08 -050025// 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// ...
Brad Fitzpatrick7349d212011-08-30 21:47:41 -070030func NewFileTransport(fs FileSystem) RoundTripper {
31 return fileTransport{fileHandler{fs}}
32}
33
Russ Coxc2049d22011-11-01 22:04:37 -040034func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
Brad Fitzpatrick7349d212011-08-30 21:47:41 -070035 // We start ServeHTTP in a goroutine, which may take a long
Brad Fitzpatrick5fea2cc2016-03-01 23:21:55 +000036 // time if the file is large. The newPopulateResponseWriter
Brad Fitzpatrick7349d212011-08-30 21:47:41 -070037 // 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
49func 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.
69type populateResponse struct {
70 res *Response
71 ch chan *Response
72 wroteHeader bool
73 hasContent bool
74 sentResponse bool
75 pw *io.PipeWriter
76}
77
78func (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
88func (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
100func (pr *populateResponse) Header() Header {
101 return pr.res.Header
102}
103
104func (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
Russ Coxc2049d22011-11-01 22:04:37 -0400114func (pr *populateResponse) Write(p []byte) (n int, err error) {
Brad Fitzpatrick7349d212011-08-30 21:47:41 -0700115 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}