debian-mirror-gitlab/workhorse/internal/badgateway/roundtripper.go

138 lines
3.5 KiB
Go
Raw Normal View History

2021-02-22 17:27:13 +05:30
package badgateway
import (
"bytes"
2023-03-17 16:20:25 +05:30
"context"
2022-08-13 15:12:31 +05:30
_ "embed"
"encoding/base64"
2021-02-22 17:27:13 +05:30
"fmt"
"html/template"
2022-07-23 23:45:48 +05:30
"io"
2021-02-22 17:27:13 +05:30
"net/http"
"strings"
"time"
2021-10-27 15:23:28 +05:30
"gitlab.com/gitlab-org/gitlab/workhorse/internal/log"
2021-02-22 17:27:13 +05:30
)
2022-08-13 15:12:31 +05:30
//go:embed embed/gitlab-logo-500.png
var gitlabLogo []byte
2021-02-22 17:27:13 +05:30
// 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)}
2021-03-08 18:12:59 +05:30
log.WithRequest(r).WithFields(fields).WithError(&sentryError{fmt.Errorf("badgateway: failed to receive response: %v", err)}).Error()
2021-02-22 17:27:13 +05:30
2023-03-17 16:20:25 +05:30
code := http.StatusBadGateway
if r.Context().Err() == context.Canceled {
code = 499 // Code used by NGINX when client disconnects
}
2021-02-22 17:27:13 +05:30
injectedResponse := &http.Response{
2023-03-17 16:20:25 +05:30
StatusCode: code,
Status: http.StatusText(code),
2021-02-22 17:27:13 +05:30
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)
}
2022-07-23 23:45:48 +05:30
injectedResponse.Body = io.NopCloser(strings.NewReader(message))
2021-02-22 17:27:13 +05:30
injectedResponse.Header.Set("Content-Type", contentType)
return injectedResponse, nil
}
func developmentModeResponse(err error) (body string, contentType string) {
data := TemplateData{
2022-08-13 15:12:31 +05:30
Time: time.Now().Format("15:04:05"),
Error: err.Error(),
ReloadSeconds: 5,
Base64EncodedGitLabLogo: base64.StdEncoding.EncodeToString(gitlabLogo),
2021-02-22 17:27:13 +05:30
}
buf := &bytes.Buffer{}
if err := developmentErrorTemplate.Execute(buf, data); err != nil {
return data.Error, "text/plain"
}
return buf.String(), "text/html"
}
type TemplateData struct {
2022-08-13 15:12:31 +05:30
Time string
Error string
ReloadSeconds int
Base64EncodedGitLabLogo string
2021-02-22 17:27:13 +05:30
}
var developmentErrorTemplate = template.Must(template.New("error502").Parse(`
2022-08-13 15:12:31 +05:30
<!DOCTYPE html>
<html lang="en">
2021-02-22 17:27:13 +05:30
<head>
2022-08-13 15:12:31 +05:30
<title>Waiting for GitLab to boot</title>
2021-02-22 17:27:13 +05:30
</head>
<body>
2022-08-13 15:12:31 +05:30
<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>
2021-02-22 17:27:13 +05:30
2022-08-13 15:12:31 +05:30
<br/>
2021-02-22 17:27:13 +05:30
2022-08-13 15:12:31 +05:30
<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>
2021-02-22 17:27:13 +05:30
</body>
2022-08-13 15:12:31 +05:30
2021-02-22 17:27:13 +05:30
</html>
`))