Skip to content
This repository was archived by the owner on Apr 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ type Config struct {
HTTPRewrite HTTPRewrite `yaml:"http_rewrite,omitempty"`

EmptyPoolResponseCode503 bool `yaml:"empty_pool_response_code_503,omitempty"`

HTMLErrorTemplateFile string `yaml:"html_error_template_file,omitempty"`
}

var defaultConfig = Config{
Expand Down
11 changes: 11 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,17 @@ backends:
Expect(err).ToNot(HaveOccurred())
Expect(config.DisableHTTP).To(BeTrue())
})

It("defaults HTMLErrorTemplateFile to empty", func() {
Expect(config.HTMLErrorTemplateFile).To(Equal(""))
})

It("sets HTMLErrorTemplateFile", func() {
var b = []byte(`html_error_template_file: "/path/to/file"`)
err := config.Initialize(b)
Expect(err).ToNot(HaveOccurred())
Expect(config.HTMLErrorTemplateFile).To(Equal("/path/to/file"))
})
})

Describe("Process", func() {
Expand Down
123 changes: 123 additions & 0 deletions errorwriter/error_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package errorwriter

import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"net/http"

"code.cloudfoundry.org/gorouter/logger"
"github.com/uber-go/zap"
)

type ErrorWriter interface {
WriteError(
rw http.ResponseWriter,
code int,
message string,
logger logger.Logger,
)
}

type plaintextErrorWriter struct{}

func NewPlaintextErrorWriter() ErrorWriter {
return &plaintextErrorWriter{}
}

// WriteStatus attempts to template an error message.
func (ew *plaintextErrorWriter) WriteError(
rw http.ResponseWriter,
code int,
message string,
logger logger.Logger,
) {
body := fmt.Sprintf("%d %s: %s", code, http.StatusText(code), message)

if code != http.StatusNotFound {
logger.Info("status", zap.String("body", body))
}

if code > 299 {
rw.Header().Del("Connection")
}

rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
rw.Header().Set("X-Content-Type-Options", "nosniff")

rw.WriteHeader(code)
fmt.Fprintln(rw, body)
}

type htmlErrorWriter struct {
tpl *template.Template
}

type htmlErrorWriterContext struct {
Status int
StatusText string
Message string
Header http.Header
}

func NewHTMLErrorWriterFromFile(path string) (ErrorWriter, error) {
ew := &htmlErrorWriter{}

bytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("Could not read HTML error template file: %s", err)
}

tpl, err := template.New("error-message").Parse(string(bytes))
if err != nil {
return nil, err
}
ew.tpl = tpl

return ew, nil
}

// WriteStatus attempts to template an error message.
// If the template cannot be rendered then text will be sent instead
// and the error will be returned even though the response has been sent
func (ew *htmlErrorWriter) WriteError(
rw http.ResponseWriter,
code int,
message string,
logger logger.Logger,
) {
body := fmt.Sprintf("%d %s: %s", code, http.StatusText(code), message)

if code != http.StatusNotFound {
logger.Info("status", zap.String("body", body))
}

if code > 299 {
rw.Header().Del("Connection")
}

tplContext := htmlErrorWriterContext{
Status: code,
StatusText: http.StatusText(code),
Message: message,
Header: rw.Header(),
}
rw.Header().Set("Content-Type", "text/html; charset=utf-8")

var respBytes []byte
var rendered bytes.Buffer
if err := ew.tpl.Execute(&rendered, &tplContext); err != nil {
logger.Error("render-error-failed", zap.Error(err))
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
rw.Header().Set("X-Content-Type-Options", "nosniff")
respBytes = []byte(body)
} else {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.Header().Set("X-Content-Type-Options", "nosniff")
respBytes = rendered.Bytes()
}

rw.WriteHeader(code)
rw.Write(respBytes)
}
Loading