@marcel91200 / caddy Mirror · read-only / commits / 3f6283b3
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

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).

Francis Lavoie committed Apr 8, 2021 at 13:09 UTC 3f6283b385642c56f34b479d1275095379b062d3
3 files changed +140
caddytest/integration/caddyfile_adapt/file_server_status.txt new
+112
@@ -0,0 +1,112 @@
1 +localhost
2 +
3 +root * /srv
4 +
5 +handle /nope* {
6 + file_server {
7 + status 403
8 + }
9 +}
10 +
11 +handle /custom-status* {
12 + file_server {
13 + status {env.CUSTOM_STATUS}
14 + }
15 +}
16 +----------
17 +{
18 + "apps": {
19 + "http": {
20 + "servers": {
21 + "srv0": {
22 + "listen": [
23 + ":443"
24 + ],
25 + "routes": [
26 + {
27 + "match": [
28 + {
29 + "host": [
30 + "localhost"
31 + ]
32 + }
33 + ],
34 + "handle": [
35 + {
36 + "handler": "subroute",
37 + "routes": [
38 + {
39 + "handle": [
40 + {
41 + "handler": "vars",
42 + "root": "/srv"
43 + }
44 + ]
45 + },
46 + {
47 + "group": "group2",
48 + "handle": [
49 + {
50 + "handler": "subroute",
51 + "routes": [
52 + {
53 + "handle": [
54 + {
55 + "handler": "file_server",
56 + "hide": [
57 + "./Caddyfile"
58 + ],
59 + "status_code": "{env.CUSTOM_STATUS}"
60 + }
61 + ]
62 + }
63 + ]
64 + }
65 + ],
66 + "match": [
67 + {
68 + "path": [
69 + "/custom-status*"
70 + ]
71 + }
72 + ]
73 + },
74 + {
75 + "group": "group2",
76 + "handle": [
77 + {
78 + "handler": "subroute",
79 + "routes": [
80 + {
81 + "handle": [
82 + {
83 + "handler": "file_server",
84 + "hide": [
85 + "./Caddyfile"
86 + ],
87 + "status_code": 403
88 + }
89 + ]
90 + }
91 + ]
92 + }
93 + ],
94 + "match": [
95 + {
96 + "path": [
97 + "/nope*"
98 + ]
99 + }
100 + ]
101 + }
102 + ]
103 + }
104 + ],
105 + "terminal": true
106 + }
107 + ]
108 + }
109 + }
110 + }
111 + }
112 +}
\ No newline at end of file
modules/caddyhttp/fileserver/caddyfile.go
+12
@@ -40,6 +40,7 @@ func init() {
40 // index <files...>
41 // browse [<template_file>]
42 // precompressed <formats...>
43 +// status <status>
44 // }
45 //
46 func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
@@ -65,21 +66,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
66 if len(fsrv.Hide) == 0 {
67 return nil, h.ArgErr()
68 }
69 +
70 case "index":
71 fsrv.IndexNames = h.RemainingArgs()
72 if len(fsrv.IndexNames) == 0 {
73 return nil, h.ArgErr()
74 }
75 +
76 case "root":
77 if !h.Args(&fsrv.Root) {
78 return nil, h.ArgErr()
79 }
80 +
81 case "browse":
82 if fsrv.Browse != nil {
83 return nil, h.Err("browsing is already configured")
84 }
85 fsrv.Browse = new(Browse)
86 h.Args(&fsrv.Browse.TemplateFile)
87 +
88 case "precompressed":
89 var order []string
90 for h.NextArg() {
@@ -100,6 +105,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
105 order = append(order, h.Val())
106 }
107 fsrv.PrecompressedOrder = order
108 +
109 + case "status":
110 + if !h.NextArg() {
111 + return nil, h.ArgErr()
112 + }
113 + fsrv.StatusCode = caddyhttp.WeakString(h.Val())
114 +
115 default:
116 return nil, h.Errf("unknown subdirective '%s'", h.Val())
117 }
modules/caddyhttp/fileserver/staticfiles.go
+16
@@ -75,6 +75,12 @@ type FileServer struct {
75 // remove trailing slash from URIs for files. Default is true.
76 CanonicalURIs *bool `json:"canonical_uris,omitempty"`
77
78 + // Override the status code written when successfully serving a file.
79 + // Particularly useful when explicitly serving a file as display for
80 + // an error, like a 404 page. A placeholder may be used. By default,
81 + // the status code will typically be 200, or 206 for partial content.
82 + StatusCode caddyhttp.WeakString `json:"status_code,omitempty"`
83 +
84 // If pass-thru mode is enabled and a requested file is not found,
85 // it will invoke the next handler in the chain instead of returning
86 // 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
351 return nil
352 }
353
354 + // if a status code override is configured, write the status code
355 + // before serving the file
356 + if codeStr := fsrv.StatusCode.String(); codeStr != "" {
357 + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
358 + if err != nil {
359 + return caddyhttp.Error(http.StatusInternalServerError, err)
360 + }
361 + w.WriteHeader(intVal)
362 + }
363 +
364 // let the standard library do what it does best; note, however,
365 // that errors generated by ServeContent are written immediately
366 // to the response, so we cannot handle them (but errors there