1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
package sendurl
import (
"fmt"
"io"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"gitlab.com/gitlab-org/labkit/mask"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/helper/fail"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/log"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/senddata"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/transport"
)
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
}
var httpTransport = transport.NewRestrictedTransport()
var httpClient = &http.Client{
Transport: httpTransport,
}
var (
sendURLRequests = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "gitlab_workhorse_send_url_requests",
Help: "How many send URL requests have been processed",
},
[]string{"status"},
)
sendURLOpenRequests = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "gitlab_workhorse_send_url_open_requests",
Help: "Describes how many send URL requests are open now",
},
)
sendURLBytes = promauto.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 (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 {
fail.Request(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()
fail.Request(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()
fail.Request(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()
fail.Request(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()
log.WithRequest(r).WithError(fmt.Errorf("SendURL: Copy response: %v", err)).Error()
return
}
sendURLRequestsSucceeded.Inc()
}
|