diff --git a/cmd/manager/deployments.go b/cmd/manager/deployments.go index 9076a7d89..ad07f3ec7 100644 --- a/cmd/manager/deployments.go +++ b/cmd/manager/deployments.go @@ -43,27 +43,6 @@ import ( "github.com/kubernetes/helm/pkg/util" ) -var deployments = []Route{ - {"CreateDeployment", "/deployments", "POST", createDeploymentHandlerFunc, "JSON"}, - {"DeleteDeplyment", "/deployments/{deployment}", "DELETE", deleteDeploymentHandlerFunc, ""}, - {"PutDeployment", "/deployments/{deployment}", "PUT", putDeploymentHandlerFunc, "JSON"}, - {"ListManifests", "/deployments/{deployment}/manifests", "GET", listManifestsHandlerFunc, ""}, - {"GetManifest", "/deployments/{deployment}/manifests/{manifest}", "GET", getManifestHandlerFunc, ""}, - {"Expand", "/expand", "POST", expandHandlerFunc, ""}, - {"ListTypes", "/types", "GET", listTypesHandlerFunc, ""}, - {"ListTypeInstances", "/types/{type}/instances", "GET", listTypeInstancesHandlerFunc, ""}, - {"GetRegistryForType", "/types/{type}/registry", "GET", getRegistryForTypeHandlerFunc, ""}, - {"GetMetadataForType", "/types/{type}/metadata", "GET", getMetadataForTypeHandlerFunc, ""}, - {"ListRegistries", "/registries", "GET", listRegistriesHandlerFunc, ""}, - {"GetRegistry", "/registries/{registry}", "GET", getRegistryHandlerFunc, ""}, - {"CreateRegistry", "/registries/{registry}", "POST", createRegistryHandlerFunc, "JSON"}, - {"ListRegistryTypes", "/registries/{registry}/types", "GET", listRegistryTypesHandlerFunc, ""}, - {"GetDownloadURLs", "/registries/{registry}/types/{type}", "GET", getDownloadURLsHandlerFunc, ""}, - {"GetFile", "/registries/{registry}/download", "GET", getFileHandlerFunc, ""}, - {"CreateCredential", "/credentials/{credential}", "POST", createCredentialHandlerFunc, "JSON"}, - {"GetCredential", "/credentials/{credential}", "GET", getCredentialHandlerFunc, ""}, -} - // Deprecated. Use Context.Manager instead. var backend manager.Manager @@ -79,21 +58,27 @@ type Route struct { } func registerDeploymentRoutes(c *router.Context, h *router.Handler) { - re := regexp.MustCompile("{[a-z]+}") - h.Add("GET /healthz", healthz) h.Add("GET /deployments", listDeploymentsHandlerFunc) h.Add("GET /deployments/*", getDeploymentHandlerFunc) - - // TODO: Replace these routes with updated ones. - for _, d := range deployments { - path := fmt.Sprintf("%s %s", d.Methods, re.ReplaceAllString(d.Path, "*")) - fmt.Printf("\t%s\n", path) - h.Add(path, func(w http.ResponseWriter, r *http.Request, c *router.Context) error { - d.HandlerFunc(w, r) - return nil - }) - } + h.Add("POST /deployments", createDeploymentHandlerFunc) + h.Add("DELETE /deployments/*", deleteDeploymentHandlerFunc) + h.Add("PUT /deployments/*", putDeploymentHandlerFunc) + h.Add("GET /deployments/*/manifests", listManifestsHandlerFunc) + h.Add("GET /deployments/*/manifests/*", getManifestHandlerFunc) + h.Add("POST /expand", expandHandlerFunc) + h.Add("GET /types", listTypesHandlerFunc) + h.Add("GET /types/*/instances", listTypeInstancesHandlerFunc) + h.Add("GET /types/*/registry", getRegistryForTypeHandlerFunc) + h.Add("GET /types/*/metadata", getMetadataForTypeHandlerFunc) + h.Add("GET /registries", listRegistriesHandlerFunc) + h.Add("GET /registries/*", getRegistryHandlerFunc) + h.Add("POST /registries/*", createRegistryHandlerFunc) + h.Add("GET /registries/*/types", listRegistryTypesHandlerFunc) + h.Add("GET /registries/*/types/*", getDownloadURLsHandlerFunc) + h.Add("GET /registries/*/download", getFileHandlerFunc) + h.Add("POST /credentials/*", createCredentialHandlerFunc) + h.Add("GET /credentials/*", getCredentialHandlerFunc) } func healthz(w http.ResponseWriter, r *http.Request, c *router.Context) error { @@ -207,7 +192,7 @@ func listDeploymentsHandlerFunc(w http.ResponseWriter, r *http.Request, c *route func getDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get deployment" util.LogHandlerEntry(handler, r) - name, err := getPathVariable(w, r, "deployment", handler) + name, err := pos(w, r, 2) if err != nil { return nil } @@ -222,7 +207,7 @@ func getDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router. return nil } -func createDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request) { +func createDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: create deployment" util.LogHandlerEntry(handler, r) defer r.Body.Close() @@ -230,52 +215,53 @@ func createDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request) { if t != nil { d, err := backend.CreateDeployment(t) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, d, http.StatusCreated) - return } + return nil } -func deleteDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request) { +func deleteDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: delete deployment" util.LogHandlerEntry(handler, r) defer r.Body.Close() - name, err := pos(w, r, 2) //getPathVariable(w, r, "deployment", handler) + name, err := pos(w, r, 2) if err != nil { - return + return err } - d, err := backend.DeleteDeployment(name, true) + d, err := c.Manager.DeleteDeployment(name, true) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + return err } util.LogHandlerExitWithJSON(handler, w, d, http.StatusOK) + return nil } -func putDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request) { +func putDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: update deployment" util.LogHandlerEntry(handler, r) defer r.Body.Close() - name, err := pos(w, r, 2) //getPathVariable(w, r, "deployment", handler) + name, err := pos(w, r, 2) if err != nil { - return + return err } t := getTemplate(w, r, handler) if t != nil { d, err := backend.PutDeployment(name, t) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, d, http.StatusCreated) } + return nil } // pos gets a path item by position. @@ -288,7 +274,6 @@ func putDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request) { func pos(w http.ResponseWriter, r *http.Request, i int) (string, error) { parts := strings.Split(r.URL.Path, "/") if len(parts) < i-1 { - httputil.BadRequest(w, r) return "", fmt.Errorf("No index for %d", i) } return parts[i], nil @@ -331,18 +316,17 @@ func getTemplate(w http.ResponseWriter, r *http.Request, handler string) *common return t } -func listManifestsHandlerFunc(w http.ResponseWriter, r *http.Request) { +func listManifestsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: list manifests" util.LogHandlerEntry(handler, r) deploymentName, err := pos(w, r, 2) if err != nil { - return + return err } m, err := backend.ListManifests(deploymentName) if err != nil { - util.LogAndReturnError(handler, http.StatusInternalServerError, err, w) - return + return err } var manifestNames []string @@ -351,31 +335,33 @@ func listManifestsHandlerFunc(w http.ResponseWriter, r *http.Request) { } util.LogHandlerExitWithJSON(handler, w, manifestNames, http.StatusOK) + return nil } -func getManifestHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getManifestHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get manifest" util.LogHandlerEntry(handler, r) deploymentName, err := pos(w, r, 2) if err != nil { - return + return err } manifestName, err := pos(w, r, 4) if err != nil { - return + return err } m, err := backend.GetManifest(deploymentName, manifestName) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, m, http.StatusOK) + return nil } -func expandHandlerFunc(w http.ResponseWriter, r *http.Request) { +func expandHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: expand config" util.LogHandlerEntry(handler, r) defer r.Body.Close() @@ -383,108 +369,114 @@ func expandHandlerFunc(w http.ResponseWriter, r *http.Request) { if t != nil { c, err := backend.Expand(t) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, c, http.StatusCreated) - return } + return nil } // Putting Type handlers here for now because deployments.go // currently owns its own Manager backend and doesn't like to share. -func listTypesHandlerFunc(w http.ResponseWriter, r *http.Request) { +func listTypesHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: list types" util.LogHandlerEntry(handler, r) types, err := backend.ListTypes() if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, types, http.StatusOK) + return nil } -func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) { +func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: list instances" util.LogHandlerEntry(handler, r) typeName, err := pos(w, r, 2) if err != nil { - return + return err } instances, err := backend.ListInstances(typeName) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, instances, http.StatusOK) + return nil } -func getRegistryForTypeHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getRegistryForTypeHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get type registry" util.LogHandlerEntry(handler, r) typeName, err := pos(w, r, 2) if err != nil { - return + return err } registry, err := backend.GetRegistryForType(typeName) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, registry, http.StatusOK) + return nil } -func getMetadataForTypeHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getMetadataForTypeHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get type metadata" util.LogHandlerEntry(handler, r) typeName, err := pos(w, r, 2) if err != nil { - return + return err } metadata, err := backend.GetMetadataForType(typeName) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, metadata, http.StatusOK) + return nil } // Putting Registry handlers here for now because deployments.go // currently owns its own Manager backend and doesn't like to share. -func listRegistriesHandlerFunc(w http.ResponseWriter, r *http.Request) { +func listRegistriesHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: list registries" util.LogHandlerEntry(handler, r) registries, err := backend.ListRegistries() if err != nil { - return + return err } util.LogHandlerExitWithJSON(handler, w, registries, http.StatusOK) + return nil } -func getRegistryHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getRegistryHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get registry" util.LogHandlerEntry(handler, r) registryName, err := pos(w, r, 2) if err != nil { - return + return err } cr, err := backend.GetRegistry(registryName) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, cr, http.StatusOK) + return nil } func getRegistry(w http.ResponseWriter, r *http.Request, handler string) *common.Registry { @@ -504,44 +496,45 @@ func getRegistry(w http.ResponseWriter, r *http.Request, handler string) *common return t } -func createRegistryHandlerFunc(w http.ResponseWriter, r *http.Request) { +func createRegistryHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: create registry" util.LogHandlerEntry(handler, r) defer r.Body.Close() registryName, err := pos(w, r, 2) if err != nil { - return + return err } reg := getRegistry(w, r, handler) if reg.Name != registryName { e := fmt.Errorf("Registry name does not match %s != %s", reg.Name, registryName) - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return + httputil.BadRequest(w, r, e) + return nil } if reg != nil { err = backend.CreateRegistry(reg) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } } util.LogHandlerExitWithJSON(handler, w, reg, http.StatusOK) + return nil } -func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request) { +func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: list registry types" util.LogHandlerEntry(handler, r) registryName, err := pos(w, r, 2) if err != nil { - return + return err } values, err := url.ParseQuery(r.URL.RawQuery) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } var regex *regexp.Regexp @@ -549,72 +542,73 @@ func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request) { if regexString != "" { regex, err = regexp.Compile(regexString) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } } registryTypes, err := backend.ListRegistryTypes(registryName, regex) if err != nil { - util.LogAndReturnError(handler, http.StatusInternalServerError, err, w) - return + return err } util.LogHandlerExitWithJSON(handler, w, registryTypes, http.StatusOK) + return nil } -func getDownloadURLsHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getDownloadURLsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get download URLs" util.LogHandlerEntry(handler, r) registryName, err := pos(w, r, 2) if err != nil { - return + return err } typeName, err := pos(w, r, 4) if err != nil { - return + return err } tt, err := registry.ParseType(typeName) if err != nil { - util.LogAndReturnError(handler, http.StatusInternalServerError, err, w) - return + return err } - c, err := backend.GetDownloadURLs(registryName, tt) + cr, err := backend.GetDownloadURLs(registryName, tt) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } urls := []string{} - for _, u := range c { + for _, u := range cr { urls = append(urls, u.String()) } util.LogHandlerExitWithJSON(handler, w, urls, http.StatusOK) + return nil } -func getFileHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getFileHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get file" util.LogHandlerEntry(handler, r) registryName, err := pos(w, r, 2) if err != nil { - return + return err } file := r.FormValue("file") if file == "" { - return + return err } b, err := backend.GetFile(registryName, file) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } util.LogHandlerExitWithJSON(handler, w, b, http.StatusOK) + return nil } func getCredential(w http.ResponseWriter, r *http.Request, handler string) *common.RegistryCredential { @@ -634,42 +628,44 @@ func getCredential(w http.ResponseWriter, r *http.Request, handler string) *comm return t } -func createCredentialHandlerFunc(w http.ResponseWriter, r *http.Request) { +func createCredentialHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: create credential" util.LogHandlerEntry(handler, r) defer r.Body.Close() credentialName, err := pos(w, r, 2) if err != nil { - return + return err } - c := getCredential(w, r, handler) - if c != nil { - err = backend.CreateCredential(credentialName, c) + cr := getCredential(w, r, handler) + if cr != nil { + err = backend.CreateCredential(credentialName, cr) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } } util.LogHandlerExitWithJSON(handler, w, c, http.StatusOK) + return nil } -func getCredentialHandlerFunc(w http.ResponseWriter, r *http.Request) { +func getCredentialHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { handler := "manager: get credential" util.LogHandlerEntry(handler, r) credentialName, err := pos(w, r, 2) if err != nil { - return + return err } - c, err := backend.GetCredential(credentialName) + cr, err := backend.GetCredential(credentialName) if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return + httputil.BadRequest(w, r, err) + return nil } - util.LogHandlerExitWithJSON(handler, w, c, http.StatusOK) + util.LogHandlerExitWithJSON(handler, w, cr, http.StatusOK) + return nil } func getJSONFromRequest(w http.ResponseWriter, r *http.Request, handler string) ([]byte, error) { diff --git a/cmd/manager/router/router.go b/cmd/manager/router/router.go index 3ed685061..65d1e86fe 100644 --- a/cmd/manager/router/router.go +++ b/cmd/manager/router/router.go @@ -31,6 +31,7 @@ package router import ( "log" "net/http" + "reflect" "github.com/Masterminds/httputil" helmhttp "github.com/kubernetes/helm/pkg/httputil" @@ -68,6 +69,7 @@ func NewHandler(c *Context) *Handler { // // The route name is "VERB /ENPOINT/PATH", e.g. "GET /foo". func (h *Handler) Add(route string, fn HandlerFunc) { + log.Printf("Map %q to %s", route, reflect.ValueOf(fn).Type().Name()) h.routes[route] = fn h.paths = append(h.paths, route) h.resolver = httputil.NewResolver(h.paths) @@ -84,6 +86,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fn, ok := h.routes[route] if !ok { + // This is a 500 because the route was registered, but not here. helmhttp.Fatal(w, r, "route %s missing", route) } diff --git a/pkg/httputil/encoder.go b/pkg/httputil/encoder.go index beefd20c0..df5cfb428 100644 --- a/pkg/httputil/encoder.go +++ b/pkg/httputil/encoder.go @@ -28,6 +28,9 @@ import ( "github.com/ghodss/yaml" ) +// DefaultEncoder is an *AcceptEncoder with the default application/json encoding. +var DefaultEncoder = &AcceptEncoder{DefaultEncoding: "application/json"} + // Encoder takes input and translate it to an expected encoded output. // // Implementations of encoders may use details of the HTTP request and response @@ -40,7 +43,9 @@ type Encoder interface { // // When an encoder fails, it logs any necessary data and then responds to // the client. - Encode(http.ResponseWriter, *http.Request, interface{}) + // + // The integer must be a valid http.Status* status code. + Encode(http.ResponseWriter, *http.Request, interface{}, int) } // AcceptEncoder uses the accept headers on a request to determine the response type. @@ -56,7 +61,7 @@ type AcceptEncoder struct { } // Encode encodeds the given interface to the first available type in the Accept header. -func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out interface{}) { +func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out interface{}, statusCode int) { a := r.Header.Get("accept") fn := encoders[e.DefaultEncoding] mt := e.DefaultEncoding @@ -70,6 +75,7 @@ func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out inter return } w.Header().Add("content-type", mt) + w.WriteHeader(statusCode) w.Write(data) } diff --git a/pkg/httputil/encoder_test.go b/pkg/httputil/encoder_test.go index 115c961d5..71ddc19eb 100644 --- a/pkg/httputil/encoder_test.go +++ b/pkg/httputil/encoder_test.go @@ -77,7 +77,7 @@ func TestAcceptEncoder(t *testing.T) { DefaultEncoding: "application/json", } fn := func(w http.ResponseWriter, r *http.Request) { - enc.Encode(w, r, []string{"hello", "world"}) + enc.Encode(w, r, []string{"hello", "world"}, http.StatusOK) } s := httptest.NewServer(http.HandlerFunc(fn)) defer s.Close() diff --git a/pkg/httputil/httperrors.go b/pkg/httputil/httperrors.go index c13a2a6bf..2a2577b39 100644 --- a/pkg/httputil/httperrors.go +++ b/pkg/httputil/httperrors.go @@ -30,27 +30,46 @@ const ( // LogFatal is for logging 500 errors. Form: Internal Server Error r.Method r.URL message LogFatal = "Internal Server Error: %s %s %s" // LogBadRequest logs 400 errors. - LogBadRequest = "Bad Request: %s %s" + LogBadRequest = "Bad Request: %s %s %s" ) +// Error represents an HTTP error that can be converted to structured types. +// +// For example, and error can be serialized to JSON or YAML. Likewise, the +// string marshal can convert it to a string. +type Error struct { + Msg string `json:"message, omitempty"` + Status string `json:"status"` +} + +// Error implements the error interface. +func (e *Error) Error() string { + return fmt.Sprintf("%s: %s", e.Status, e.Msg) +} + // NotFound writes a 404 error to the client and logs an error. func NotFound(w http.ResponseWriter, r *http.Request) { - log.Printf(LogNotFound, r.Method, r.URL) - w.WriteHeader(http.StatusNotFound) - fmt.Fprintln(w, "File Not Found") + msg := fmt.Sprintf(LogNotFound, r.Method, r.URL) + log.Println(msg) + writeErr(w, r, msg, http.StatusNotFound) +} + +// BadRequest writes an HTTP 400. +func BadRequest(w http.ResponseWriter, r *http.Request, err error) { + log.Printf(LogBadRequest, r.Method, r.URL, err) + writeErr(w, r, err.Error(), http.StatusBadRequest) } -func BadRequest(w http.ResponseWriter, r *http.Request) { - log.Printf(LogNotFound, r.Method, r.URL) - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, "Bad Request") +// writeErr formats and writes the error using the default encoder. +func writeErr(w http.ResponseWriter, r *http.Request, msg string, status int) { + DefaultEncoder.Encode(w, r, &Error{Status: http.StatusText(status), Msg: msg}, status) } // Fatal writes a 500 response to the client and logs the message. // // Additional arguments are past into the the formatter as params to msg. func Fatal(w http.ResponseWriter, r *http.Request, msg string, v ...interface{}) { - log.Printf(LogFatal, r.Method, r.URL, fmt.Sprintf(msg, v...)) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintln(w, "Internal Server Error") + m := fmt.Sprintf(msg, v...) + log.Printf(LogFatal, r.Method, r.URL, m) + writeErr(w, r, m, http.StatusInternalServerError) }