[PR #4973] [CLOSED] feat(vhost): add customResponse config for unregistered HTTP hostnames #5090

Closed
opened 2026-05-05 14:54:32 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/fatedier/frp/pull/4973
Author: @mnasruul
Created: 9/8/2025
Status: Closed

Base: devHead: dev


📝 Commits (3)

  • d63f7fd [ADD] feature vHost customResponse
  • 9ac0c7d [UPDATE] log level vhost custom response
  • b504723 feat(vhost): add customResponse config example with explanations in frps.toml

📊 Changes

6 files changed (+234 additions, -0 deletions)

View changed files

📝 conf/frps_full_example.toml (+57 -0)
📝 pkg/config/v1/server.go (+15 -0)
📝 pkg/util/http/http.go (+23 -0)
📝 pkg/util/vhost/http.go (+5 -0)
📝 pkg/util/vhost/resource.go (+114 -0)
📝 server/service.go (+20 -0)

📄 Description

Summary

This PR introduces a new customResponse configuration block in FRPS.
When a client sends an HTTP request with a Host that is not registered by any FRPC, FRPS can now return a custom response instead of the default 404 page.

Why

Currently, unregistered host requests always return the same default 404 response.
With customResponse, users can define:

  • Status code (e.g., 404, 503)
  • Content type (e.g., text/html, application/json)
  • Custom body (multi-line supported)
  • Additional headers (map of key:value)

This improves:

  • User experience: clearer responses for clients.
  • Multi-tenant setups: per-domain fallback behavior.

Configuration Example (frps.toml)

[customResponse]
# Enable or disable the feature. Default = false (disabled).
enable = false

# Rule 1: Return a 503 HTML page for *.example.com and example.com
[[customResponse.rules]]
hostname    = ["*.example.com", "example.com"]
statusCode  = 503
contentType = "text/html"
body = """
<!doctype html>
<html>
<body>
<h1>Service Unavailable</h1>
<p>The server is currently unavailable.<br/>
Please try again later.</p>
</body>
</html>
"""
[customResponse.rules.headers]
Cache-Control = "no-store"
X-Error-Code  = "UNREGISTERED_HOST"

# Rule 2: Return a 404 JSON payload for spesific.example2.com
[[customResponse.rules]]
hostname    = ["spesific.example2.com"]
statusCode  = 404
contentType = "application/json"
body        = "{\"error\":\"unregistered_host\",\"hint\":\"register frpc for this hostname\"}"
[customResponse.rules.headers]
Cache-Control = "no-store"

Notes:

Rules are matched in order; first match wins.

  • *.example.com does not match example.com. To cover both, list both hostnames.
  • "*" can be used as a catch-all rule.
  • If enable = false or no rule matches, FRPS falls back to current default behavior.

Design Overview

  • New config types in pkg/config/v1/server.go: CustomResponseConfig and CustomResponseRule.
  • Internal mapping to vhost.CustomErrorPage in server/service.go (avoids import cycles).
  • New matcher helper MatchDomain in pkg/util/http/http.go.
  • ErrorHandler in pkg/util/vhost/http.go updated to call CustomErrorResponse.

Compatibility

  • Default behavior unchanged when [customResponse] is disabled (or not defined).
  • Fully backward-compatible.

Test Plan

  • Unit tests for:
  • Exact host match (example.com)
  • Wildcard (*.example.com)
  • Catch-all ("*")
  • Hostnames with :port (normalized)
  • No match (falls back to default 404)

Manual curl tests against FRPS with/without enabled rules.
Related Issue
Closes https://github.com/fatedier/frp/issues/4972


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/fatedier/frp/pull/4973 **Author:** [@mnasruul](https://github.com/mnasruul) **Created:** 9/8/2025 **Status:** ❌ Closed **Base:** `dev` ← **Head:** `dev` --- ### 📝 Commits (3) - [`d63f7fd`](https://github.com/fatedier/frp/commit/d63f7fd0e4439482704302ea977f2f4442770155) [ADD] feature vHost customResponse - [`9ac0c7d`](https://github.com/fatedier/frp/commit/9ac0c7d1729c377095af0725f2c5acb3d72e1f4b) [UPDATE] log level vhost custom response - [`b504723`](https://github.com/fatedier/frp/commit/b50472340d63bd8325ea43425bbb0243f5320c64) feat(vhost): add customResponse config example with explanations in frps.toml ### 📊 Changes **6 files changed** (+234 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `conf/frps_full_example.toml` (+57 -0) 📝 `pkg/config/v1/server.go` (+15 -0) 📝 `pkg/util/http/http.go` (+23 -0) 📝 `pkg/util/vhost/http.go` (+5 -0) 📝 `pkg/util/vhost/resource.go` (+114 -0) 📝 `server/service.go` (+20 -0) </details> ### 📄 Description ### **Summary** This PR introduces a new customResponse configuration block in FRPS. When a client sends an HTTP request with a Host that is not registered by any FRPC, FRPS can now return a custom response instead of the default 404 page. ### **Why** Currently, unregistered host requests always return the same default 404 response. With customResponse, users can define: - Status code (e.g., 404, 503) - Content type (e.g., text/html, application/json) - Custom body (multi-line supported) - Additional headers (map of key:value) ### This improves: - User experience: clearer responses for clients. - Multi-tenant setups: per-domain fallback behavior. Configuration Example (frps.toml) ``` [customResponse] # Enable or disable the feature. Default = false (disabled). enable = false # Rule 1: Return a 503 HTML page for *.example.com and example.com [[customResponse.rules]] hostname = ["*.example.com", "example.com"] statusCode = 503 contentType = "text/html" body = """ <!doctype html> <html> <body> <h1>Service Unavailable</h1> <p>The server is currently unavailable.<br/> Please try again later.</p> </body> </html> """ [customResponse.rules.headers] Cache-Control = "no-store" X-Error-Code = "UNREGISTERED_HOST" # Rule 2: Return a 404 JSON payload for spesific.example2.com [[customResponse.rules]] hostname = ["spesific.example2.com"] statusCode = 404 contentType = "application/json" body = "{\"error\":\"unregistered_host\",\"hint\":\"register frpc for this hostname\"}" [customResponse.rules.headers] Cache-Control = "no-store" ``` ### Notes: Rules are matched in order; first match wins. - *.example.com does not match example.com. To cover both, list both hostnames. - "*" can be used as a catch-all rule. - If enable = false or no rule matches, FRPS falls back to current default behavior. ### Design Overview - New config types in pkg/config/v1/server.go: CustomResponseConfig and CustomResponseRule. - Internal mapping to vhost.CustomErrorPage in server/service.go (avoids import cycles). - New matcher helper MatchDomain in pkg/util/http/http.go. - ErrorHandler in pkg/util/vhost/http.go updated to call CustomErrorResponse. ### Compatibility - Default behavior unchanged when [customResponse] is disabled (or not defined). - Fully backward-compatible. ### Test Plan - Unit tests for: - Exact host match (example.com) - Wildcard (*.example.com) - Catch-all ("*") - Hostnames with :port (normalized) - No match (falls back to default 404) Manual curl tests against FRPS with/without enabled rules. Related Issue Closes https://github.com/fatedier/frp/issues/4972 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
gitea-mirror 2026-05-05 14:54:32 -06:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#5090
No description provided.