131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
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>
|
|
`))
|