# Error Page Middleware
Custom error page serving middleware that replaces default HTTP error responses with styled custom pages.
## Overview
This package provides two components:
1. **errorpage package**: Manages error page file loading, caching, and hot-reloading from disk
1. **CustomErrorPage middleware**: Intercepts error responses and replaces them with custom error pages
## Architecture
```mermaid
graph TD
A[HTTP Error Response] --> B{CustomErrorPage Middleware}
B --> C{Status Code & Content Type}
C -->|HTML/Plain| D[Look Up Error Page]
C -->|Other| E[Pass Through]
D --> F{Page Found?}
F -->|Yes| G[Replace Body
Set Content-Type]
F -->|No| H[Log Error
Pass Through]
G --> I[Custom Error Page Response]
subgraph Error Page Management
J[Error Pages Directory]
K[File Watcher]
L[Content Cache]
M[HTTP Handler]
end
J -->|Read| L
J -->|Watch Changes| K
K -->|Notify| L
L --> M
```
## Error Page Lookup Flow
```mermaid
flowchart TD
A[Error Status: 503] --> B{Look for 503.html?}
B -->|Found| C[Return 503.html]
B -->|Not Found| D{Look for 404.html?}
D -->|Found| E[Return 404.html]
D -->|Not Found| F[Return Default Error]
```
## Core Components
### Error Page Package
```go
var (
setupOnce sync.Once
dirWatcher watcher.Watcher
fileContentMap = xsync.NewMap[string, []byte]()
)
func setup() {
t := task.RootTask("error_page", false)
dirWatcher = watcher.NewDirectoryWatcher(t, errPagesBasePath)
loadContent()
go watchDir()
}
// GetStaticFile retrieves an error page file by filename
func GetStaticFile(filename string) ([]byte, bool)
// GetErrorPageByStatus retrieves the error page for a given status code
func GetErrorPageByStatus(statusCode int) (content []byte, ok bool)
```
### File Watcher
The package watches the error pages directory for changes:
```go
func watchDir() {
eventCh, errCh := dirWatcher.Events(task.RootContext())
for {
select {
case event := <-eventCh:
filename := event.ActorName
switch event.Action {
case events.ActionFileWritten:
fileContentMap.Delete(filename)
loadContent()
case events.ActionFileDeleted:
fileContentMap.Delete(filename)
case events.ActionFileRenamed:
fileContentMap.Delete(filename)
loadContent()
}
case err := <-errCh:
gperr.LogError("error watching error page directory", err)
}
}
}
```
### Custom Error Page Middleware
```go
type customErrorPage struct{}
var CustomErrorPage = NewMiddleware[customErrorPage]()
const StaticFilePathPrefix = "/$gperrorpage/"
```
### Request Modifier
```go
func (customErrorPage) before(w http.ResponseWriter, r *http.Request) bool {
return !ServeStaticErrorPageFile(w, r)
}
```
### Response Modifier
```go
func (customErrorPage) modifyResponse(resp *http.Response) error {
// Only handles:
// - Non-success status codes (4xx, 5xx)
// - HTML or Plain Text content types
contentType := httputils.GetContentType(resp.Header)
if !httputils.IsSuccess(resp.StatusCode) && (contentType.IsHTML() || contentType.IsPlainText()) {
errorPage, ok := errorpage.GetErrorPageByStatus(resp.StatusCode)
if ok {
// Replace response body with error page
resp.Body = io.NopCloser(bytes.NewReader(errorPage))
resp.ContentLength = int64(len(errorPage))
resp.Header.Set(httpheaders.HeaderContentLength, strconv.Itoa(len(errorPage)))
resp.Header.Set(httpheaders.HeaderContentType, "text/html; charset=utf-8")
}
}
return nil
}
```
## Static File Serving
The middleware also serves static error page assets:
```go
func ServeStaticErrorPageFile(w http.ResponseWriter, r *http.Request) bool {
if strings.HasPrefix(path, StaticFilePathPrefix) {
filename := path[len(StaticFilePathPrefix):]
file, ok := errorpage.GetStaticFile(filename)
if ok {
// Set content type based on extension
switch ext := filepath.Ext(filename); ext {
case ".html":
w.Header().Set(httpheaders.HeaderContentType, "text/html; charset=utf-8")
case ".js":
w.Header().Set(httpheaders.HeaderContentType, "application/javascript; charset=utf-8")
case ".css":
w.Header().Set(httpheaders.HeaderContentType, "text/css; charset=utf-8")
}
w.Write(file)
return true
}
}
return false
}
```
## Configuration
### Error Pages Directory
Default path: `config/error_pages/`
### Supported Files
| File Pattern | Description |
| ------------------- | -------------------------------------- |
| `{statusCode}.html` | Specific error page (e.g., `503.html`) |
| `404.html` | Fallback for missing specific pages |
| `*.css` | Stylesheets |
| `*.js` | JavaScript files |
| `*.{png,jpg,svg}` | Images and assets |
### Example Structure
```
config/error_pages/
├── 403.html
├── 404.html
├── 500.html
├── 502.html
├── 503.html
├── style.css
└── logo.png
```
### Middleware Configuration
```yaml
# In route middleware configuration
- use: errorpage
# Optional: bypass rules
bypass:
- type: PathPrefix
value: /api
```
## Response Processing
```mermaid
flowchart TD
A[Backend Response] --> B{Status Code >= 400?}
B -->|No| C[Pass Through]
B -->|Yes| D{Content Type HTML/Plain?}
D -->|No| C
D -->|Yes| E{Look Up Error Page}
E -->|Found| F[Replace Body]
E -->|Not Found| G[Log Error]
G --> C
F --> H[Set Content-Type: text/html]
H --> I[Return Custom Error Page]
```
## Usage Examples
### Creating Custom Error Pages
**503.html**:
```html
The service is temporarily unavailable. Please try again later.