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
|
package badgateway
import (
"bytes"
_ "embed"
"encoding/base64"
"fmt"
"html/template"
"io"
"net/http"
"strings"
"time"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/log"
)
//go:embed embed/gitlab-logo-500.png
var gitlabLogo []byte
// Error is a custom error for pretty Sentry 'issues'
type sentryError struct{ error }
type roundTripper struct {
next http.RoundTripper
developmentMode bool
}
// NewRoundTripper creates a RoundTripper with a provided underlying next transport
func NewRoundTripper(developmentMode bool, next http.RoundTripper) http.RoundTripper {
return &roundTripper{next: next, developmentMode: developmentMode}
}
func (t *roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
start := time.Now()
res, err := t.next.RoundTrip(r)
if err == nil {
return res, err
}
// httputil.ReverseProxy translates all errors from this
// RoundTrip function into 500 errors. But the most likely error
// is that the Rails app is not responding, in which case users
// and administrators expect to see a 502 error. To show 502s
// instead of 500s we catch the RoundTrip error here and inject a
// 502 response.
fields := log.Fields{"duration_ms": int64(time.Since(start).Seconds() * 1000)}
log.WithRequest(r).WithFields(fields).WithError(&sentryError{fmt.Errorf("badgateway: failed to receive response: %v", err)}).Error()
injectedResponse := &http.Response{
StatusCode: http.StatusBadGateway,
Status: http.StatusText(http.StatusBadGateway),
Request: r,
ProtoMajor: r.ProtoMajor,
ProtoMinor: r.ProtoMinor,
Proto: r.Proto,
Header: make(http.Header),
Trailer: make(http.Header),
}
message := "GitLab is not responding"
contentType := "text/plain"
if t.developmentMode {
message, contentType = developmentModeResponse(err)
}
injectedResponse.Body = io.NopCloser(strings.NewReader(message))
injectedResponse.Header.Set("Content-Type", contentType)
return injectedResponse, nil
}
func developmentModeResponse(err error) (body string, contentType string) {
data := TemplateData{
Time: time.Now().Format("15:04:05"),
Error: err.Error(),
ReloadSeconds: 5,
Base64EncodedGitLabLogo: base64.StdEncoding.EncodeToString(gitlabLogo),
}
buf := &bytes.Buffer{}
if err := developmentErrorTemplate.Execute(buf, data); err != nil {
return data.Error, "text/plain"
}
return buf.String(), "text/html"
}
type TemplateData struct {
Time string
Error string
ReloadSeconds int
Base64EncodedGitLabLogo string
}
var developmentErrorTemplate = template.Must(template.New("error502").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<title>Waiting for GitLab to boot</title>
</head>
<body>
<div style="text-align: center; font-family: Source Sans Pro, sans-serif">
<img style="padding: 60px 0" src="data:image/png;base64,{{.Base64EncodedGitLabLogo}}" alt="GitLab" />
<h1>Waiting for GitLab to boot</h1>
<pre>{{.Error}}</pre>
<br/>
<p>It can take 60-180 seconds for GitLab to boot completely.</p>
<p>This page will automatically reload every {{.ReloadSeconds}} seconds.</p>
<br/>
<footer>
<p>Generated by gitlab-workhorse running in development mode at {{.Time}}.</p>
</footer>
</div>
<script>
window.setTimeout(function() { location.reload(); }, {{.ReloadSeconds}} * 1000)
</script>
</body>
</html>
`))
|