mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 22:23:48 +03:00
fileserver: Add status code override (#4076)
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).
This commit is contained in:
parent
45fb7202ac
commit
3f6283b385
3 changed files with 140 additions and 0 deletions
112
caddytest/integration/caddyfile_adapt/file_server_status.txt
Normal file
112
caddytest/integration/caddyfile_adapt/file_server_status.txt
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ func init() {
|
||||||
// index <files...>
|
// index <files...>
|
||||||
// browse [<template_file>]
|
// browse [<template_file>]
|
||||||
// precompressed <formats...>
|
// precompressed <formats...>
|
||||||
|
// status <status>
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
@ -65,21 +66,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
if len(fsrv.Hide) == 0 {
|
if len(fsrv.Hide) == 0 {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "index":
|
case "index":
|
||||||
fsrv.IndexNames = h.RemainingArgs()
|
fsrv.IndexNames = h.RemainingArgs()
|
||||||
if len(fsrv.IndexNames) == 0 {
|
if len(fsrv.IndexNames) == 0 {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "root":
|
case "root":
|
||||||
if !h.Args(&fsrv.Root) {
|
if !h.Args(&fsrv.Root) {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "browse":
|
case "browse":
|
||||||
if fsrv.Browse != nil {
|
if fsrv.Browse != nil {
|
||||||
return nil, h.Err("browsing is already configured")
|
return nil, h.Err("browsing is already configured")
|
||||||
}
|
}
|
||||||
fsrv.Browse = new(Browse)
|
fsrv.Browse = new(Browse)
|
||||||
h.Args(&fsrv.Browse.TemplateFile)
|
h.Args(&fsrv.Browse.TemplateFile)
|
||||||
|
|
||||||
case "precompressed":
|
case "precompressed":
|
||||||
var order []string
|
var order []string
|
||||||
for h.NextArg() {
|
for h.NextArg() {
|
||||||
|
@ -100,6 +105,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
order = append(order, h.Val())
|
order = append(order, h.Val())
|
||||||
}
|
}
|
||||||
fsrv.PrecompressedOrder = order
|
fsrv.PrecompressedOrder = order
|
||||||
|
|
||||||
|
case "status":
|
||||||
|
if !h.NextArg() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
fsrv.StatusCode = caddyhttp.WeakString(h.Val())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unknown subdirective '%s'", h.Val())
|
return nil, h.Errf("unknown subdirective '%s'", h.Val())
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,12 @@ type FileServer struct {
|
||||||
// remove trailing slash from URIs for files. Default is true.
|
// remove trailing slash from URIs for files. Default is true.
|
||||||
CanonicalURIs *bool `json:"canonical_uris,omitempty"`
|
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,
|
// 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
|
// it will invoke the next handler in the chain instead of returning
|
||||||
// a 404 error. By default, this is false (disabled).
|
// a 404 error. By default, this is false (disabled).
|
||||||
|
@ -345,6 +351,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||||
return nil
|
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,
|
// let the standard library do what it does best; note, however,
|
||||||
// that errors generated by ServeContent are written immediately
|
// that errors generated by ServeContent are written immediately
|
||||||
// to the response, so we cannot handle them (but errors there
|
// to the response, so we cannot handle them (but errors there
|
||||||
|
|
Loading…
Reference in a new issue