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)
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//
25//   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//   ...
30func NewFileTransport(fs FileSystem) RoundTripper {
31	return fileTransport{fileHandler{fs}}
32}
33
34func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
35	// We start ServeHTTP in a goroutine, which may take a long
36	// time if the file is large. The newPopulateResponseWriter
37	// 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
114func (pr *populateResponse) Write(p []byte) (n int, err error) {
115	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}
124