2017-09-13 23:04:20 +00:00
|
|
|
/*
|
|
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"mime"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
2018-02-14 17:16:44 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2020-03-17 09:58:39 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2018-02-14 17:16:44 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2017-09-13 23:04:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2017-12-02 15:02:00 +00:00
|
|
|
// FormatHeader name of the header used to extract the format
|
2017-09-13 23:04:20 +00:00
|
|
|
FormatHeader = "X-Format"
|
|
|
|
|
2019-03-22 10:33:11 +00:00
|
|
|
// CodeHeader name of the header used as source of the HTTP status code to return
|
2017-09-13 23:04:20 +00:00
|
|
|
CodeHeader = "X-Code"
|
|
|
|
|
2017-12-02 15:02:00 +00:00
|
|
|
// ContentType name of the header that defines the format of the reply
|
2017-09-13 23:04:20 +00:00
|
|
|
ContentType = "Content-Type"
|
2018-06-12 22:03:48 +00:00
|
|
|
|
2018-06-17 19:41:39 +00:00
|
|
|
// OriginalURI name of the header with the original URL from NGINX
|
|
|
|
OriginalURI = "X-Original-URI"
|
|
|
|
|
|
|
|
// Namespace name of the header that contains information about the Ingress namespace
|
|
|
|
Namespace = "X-Namespace"
|
|
|
|
|
|
|
|
// IngressName name of the header that contains the matched Ingress
|
|
|
|
IngressName = "X-Ingress-Name"
|
|
|
|
|
|
|
|
// ServiceName name of the header that contains the matched Service in the Ingress
|
|
|
|
ServiceName = "X-Service-Name"
|
|
|
|
|
|
|
|
// ServicePort name of the header that contains the matched Service port in the Ingress
|
|
|
|
ServicePort = "X-Service-Port"
|
|
|
|
|
2019-03-22 10:59:58 +00:00
|
|
|
// RequestId is a unique ID that identifies the request - same as for backend service
|
2019-03-22 10:33:11 +00:00
|
|
|
RequestId = "X-Request-ID"
|
|
|
|
|
2018-06-12 22:03:48 +00:00
|
|
|
// ErrFilesPathVar is the name of the environment variable indicating
|
|
|
|
// the location on disk of files served by the handler.
|
|
|
|
ErrFilesPathVar = "ERROR_FILES_PATH"
|
2021-08-06 20:27:29 +00:00
|
|
|
|
|
|
|
// DefaultFormatVar is the name of the environment variable indicating
|
|
|
|
// the default error MIME type that should be returned if either the
|
|
|
|
// client does not specify an Accept header, or the Accept header provided
|
|
|
|
// cannot be mapped to a file extension.
|
|
|
|
DefaultFormatVar = "DEFAULT_RESPONSE_FORMAT"
|
2017-09-13 23:04:20 +00:00
|
|
|
)
|
|
|
|
|
2020-03-17 09:58:39 +00:00
|
|
|
func init() {
|
|
|
|
prometheus.MustRegister(requestCount)
|
|
|
|
prometheus.MustRegister(requestDuration)
|
|
|
|
}
|
|
|
|
|
2017-09-13 23:04:20 +00:00
|
|
|
func main() {
|
2018-06-12 22:03:48 +00:00
|
|
|
errFilesPath := "/www"
|
|
|
|
if os.Getenv(ErrFilesPathVar) != "" {
|
|
|
|
errFilesPath = os.Getenv(ErrFilesPathVar)
|
2017-09-13 23:04:20 +00:00
|
|
|
}
|
2018-02-14 17:16:44 +00:00
|
|
|
|
2021-08-06 20:27:29 +00:00
|
|
|
defaultFormat := "text/html"
|
|
|
|
if os.Getenv(DefaultFormatVar) != "" {
|
|
|
|
defaultFormat = os.Getenv(DefaultFormatVar)
|
|
|
|
}
|
|
|
|
|
|
|
|
http.HandleFunc("/", errorHandler(errFilesPath, defaultFormat))
|
2018-02-14 17:16:44 +00:00
|
|
|
|
|
|
|
http.Handle("/metrics", promhttp.Handler())
|
|
|
|
|
|
|
|
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
})
|
|
|
|
|
2017-09-13 23:04:20 +00:00
|
|
|
http.ListenAndServe(fmt.Sprintf(":8080"), nil)
|
|
|
|
}
|
|
|
|
|
2021-08-06 20:27:29 +00:00
|
|
|
func errorHandler(path, defaultFormat string) func(http.ResponseWriter, *http.Request) {
|
|
|
|
defaultExts, err := mime.ExtensionsByType(defaultFormat)
|
|
|
|
if err != nil || len(defaultExts) == 0 {
|
|
|
|
panic("couldn't get file extension for default format")
|
|
|
|
}
|
|
|
|
defaultExt := defaultExts[0]
|
|
|
|
|
2017-09-13 23:04:20 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2018-02-14 17:16:44 +00:00
|
|
|
start := time.Now()
|
2021-08-06 20:27:29 +00:00
|
|
|
ext := defaultExt
|
2017-09-13 23:04:20 +00:00
|
|
|
|
2018-06-17 19:41:39 +00:00
|
|
|
if os.Getenv("DEBUG") != "" {
|
|
|
|
w.Header().Set(FormatHeader, r.Header.Get(FormatHeader))
|
|
|
|
w.Header().Set(CodeHeader, r.Header.Get(CodeHeader))
|
|
|
|
w.Header().Set(ContentType, r.Header.Get(ContentType))
|
|
|
|
w.Header().Set(OriginalURI, r.Header.Get(OriginalURI))
|
|
|
|
w.Header().Set(Namespace, r.Header.Get(Namespace))
|
|
|
|
w.Header().Set(IngressName, r.Header.Get(IngressName))
|
|
|
|
w.Header().Set(ServiceName, r.Header.Get(ServiceName))
|
|
|
|
w.Header().Set(ServicePort, r.Header.Get(ServicePort))
|
2019-03-22 10:33:11 +00:00
|
|
|
w.Header().Set(RequestId, r.Header.Get(RequestId))
|
2018-06-17 19:41:39 +00:00
|
|
|
}
|
|
|
|
|
2017-09-13 23:04:20 +00:00
|
|
|
format := r.Header.Get(FormatHeader)
|
|
|
|
if format == "" {
|
2021-08-06 20:27:29 +00:00
|
|
|
format = defaultFormat
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("format not specified. Using %v", format)
|
2017-09-13 23:04:20 +00:00
|
|
|
}
|
|
|
|
|
2018-06-12 22:03:48 +00:00
|
|
|
cext, err := mime.ExtensionsByType(format)
|
2017-09-13 23:04:20 +00:00
|
|
|
if err != nil {
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("unexpected error reading media type extension: %v. Using %v", err, ext)
|
2021-08-06 20:27:29 +00:00
|
|
|
format = defaultFormat
|
2018-02-14 17:16:44 +00:00
|
|
|
} else if len(cext) == 0 {
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("couldn't get media type extension. Using %v", ext)
|
2017-09-13 23:04:20 +00:00
|
|
|
} else {
|
|
|
|
ext = cext[0]
|
|
|
|
}
|
|
|
|
w.Header().Set(ContentType, format)
|
|
|
|
|
|
|
|
errCode := r.Header.Get(CodeHeader)
|
|
|
|
code, err := strconv.Atoi(errCode)
|
|
|
|
if err != nil {
|
|
|
|
code = 404
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("unexpected error reading return code: %v. Using %v", err, code)
|
2017-09-13 23:04:20 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(code)
|
|
|
|
|
2018-02-14 17:16:44 +00:00
|
|
|
if !strings.HasPrefix(ext, ".") {
|
|
|
|
ext = "." + ext
|
|
|
|
}
|
2021-08-06 20:27:29 +00:00
|
|
|
// special case for compatibility
|
|
|
|
if ext == ".htm" {
|
|
|
|
ext = ".html"
|
|
|
|
}
|
2017-09-13 23:04:20 +00:00
|
|
|
file := fmt.Sprintf("%v/%v%v", path, code, ext)
|
|
|
|
f, err := os.Open(file)
|
|
|
|
if err != nil {
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("unexpected error opening file: %v", err)
|
2017-09-13 23:04:20 +00:00
|
|
|
scode := strconv.Itoa(code)
|
|
|
|
file := fmt.Sprintf("%v/%cxx%v", path, scode[0], ext)
|
|
|
|
f, err := os.Open(file)
|
|
|
|
if err != nil {
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("unexpected error opening file: %v", err)
|
2017-09-13 23:04:20 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("serving custom error response for code %v and format %v from file %v", code, format, file)
|
2017-09-13 23:04:20 +00:00
|
|
|
io.Copy(w, f)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2018-06-12 22:03:48 +00:00
|
|
|
log.Printf("serving custom error response for code %v and format %v from file %v", code, format, file)
|
2017-09-13 23:04:20 +00:00
|
|
|
io.Copy(w, f)
|
2018-02-14 17:16:44 +00:00
|
|
|
|
|
|
|
duration := time.Now().Sub(start).Seconds()
|
|
|
|
|
|
|
|
proto := strconv.Itoa(r.ProtoMajor)
|
|
|
|
proto = fmt.Sprintf("%s.%s", proto, strconv.Itoa(r.ProtoMinor))
|
|
|
|
|
|
|
|
requestCount.WithLabelValues(proto).Inc()
|
|
|
|
requestDuration.WithLabelValues(proto).Observe(duration)
|
2017-09-13 23:04:20 +00:00
|
|
|
}
|
|
|
|
}
|