sendfile.go 1.91 KB
Newer Older
1 2 3 4 5 6
/*
The xSendFile middleware transparently sends static files in HTTP responses
via the X-Sendfile mechanism. All that is needed in the Rails code is the
'send_file' method.
*/

7
package senddata
8 9

import (
10
	"../git"
11
	"../helper"
12 13
	"log"
	"net/http"
14
	"strings"
15 16
)

17 18
const sendDataHeader = "Gitlab-Workhorse-Send-Data"

19 20 21 22 23 24 25
type sendFileResponseWriter struct {
	rw       http.ResponseWriter
	status   int
	hijacked bool
	req      *http.Request
}

26
func NewSendFileResponseWriter(rw http.ResponseWriter, req *http.Request) sendFileResponseWriter {
27
	s := sendFileResponseWriter{
28 29
		rw:  rw,
		req: req,
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
	}
	req.Header.Set("X-Sendfile-Type", "X-Sendfile")
	return s
}

func (s *sendFileResponseWriter) Header() http.Header {
	return s.rw.Header()
}

func (s *sendFileResponseWriter) Write(data []byte) (n int, err error) {
	if s.status == 0 {
		s.WriteHeader(http.StatusOK)
	}
	if s.hijacked {
		return
	}
	return s.rw.Write(data)
}

func (s *sendFileResponseWriter) WriteHeader(status int) {
	if s.status != 0 {
		return
	}

	s.status = status
55 56 57 58 59 60
	if s.status != http.StatusOK {
		s.rw.WriteHeader(s.status)
		return
	}

	if file := s.Header().Get("X-Sendfile"); file != "" {
61
		s.Header().Del("X-Sendfile")
62 63
		// Mark this connection as hijacked
		s.hijacked = true
64

65 66 67
		// Serve the file
		sendFileFromDisk(s.rw, s.req, file)
		return
68
	}
69 70
	if sendData := s.Header().Get(sendDataHeader); strings.HasPrefix(sendData, git.SendBlobPrefix) {
		s.Header().Del(sendDataHeader)
71
		s.hijacked = true
72
		git.SendBlob(s.rw, s.req, sendData)
73 74
		return
	}
75 76 77

	s.rw.WriteHeader(s.status)
	return
78
}
79

80 81
func sendFileFromDisk(w http.ResponseWriter, r *http.Request, file string) {
	log.Printf("Send file %q for %s %q", file, r.Method, r.RequestURI)
82
	content, fi, err := helper.OpenFile(file)
83
	if err != nil {
84
		http.NotFound(w, r)
85 86 87 88
		return
	}
	defer content.Close()

89
	http.ServeContent(w, r, "", fi.ModTime(), content)
90 91 92 93 94
}

func (s *sendFileResponseWriter) Flush() {
	s.WriteHeader(http.StatusOK)
}