Commit 3920a26e authored by Kamil Trzcinski's avatar Kamil Trzcinski

Make GitLab Workhorse passthrough to Unicorn

parent d1b215d5
......@@ -2,7 +2,7 @@ PREFIX=/usr/local
VERSION=$(shell git describe)-$(shell date -u +%Y%m%d.%H%M%S)
gitlab-workhorse: $(wildcard *.go)
go build -ldflags "-X main.Version ${VERSION}" -o gitlab-workhorse
go build -ldflags "-X main.Version=${VERSION}" -o gitlab-workhorse
install: gitlab-workhorse
install gitlab-workhorse ${PREFIX}/bin/
......
......@@ -2,13 +2,55 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
func (u *upstream) newUpstreamRequest(r *http.Request, body io.Reader, suffix string) (*http.Request, error) {
url := u.authBackend + r.URL.RequestURI() + suffix
authReq, err := http.NewRequest(r.Method, url, body)
if err != nil {
return nil, err
}
// Forward all headers from our client to the auth backend. This includes
// HTTP Basic authentication credentials (the 'Authorization' header).
for k, v := range r.Header {
authReq.Header[k] = v
}
// Clean some headers when issuing a new request without body
if body == nil {
authReq.Header.Del("Content-Type")
authReq.Header.Del("Content-Encoding")
authReq.Header.Del("Content-Length")
authReq.Header.Del("Content-Disposition")
authReq.Header.Del("Accept-Encoding")
// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
authReq.Header.Del("Transfer-Encoding")
authReq.Header.Del("Connection")
authReq.Header.Del("Keep-Alive")
authReq.Header.Del("Proxy-Authenticate")
authReq.Header.Del("Proxy-Authorization")
authReq.Header.Del("Te")
authReq.Header.Del("Trailers")
authReq.Header.Del("Upgrade")
}
// Also forward the Host header, which is excluded from the Header map by the http libary.
// This allows the Host header received by the backend to be consistent with other
// requests not going through gitlab-workhorse.
authReq.Host = r.Host
// Set a custom header for the request. This can be used in some
// configurations (Passenger) to solve auth request routing problems.
authReq.Header.Set("Gitlab-Workhorse", Version)
return authReq, nil
}
func preAuthorizeHandler(handleFunc serviceHandleFunc, suffix string) serviceHandleFunc {
return func(w http.ResponseWriter, r *gitRequest) {
authReq, err := r.u.newUpstreamRequest(r.Request, nil, suffix)
......@@ -65,19 +107,3 @@ func preAuthorizeHandler(handleFunc serviceHandleFunc, suffix string) serviceHan
handleFunc(w, r)
}
}
func repoPreAuthorizeHandler(handleFunc serviceHandleFunc) serviceHandleFunc {
return preAuthorizeHandler(func(w http.ResponseWriter, r *gitRequest) {
if r.RepoPath == "" {
fail500(w, errors.New("repoPreAuthorizeHandler: RepoPath empty"))
return
}
if !looksLikeRepo(r.RepoPath) {
http.Error(w, "Not Found", 404)
return
}
handleFunc(w, r)
}, "")
}
package main
import (
"io/ioutil"
"log"
"net/http"
"path/filepath"
)
func handleDeployPage(deployPage string, handler serviceHandleFunc) serviceHandleFunc {
deployPage, err := filepath.Abs(deployPage)
if err != nil {
log.Fatalln(err)
}
return func(w http.ResponseWriter, r *gitRequest) {
data, err := ioutil.ReadFile(deployPage)
if err != nil {
handler(w, r)
return
}
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
w.Write(data)
}
}
package main
import (
"fmt"
"io/ioutil"
"net/http"
"log"
)
type errorPageResponseWriter struct {
rw http.ResponseWriter
status int
hijacked bool
}
func newErrorPageResponseWriter(rw http.ResponseWriter) *errorPageResponseWriter {
s := &errorPageResponseWriter{
rw: rw,
}
return s
}
func (s *errorPageResponseWriter) Header() http.Header {
return s.rw.Header()
}
func (s *errorPageResponseWriter) Write(data []byte) (n int, err error) {
if s.status == 0 {
s.WriteHeader(http.StatusOK)
}
if s.hijacked {
return 0, nil
}
return s.rw.Write(data)
}
func (s *errorPageResponseWriter) WriteHeader(status int) {
if s.status != 0 {
return
}
s.status = status
switch s.status {
case 404, 422, 500, 502:
data, err := ioutil.ReadFile(fmt.Sprintf("public/%d.html", s.status))
if err != nil {
break
}
log.Printf("ErroPage: serving predefined error page: %d", s.status)
s.hijacked = true
s.rw.Header().Set("Content-Type", "text/html")
s.rw.WriteHeader(s.status)
s.rw.Write(data)
return
default:
break
}
s.rw.WriteHeader(status)
}
func (s *errorPageResponseWriter) Flush() {
s.WriteHeader(http.StatusOK)
}
func handleRailsError(handler serviceHandleFunc) serviceHandleFunc {
return func(w http.ResponseWriter, r *gitRequest) {
rw := newErrorPageResponseWriter(w)
defer rw.Flush()
handler(rw, r)
}
}
......@@ -5,13 +5,43 @@ In this file we handle the Git 'smart HTTP' protocol
package main
import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
)
func looksLikeRepo(p string) bool {
// If /path/to/foo.git/objects exists then let's assume it is a valid Git
// repository.
if _, err := os.Stat(path.Join(p, "objects")); err != nil {
log.Print(err)
return false
}
return true
}
func repoPreAuthorizeHandler(handleFunc serviceHandleFunc) serviceHandleFunc {
return preAuthorizeHandler(func(w http.ResponseWriter, r *gitRequest) {
if r.RepoPath == "" {
fail500(w, errors.New("repoPreAuthorizeHandler: RepoPath empty"))
return
}
if !looksLikeRepo(r.RepoPath) {
http.Error(w, "Not Found", 404)
return
}
handleFunc(w, r)
}, "")
}
func handleGetInfoRefs(w http.ResponseWriter, r *gitRequest) {
rpc := r.URL.Query().Get("service")
if !(rpc == "git-upload-pack" || rpc == "git-receive-pack") {
......
......@@ -5,6 +5,7 @@ In this file we handle git lfs objects downloads and uploads
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
......@@ -67,20 +68,12 @@ func handleStoreLfsObject(w http.ResponseWriter, r *gitRequest) {
fail500(w, fmt.Errorf("handleStoreLfsObject: expected sha256 %s, got %s", r.LfsOid, shaStr))
return
}
r.Header.Set("X-GitLab-Lfs-Tmp", filepath.Base(file.Name()))
storeReq, err := r.u.newUpstreamRequest(r.Request, nil, "")
if err != nil {
fail500(w, fmt.Errorf("handleStoreLfsObject: newUpstreamRequest: %v", err))
return
}
storeResponse, err := r.u.httpClient.Do(storeReq)
if err != nil {
fail500(w, fmt.Errorf("handleStoreLfsObject: do %v: %v", storeReq.URL.Path, err))
return
}
defer storeResponse.Body.Close()
// Inject header and body
r.Header.Set("X-GitLab-Lfs-Tmp", filepath.Base(file.Name()))
r.Body = ioutil.NopCloser(&bytes.Buffer{})
r.ContentLength = 0
forwardResponseToClient(w, storeResponse)
// And proxy the request
proxyRequest(w, r)
}
package main
import (
"fmt"
"net/http"
"time"
)
type loggingResponseWriter struct {
rw http.ResponseWriter
status int
written int64
started time.Time
}
func newLoggingResponseWriter(rw http.ResponseWriter) *loggingResponseWriter {
return &loggingResponseWriter{
rw: rw,
started: time.Now(),
}
}
func (l *loggingResponseWriter) Header() http.Header {
return l.rw.Header()
}
func (l *loggingResponseWriter) Write(data []byte) (n int, err error) {
if l.status == 0 {
l.WriteHeader(http.StatusOK)
}
n, err = l.rw.Write(data)
l.written += int64(n)
return
}
func (l *loggingResponseWriter) WriteHeader(status int) {
if l.status != 0 {
return
}
l.status = status
l.rw.WriteHeader(status)
}
func (l *loggingResponseWriter) Log(r *http.Request) {
fmt.Printf("%s %s - - [%s] %q %d %d %q %q\n",
r.Host, r.RemoteAddr, l.started,
fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto),
l.status, l.written, r.Referer(), r.UserAgent(),
)
}
......@@ -21,20 +21,56 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"regexp"
"syscall"
"time"
)
var Version = "(unknown version)" // Set at build time in the Makefile
var printVersion = flag.Bool("version", false, "Print version and exit")
var listenAddr = flag.String("listenAddr", "localhost:8181", "Listen address for HTTP server")
var listenNetwork = flag.String("listenNetwork", "tcp", "Listen 'network' (tcp, tcp4, tcp6, unix)")
var listenUmask = flag.Int("listenUmask", 022, "Umask for Unix socket, default: 022")
var authBackend = flag.String("authBackend", "http://localhost:8080", "Authentication/authorization backend")
var authSocket = flag.String("authSocket", "", "Optional: Unix domain socket to dial authBackend at")
var pprofListenAddr = flag.String("pprofListenAddr", "", "pprof listening address, e.g. 'localhost:6060'")
type httpRoute struct {
method string
regex *regexp.Regexp
handleFunc serviceHandleFunc
}
// Routing table
var httpRoutes = [...]httpRoute{
httpRoute{"GET", regexp.MustCompile(`/info/refs\z`), repoPreAuthorizeHandler(handleGetInfoRefs)},
httpRoute{"POST", regexp.MustCompile(`/git-upload-pack\z`), repoPreAuthorizeHandler(contentEncodingHandler(handlePostRPC))},
httpRoute{"POST", regexp.MustCompile(`/git-receive-pack\z`), repoPreAuthorizeHandler(contentEncodingHandler(handlePostRPC))},
httpRoute{"GET", regexp.MustCompile(`/repository/archive\z`), repoPreAuthorizeHandler(handleGetArchive)},
httpRoute{"GET", regexp.MustCompile(`/repository/archive.zip\z`), repoPreAuthorizeHandler(handleGetArchive)},
httpRoute{"GET", regexp.MustCompile(`/repository/archive.tar\z`), repoPreAuthorizeHandler(handleGetArchive)},
httpRoute{"GET", regexp.MustCompile(`/repository/archive.tar.gz\z`), repoPreAuthorizeHandler(handleGetArchive)},
httpRoute{"GET", regexp.MustCompile(`/repository/archive.tar.bz2\z`), repoPreAuthorizeHandler(handleGetArchive)},
// Git LFS
httpRoute{"PUT", regexp.MustCompile(`/gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`), lfsAuthorizeHandler(handleStoreLfsObject)},
// CI artifacts
httpRoute{"POST", regexp.MustCompile(`^/ci/api/v1/builds/[0-9]+/artifacts\z`), artifactsAuthorizeHandler(contentEncodingHandler(handleFileUploads))},
// Explicitly proxy API
httpRoute{"", regexp.MustCompile(`^/api/`), proxyRequest},
httpRoute{"", regexp.MustCompile(`^/ci/api/`), proxyRequest},
// Serve static files and forward otherwise
httpRoute{"", nil, handleServeFile("public",
handleDeployPage("public/index.html",
handleRailsError(proxyRequest),
))},
}
func main() {
printVersion := flag.Bool("version", false, "Print version and exit")
listenAddr := flag.String("listenAddr", "localhost:8181", "Listen address for HTTP server")
listenNetwork := flag.String("listenNetwork", "tcp", "Listen 'network' (tcp, tcp4, tcp6, unix)")
listenUmask := flag.Int("listenUmask", 022, "Umask for Unix socket, default: 022")
authBackend := flag.String("authBackend", "http://localhost:8080", "Authentication/authorization backend")
authSocket := flag.String("authSocket", "", "Optional: Unix domain socket to dial authBackend at")
pprofListenAddr := flag.String("pprofListenAddr", "", "pprof listening address, e.g. 'localhost:6060'")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\n %s [OPTIONS]\n\nOptions:\n", os.Args[0])
......
package main
import (
"fmt"
"net/http"
)
func proxyRequest(w http.ResponseWriter, r *gitRequest) {
upRequest, err := r.u.newUpstreamRequest(r.Request, r.Body, "")
if err != nil {
fail500(w, fmt.Errorf("proxyRequest: newUpstreamRequest: %v", err))
return
func headerClone(h http.Header) http.Header {
h2 := make(http.Header, len(h))
for k, vv := range h {
vv2 := make([]string, len(vv))
copy(vv2, vv)
h2[k] = vv2
}
return h2
}
upResponse, err := r.u.httpClient.Do(upRequest)
if err != nil {
fail500(w, fmt.Errorf("proxyRequest: do %v: %v", upRequest.URL.Path, err))
return
}
defer upResponse.Body.Close()
func proxyRequest(w http.ResponseWriter, r *gitRequest) {
// Clone request
req := *r.Request
req.Header = headerClone(r.Header)
forwardResponseToClient(w, upResponse)
// Set Workhorse version
req.Header.Set("Gitlab-Workhorse", Version)
rw := newSendFileResponseWriter(w, &req)
defer rw.Flush()
r.u.httpProxy.ServeHTTP(rw, &req)
}
/*
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.
*/
package main
import (
"fmt"
"log"
"net/http"
"os"
)
type sendFileResponseWriter struct {
rw http.ResponseWriter
status int
hijacked bool
req *http.Request
}
func newSendFileResponseWriter(rw http.ResponseWriter, req *http.Request) *sendFileResponseWriter {
s := &sendFileResponseWriter{
rw: rw,
req: req,
}
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
// Check X-Sendfile header
file := s.Header().Get("X-Sendfile")
s.Header().Del("X-Sendfile")
// If file is empty or status is not 200 pass through header
if file == "" || s.status != http.StatusOK {
s.rw.WriteHeader(s.status)
return
}
// Mark this connection as hijacked
s.hijacked = true
// Serve the file
log.Printf("SendFile: serving %q", file)
content, err := os.Open(file)
if err != nil {
http.NotFound(s.rw, s.req)
return
}
defer content.Close()
fi, err := content.Stat()
if err != nil || fi.IsDir() {
fail500(s.rw, fmt.Errorf("handleSendfile: get mtime: %v", err))
return
}
http.ServeContent(s.rw, s.req, "", fi.ModTime(), content)
}
func (s *sendFileResponseWriter) Flush() {
s.WriteHeader(http.StatusOK)
}
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
)
func handleServeFile(rootDir string, notFoundHandler serviceHandleFunc) serviceHandleFunc {
rootDir, err := filepath.Abs(rootDir)
if err != nil {
log.Fatalln(err)
}
return func(w http.ResponseWriter, r *gitRequest) {
file := filepath.Join(rootDir, r.URL.Path)
file, err := filepath.Abs(file)
if err != nil {
fail500(w, fmt.Errorf("invalid path:"+file, err))
return
}
if !strings.HasPrefix(file, rootDir) {
fail500(w, fmt.Errorf("invalid path: "+file, os.ErrInvalid))
return
}
content, err := os.Open(file)
if err != nil {
if notFoundHandler != nil {
notFoundHandler(w, r)
} else {
http.NotFound(w, r.Request)
}
return
}
defer content.Close()
fi, err := content.Stat()
if err != nil {
fail500(w, fmt.Errorf("handleServeFileHandler", err))
return
}
if fi.IsDir() {
if notFoundHandler != nil {
notFoundHandler(w, r)
} else {
http.NotFound(w, r.Request)
}
return
}
log.Printf("StaticFile: serving %q", file)
http.ServeContent(w, r.Request, filepath.Base(file), fi.ModTime(), content)
}
}
......@@ -111,25 +111,11 @@ func handleFileUploads(w http.ResponseWriter, r *gitRequest) {
// Close writer
writer.Close()
// Create request
upstreamRequest, err := r.u.newUpstreamRequest(r.Request, nil, "")
if err != nil {
fail500(w, fmt.Errorf("handleFileUploads: newUpstreamRequest: %v", err))
return
}
// Set multipart form data
upstreamRequest.Body = ioutil.NopCloser(&body)
upstreamRequest.ContentLength = int64(body.Len())
upstreamRequest.Header.Set("Content-Type", writer.FormDataContentType())
// Forward request to backend
upstreamResponse, err := r.u.httpClient.Do(upstreamRequest)
if err != nil {
fail500(w, fmt.Errorf("handleFileUploads: do request %v: %v", upstreamRequest.URL.Path, err))
return
}
defer upstreamResponse.Body.Close()
// Hijack the request
r.Body = ioutil.NopCloser(&body)
r.ContentLength = int64(body.Len())
r.Header.Set("Content-Type", writer.FormDataContentType())
forwardResponseToClient(w, upstreamResponse)
// Proxy the request
proxyRequest(w, r)
}
......@@ -7,12 +7,10 @@ In this file we handle request routing and interaction with the authBackend.
package main
import (
"io"
"log"
"net/http"
"os"
"path"
"regexp"
"net/http/httputil"
"net/url"
)
type serviceHandleFunc func(w http.ResponseWriter, r *gitRequest)
......@@ -20,12 +18,7 @@ type serviceHandleFunc func(w http.ResponseWriter, r *gitRequest)
type upstream struct {
httpClient *http.Client
authBackend string
}
type gitService struct {
method string
regex *regexp.Regexp
handleFunc serviceHandleFunc
httpProxy *httputil.ReverseProxy
}
type authorizationResponse struct {
......@@ -56,7 +49,7 @@ type authorizationResponse struct {
TempPath string
}
// A gitReqest is an *http.Request decorated with attributes returned by the
// A gitRequest is an *http.Request decorated with attributes returned by the
// GitLab Rails application.
type gitRequest struct {
*http.Request
......@@ -64,42 +57,35 @@ type gitRequest struct {
u *upstream
}
// Routing table
var gitServices = [...]gitService{
gitService{"GET", regexp.MustCompile(`/info/refs\z`), repoPreAuthorizeHandler(handleGetInfoRefs)},
gitService{"POST", regexp.MustCompile(`/git-upload-pack\z`), repoPreAuthorizeHandler(contentEncodingHandler(handlePostRPC))},
gitService{"POST", regexp.MustCompile(`/git-receive-pack\z`), repoPreAuthorizeHandler(contentEncodingHandler(handlePostRPC))},
gitService{"GET", regexp.MustCompile(`/repository/archive\z`), repoPreAuthorizeHandler(handleGetArchive)},
gitService{"GET", regexp.MustCompile(`/repository/archive.zip\z`), repoPreAuthorizeHandler(handleGetArchive)},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar\z`), repoPreAuthorizeHandler(handleGetArchive)},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar.gz\z`), repoPreAuthorizeHandler(handleGetArchive)},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar.bz2\z`), repoPreAuthorizeHandler(handleGetArchive)},
gitService{"GET", regexp.MustCompile(`/uploads/`), handleSendFile},
// Git LFS
gitService{"PUT", regexp.MustCompile(`/gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`), lfsAuthorizeHandler(handleStoreLfsObject)},
gitService{"GET", regexp.MustCompile(`/gitlab-lfs/objects/([0-9a-f]{64})\z`), handleSendFile},
// CI artifacts
gitService{"GET", regexp.MustCompile(`/builds/download\z`), handleSendFile},
gitService{"GET", regexp.MustCompile(`/ci/api/v1/builds/[0-9]+/artifacts\z`), handleSendFile},
gitService{"POST", regexp.MustCompile(`/ci/api/v1/builds/[0-9]+/artifacts\z`), artifactsAuthorizeHandler(contentEncodingHandler(handleFileUploads))},
gitService{"DELETE", regexp.MustCompile(`/ci/api/v1/builds/[0-9]+/artifacts\z`), proxyRequest},
}
func newUpstream(authBackend string, authTransport http.RoundTripper) *upstream {
return &upstream{&http.Client{Transport: authTransport}, authBackend}
u, err := url.Parse(authBackend)
if err != nil {
log.Fatalln(err)
}
up := &upstream{
authBackend: authBackend,
httpClient: &http.Client{Transport: authTransport},
httpProxy: httputil.NewSingleHostReverseProxy(u),
}
up.httpProxy.Transport = authTransport
return up
}
func (u *upstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var g gitService
func (u *upstream) ServeHTTP(ow http.ResponseWriter, r *http.Request) {
var g httpRoute
log.Printf("%s %q", r.Method, r.URL)
w := newLoggingResponseWriter(ow)
defer w.Log(r)
// Look for a matching Git service
foundService := false
for _, g = range gitServices {
if r.Method == g.method && g.regex.MatchString(r.URL.Path) {
for _, g = range httpRoutes {
if g.method != "" && r.Method != g.method {
continue
}
if g.regex == nil || g.regex.MatchString(r.URL.Path) {
foundService = true
break
}
......@@ -118,46 +104,3 @@ func (u *upstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.handleFunc(w, &request)
}
func looksLikeRepo(p string) bool {
// If /path/to/foo.git/objects exists then let's assume it is a valid Git
// repository.
if _, err := os.Stat(path.Join(p, "objects")); err != nil {
log.Print(err)
return false
}
return true
}
func (u *upstream) newUpstreamRequest(r *http.Request, body io.Reader, suffix string) (*http.Request, error) {
url := u.authBackend + r.URL.RequestURI() + suffix
authReq, err := http.NewRequest(r.Method, url, body)
if err != nil {
return nil, err
}
// Forward all headers from our client to the auth backend. This includes
// HTTP Basic authentication credentials (the 'Authorization' header).
for k, v := range r.Header {
authReq.Header[k] = v
}
// Clean some headers when issuing a new request without body
if body == nil {
authReq.Header.Del("Content-Type")
authReq.Header.Del("Content-Encoding")
authReq.Header.Del("Content-Length")
authReq.Header.Del("Content-Disposition")
authReq.Header.Del("Accept-Encoding")
authReq.Header.Del("Transfer-Encoding")
}
// Also forward the Host header, which is excluded from the Header map by the http libary.
// This allows the Host header received by the backend to be consistent with other
// requests not going through gitlab-workhorse.
authReq.Host = r.Host
// Set a custom header for the request. This can be used in some
// configurations (Passenger) to solve auth request routing problems.
authReq.Header.Set("Gitlab-Workhorse", Version)
return authReq, nil
}
/*
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.
*/
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func handleSendFile(w http.ResponseWriter, r *gitRequest) {
upRequest, err := r.u.newUpstreamRequest(r.Request, r.Body, "")
if err != nil {
fail500(w, fmt.Errorf("handleSendFile: newUpstreamRequest: %v", err))
return
}
upRequest.Header.Set("X-Sendfile-Type", "X-Sendfile")
upResponse, err := r.u.httpClient.Do(upRequest)
r.Body.Close()
if err != nil {
fail500(w, fmt.Errorf("handleSendfile: do upstream request: %v", err))
return
}
defer upResponse.Body.Close()
// Get X-Sendfile
sendfile := upResponse.Header.Get("X-Sendfile")
upResponse.Header.Del("X-Sendfile")
// Copy headers from Rails upResponse
for k, v := range upResponse.Header {
w.Header()[k] = v
}
// Use accelerated file serving
if sendfile == "" {
// Copy request body otherwise
w.WriteHeader(upResponse.StatusCode)
// Copy body from Rails upResponse
if _, err := io.Copy(w, upResponse.Body); err != nil {
fail500(w, fmt.Errorf("handleSendFile: copy upstream response: %v", err))
}
return
}
log.Printf("Serving file %q", sendfile)
upResponse.Body.Close()
content, err := os.Open(sendfile)
if err != nil {
fail500(w, fmt.Errorf("handleSendile: open sendfile: %v", err))
return
}
defer content.Close()
fi, err := content.Stat()
if err != nil {
fail500(w, fmt.Errorf("handleSendfile: get mtime: %v", err))
return
}
http.ServeContent(w, r.Request, "", fi.ModTime(), content)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment