mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04: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
					
				
							
								
								
									
										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...> | ||||
| //        browse        [<template_file>] | ||||
| //        precompressed <formats...> | ||||
| //        status        <status> | ||||
| //    } | ||||
| // | ||||
| 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 { | ||||
| 					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() { | ||||
| @ -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()) | ||||
| 			} | ||||
|  | ||||
| @ -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). | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user