Skip to content

Commit

Permalink
fileserver: Add status code override (#4076)
Browse files Browse the repository at this point in the history
After reading a question about the `handle_response` feature of `reverse_proxy`, I realized that we didn't have a way of serving an arbitrary file with a status code other than 200. This is an issue in situations where you want to serve a custom error page in routes that are not errors, like the aforementioned `handle_response`, where you may want to retain the status code returned by the proxy but write a response with content from a file.

This feature is super simple, basically if a status code is configured (can be a status code number, or a placeholder string) then that status will be written out before serving the file - if we write the status code first, then the stdlib won't write its own (only the first HTTP status header wins).
  • Loading branch information
francislavoie authored Apr 8, 2021
1 parent 45fb720 commit 3f6283b
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 0 deletions.
112 changes: 112 additions & 0 deletions caddytest/integration/caddyfile_adapt/file_server_status.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
localhost

root * /srv

handle /nope* {
file_server {
status 403
}
}

handle /custom-status* {
file_server {
status {env.CUSTOM_STATUS}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "vars",
"root": "/srv"
}
]
},
{
"group": "group2",
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"status_code": "{env.CUSTOM_STATUS}"
}
]
}
]
}
],
"match": [
{
"path": [
"/custom-status*"
]
}
]
},
{
"group": "group2",
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"status_code": 403
}
]
}
]
}
],
"match": [
{
"path": [
"/nope*"
]
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
12 changes: 12 additions & 0 deletions modules/caddyhttp/fileserver/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func init() {
// index <files...>
// browse [<template_file>]
// precompressed <formats...>
// status <status>
// }
//
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
Expand All @@ -65,21 +66,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
if len(fsrv.Hide) == 0 {
return nil, h.ArgErr()
}

case "index":
fsrv.IndexNames = h.RemainingArgs()
if len(fsrv.IndexNames) == 0 {
return nil, h.ArgErr()
}

case "root":
if !h.Args(&fsrv.Root) {
return nil, h.ArgErr()
}

case "browse":
if fsrv.Browse != nil {
return nil, h.Err("browsing is already configured")
}
fsrv.Browse = new(Browse)
h.Args(&fsrv.Browse.TemplateFile)

case "precompressed":
var order []string
for h.NextArg() {
Expand All @@ -100,6 +105,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
order = append(order, h.Val())
}
fsrv.PrecompressedOrder = order

case "status":
if !h.NextArg() {
return nil, h.ArgErr()
}
fsrv.StatusCode = caddyhttp.WeakString(h.Val())

default:
return nil, h.Errf("unknown subdirective '%s'", h.Val())
}
Expand Down
16 changes: 16 additions & 0 deletions modules/caddyhttp/fileserver/staticfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ type FileServer struct {
// remove trailing slash from URIs for files. Default is true.
CanonicalURIs *bool `json:"canonical_uris,omitempty"`

// Override the status code written when successfully serving a file.
// Particularly useful when explicitly serving a file as display for
// an error, like a 404 page. A placeholder may be used. By default,
// the status code will typically be 200, or 206 for partial content.
StatusCode caddyhttp.WeakString `json:"status_code,omitempty"`

// If pass-thru mode is enabled and a requested file is not found,
// it will invoke the next handler in the chain instead of returning
// a 404 error. By default, this is false (disabled).
Expand Down Expand Up @@ -345,6 +351,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
return nil
}

// if a status code override is configured, write the status code
// before serving the file
if codeStr := fsrv.StatusCode.String(); codeStr != "" {
intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
if err != nil {
return caddyhttp.Error(http.StatusInternalServerError, err)
}
w.WriteHeader(intVal)
}

// let the standard library do what it does best; note, however,
// that errors generated by ServeContent are written immediately
// to the response, so we cannot handle them (but errors there
Expand Down

0 comments on commit 3f6283b

Please sign in to comment.