173 lines
4.4 KiB
Go
173 lines
4.4 KiB
Go
package sendurl
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"gitlab.com/gitlab-org/labkit/correlation"
|
|
"gitlab.com/gitlab-org/labkit/log"
|
|
"gitlab.com/gitlab-org/labkit/mask"
|
|
"gitlab.com/gitlab-org/labkit/tracing"
|
|
|
|
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
|
|
"gitlab.com/gitlab-org/gitlab-workhorse/internal/senddata"
|
|
)
|
|
|
|
type entry struct{ senddata.Prefix }
|
|
|
|
type entryParams struct {
|
|
URL string
|
|
AllowRedirects bool
|
|
}
|
|
|
|
var SendURL = &entry{"send-url:"}
|
|
|
|
var rangeHeaderKeys = []string{
|
|
"If-Match",
|
|
"If-Unmodified-Since",
|
|
"If-None-Match",
|
|
"If-Modified-Since",
|
|
"If-Range",
|
|
"Range",
|
|
}
|
|
|
|
// Keep cache headers from the original response, not the proxied response. The
|
|
// original response comes from the Rails application, which should be the
|
|
// source of truth for caching.
|
|
var preserveHeaderKeys = map[string]bool{
|
|
"Cache-Control": true,
|
|
"Expires": true,
|
|
"Date": true, // Support for HTTP 1.0 proxies
|
|
"Pragma": true, // Support for HTTP 1.0 proxies
|
|
}
|
|
|
|
// httpTransport defines a http.Transport with values
|
|
// that are more restrictive than for http.DefaultTransport,
|
|
// they define shorter TLS Handshake, and more aggressive connection closing
|
|
// to prevent the connection hanging and reduce FD usage
|
|
var httpTransport = tracing.NewRoundTripper(correlation.NewInstrumentedRoundTripper(&http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
DialContext: (&net.Dialer{
|
|
Timeout: 30 * time.Second,
|
|
KeepAlive: 10 * time.Second,
|
|
}).DialContext,
|
|
MaxIdleConns: 2,
|
|
IdleConnTimeout: 30 * time.Second,
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
ExpectContinueTimeout: 10 * time.Second,
|
|
ResponseHeaderTimeout: 30 * time.Second,
|
|
}))
|
|
|
|
var httpClient = &http.Client{
|
|
Transport: httpTransport,
|
|
}
|
|
|
|
var (
|
|
sendURLRequests = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "gitlab_workhorse_send_url_requests",
|
|
Help: "How many send URL requests have been processed",
|
|
},
|
|
[]string{"status"},
|
|
)
|
|
sendURLOpenRequests = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Name: "gitlab_workhorse_send_url_open_requests",
|
|
Help: "Describes how many send URL requests are open now",
|
|
},
|
|
)
|
|
sendURLBytes = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Name: "gitlab_workhorse_send_url_bytes",
|
|
Help: "How many bytes were passed with send URL",
|
|
},
|
|
)
|
|
|
|
sendURLRequestsInvalidData = sendURLRequests.WithLabelValues("invalid-data")
|
|
sendURLRequestsRequestFailed = sendURLRequests.WithLabelValues("request-failed")
|
|
sendURLRequestsSucceeded = sendURLRequests.WithLabelValues("succeeded")
|
|
)
|
|
|
|
func init() {
|
|
prometheus.MustRegister(
|
|
sendURLRequests,
|
|
sendURLOpenRequests,
|
|
sendURLBytes)
|
|
}
|
|
|
|
func (e *entry) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
|
|
var params entryParams
|
|
|
|
sendURLOpenRequests.Inc()
|
|
defer sendURLOpenRequests.Dec()
|
|
|
|
if err := e.Unpack(¶ms, sendData); err != nil {
|
|
helper.Fail500(w, r, fmt.Errorf("SendURL: unpack sendData: %v", err))
|
|
return
|
|
}
|
|
|
|
log.WithContextFields(r.Context(), log.Fields{
|
|
"url": mask.URL(params.URL),
|
|
"path": r.URL.Path,
|
|
}).Info("SendURL: sending")
|
|
|
|
if params.URL == "" {
|
|
sendURLRequestsInvalidData.Inc()
|
|
helper.Fail500(w, r, fmt.Errorf("SendURL: URL is empty"))
|
|
return
|
|
}
|
|
|
|
// create new request and copy range headers
|
|
newReq, err := http.NewRequest("GET", params.URL, nil)
|
|
if err != nil {
|
|
sendURLRequestsInvalidData.Inc()
|
|
helper.Fail500(w, r, fmt.Errorf("SendURL: NewRequest: %v", err))
|
|
return
|
|
}
|
|
newReq = newReq.WithContext(r.Context())
|
|
|
|
for _, header := range rangeHeaderKeys {
|
|
newReq.Header[header] = r.Header[header]
|
|
}
|
|
|
|
// execute new request
|
|
var resp *http.Response
|
|
if params.AllowRedirects {
|
|
resp, err = httpClient.Do(newReq)
|
|
} else {
|
|
resp, err = httpTransport.RoundTrip(newReq)
|
|
}
|
|
if err != nil {
|
|
sendURLRequestsRequestFailed.Inc()
|
|
helper.Fail500(w, r, fmt.Errorf("SendURL: Do request: %v", err))
|
|
return
|
|
}
|
|
|
|
// Prevent Go from adding a Content-Length header automatically
|
|
w.Header().Del("Content-Length")
|
|
|
|
// copy response headers and body, except the headers from preserveHeaderKeys
|
|
for key, value := range resp.Header {
|
|
if !preserveHeaderKeys[key] {
|
|
w.Header()[key] = value
|
|
}
|
|
}
|
|
w.WriteHeader(resp.StatusCode)
|
|
|
|
defer resp.Body.Close()
|
|
n, err := io.Copy(w, resp.Body)
|
|
sendURLBytes.Add(float64(n))
|
|
|
|
if err != nil {
|
|
sendURLRequestsRequestFailed.Inc()
|
|
helper.LogError(r, fmt.Errorf("SendURL: Copy response: %v", err))
|
|
return
|
|
}
|
|
|
|
sendURLRequestsSucceeded.Inc()
|
|
}
|