Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/en/guide/url-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ rtp2httpd supports multiple streaming protocols, distinguished by different URL

Basic format: `http://server:port/path[?param1=value1][&param2=value2][&param3=value3]`

If `app-path-prefix` is configured, add that prefix to every path. For example, with `app-path-prefix = /app/rtp2httpd`, `/rtp/...`, `/status`, `/player`, and `/playlist.m3u` become `/app/rtp2httpd/rtp/...`, `/app/rtp2httpd/status`, `/app/rtp2httpd/player`, and `/app/rtp2httpd/playlist.m3u`.

When `r2h-token` (HTTP request authentication token) is configured, all URLs must include the `r2h-token=<your token>` parameter to be accessible.

> [!TIP]
Expand Down
11 changes: 9 additions & 2 deletions docs/en/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Unix socket listen paths must be absolute and must not contain whitespace. At st
- Set to `*` to allow all origins, or specify a domain (e.g., `https://example.com`)
- `-s, --status-page-path <path>` - Status page and API root path (default: /status)
- `-p, --player-page-path <path>` - Built-in player page path (default: /player)
- `--app-path-prefix <path>` - Public access prefix for all HTTP resources (default: none)

### Compatibility

Expand Down Expand Up @@ -143,12 +144,18 @@ xff = no
# http://server:5140/player?r2h-token=your-secret-token
r2h-token = your-secret-token-here

# Status page path (default: /status)
# Status page app path (default: /status; mounted under app-path-prefix when configured)
status-page-path = /status

# Player page path (default: /player)
# Player page app path (default: /player; mounted under app-path-prefix when configured)
player-page-path = /player

# Public access prefix for all HTTP resources (default: none)
# After this is set, the status page, player, static assets,
# playlist.m3u, epg.xml, and stream URLs are all served under this
# prefix, for example /app/rtp2httpd/player.
app-path-prefix = /app/rtp2httpd

# Upstream network interface configuration (optional)
#
# Simple configuration: Configure only one default interface for all traffic types
Expand Down
2 changes: 2 additions & 0 deletions docs/guide/url-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ rtp2httpd 支持多种流媒体协议,通过不同的 URL 前缀进行区分

基本格式:`http://服务器地址:端口/路径[?参数1=值1][&参数2=值2][&参数3=值3]`

如果配置了 `app-path-prefix`,所有路径都需要加上此前缀。例如 `app-path-prefix = /app/rtp2httpd` 时,`/rtp/...`、`/status`、`/player`、`/playlist.m3u` 等路径分别变为 `/app/rtp2httpd/rtp/...`、`/app/rtp2httpd/status`、`/app/rtp2httpd/player`、`/app/rtp2httpd/playlist.m3u`。

当配置了 `r2h-token(HTTP 请求认证令牌)` 时,所有 URL 都需要额外带上参数 `r2h-token=<your token>` 才能访问。

> [!TIP]
Expand Down
10 changes: 8 additions & 2 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Unix socket 监听路径必须是绝对路径,且路径中不能包含空白
- 设为 `*` 允许所有来源,或指定具体域名(如 `https://example.com`)
- `-s, --status-page-path <路径>` - 状态页面与 API 根路径 (默认: /status)
- `-p, --player-page-path <路径>` - 内置播放器页面路径 (默认: /player)
- `--app-path-prefix <路径>` - 所有 HTTP 资源的公开访问前缀 (默认: 无)

### 兼容性

Expand Down Expand Up @@ -143,12 +144,17 @@ xff = no
# http://server:5140/player?r2h-token=your-secret-token
r2h-token = your-secret-token-here

# 状态页路径(默认: /status)
# 状态页应用内路径(默认: /status;配置 app-path-prefix 时会挂载在该前缀下
status-page-path = /status

# 播放器页路径(默认: /player)
# 播放器页应用内路径(默认: /player;配置 app-path-prefix 时会挂载在该前缀下
player-page-path = /player

# 所有 HTTP 资源的公开访问前缀(默认: 无)
# 设置后,状态页、播放器、静态资源、playlist.m3u、epg.xml 和流媒体 URL
# 都会在此前缀下提供,例如 /app/rtp2httpd/player。
app-path-prefix = /app/rtp2httpd

# 上游网络接口配置 (可选)
#
# 简单配置:只配置一个默认接口,所有流量类型都使用此接口
Expand Down
21 changes: 21 additions & 0 deletions e2e/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ def _assert_player_page_path(port: int, expected_path: str):
assert status2 == 404


def _assert_app_path_prefix(port: int, expected_prefix: str):
normalized = "/" + expected_prefix.strip("/")
status, _, _ = http_get("127.0.0.1", port, f"{normalized}/status")
assert status == 200
status2, _, _ = http_get("127.0.0.1", port, "/status")
assert status2 == 404


def _assert_hostname(port: int, expected_hosts: tuple[str, str]):
allowed_host, rejected_host = expected_hosts

Expand Down Expand Up @@ -230,6 +238,19 @@ def _assert_http_proxy_user_agent(port: int, expected_user_agent: str):
},
id="player-page-path",
),
pytest.param(
{
"name": "app-path-prefix",
"config_lines": _value_config_line("app-path-prefix"),
"cli_args": _value_cli_args("--app-path-prefix"),
"config_source_value": "cfg-prefix/",
"cli_source_value": "/cli-prefix",
"priority_config_value": "/config-prefix",
"priority_cli_value": "override-prefix/",
"assertion": _assert_app_path_prefix,
},
id="app-path-prefix",
),
pytest.param(
{
"name": "hostname",
Expand Down
38 changes: 38 additions & 0 deletions e2e/test_http_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)

pytestmark = pytest.mark.http_proxy
APP_PREFIX = "/app/rtp2httpd"


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -353,6 +354,43 @@ def test_302_location_rewritten(self, shared_r2h):
finally:
upstream.stop()

def test_302_location_rewritten_with_app_path_prefix(self, r2h_binary):
"""Redirect Location should include app-path-prefix when configured."""
port = find_free_port()
config = f"""\
[global]
verbosity = 4
app-path-prefix = {APP_PREFIX}

[bind]
* {port}
"""
r2h = R2HProcess(r2h_binary, port, config_content=config)
upstream = MockHTTPUpstream(
routes={
"/old": {
"status": 302,
"body": b"",
"headers": {"Location": "http://10.0.0.1:8080/new/page"},
},
}
)
upstream.start()
try:
r2h.start()
status, hdrs, _ = http_get(
"127.0.0.1",
port,
f"{APP_PREFIX}/http/127.0.0.1:{upstream.port}/old",
timeout=5.0,
)
assert status == 302
location = _get_location(hdrs)
assert location == f"{APP_PREFIX}/http/10.0.0.1:8080/new/page"
finally:
r2h.stop()
upstream.stop()

def test_301_location_rewritten(self, shared_r2h):
"""301 permanent redirect should also rewrite Location."""
upstream = MockHTTPUpstream(
Expand Down
41 changes: 39 additions & 2 deletions e2e/test_http_proxy_m3u_rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

_TIMEOUT = 5.0
_HEADER_PARSE_READ_SIZE = 8191 # HTTP_PROXY_RESPONSE_BUFFER_SIZE - 1
APP_PREFIX = "/app/rtp2httpd"


# ---------------------------------------------------------------------------
Expand All @@ -40,17 +41,36 @@ def shared_r2h(r2h_binary):
r2h.stop()


@pytest.fixture(scope="module")
def prefixed_r2h(r2h_binary):
"""A shared rtp2httpd instance mounted under app-path-prefix."""
port = find_free_port()
config = f"""\
[global]
verbosity = 4
maxclients = 100
app-path-prefix = {APP_PREFIX}

[bind]
* {port}
"""
r2h = R2HProcess(r2h_binary, port, config_content=config)
r2h.start()
yield r2h
r2h.stop()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _m3u_get(shared_r2h, upstream_port, path, content_type="application/vnd.apple.mpegurl"):
def _m3u_get(shared_r2h, upstream_port, path, content_type="application/vnd.apple.mpegurl", path_prefix=""):
"""Convenience: GET an M3U path through the proxy and return decoded text."""
status, hdrs, body = http_get(
"127.0.0.1",
shared_r2h.port,
f"/http/127.0.0.1:{upstream_port}{path}",
f"{path_prefix}/http/127.0.0.1:{upstream_port}{path}",
timeout=_TIMEOUT,
)
return status, hdrs, body.decode("utf-8", errors="replace")
Expand Down Expand Up @@ -178,6 +198,23 @@ def test_segment_urls_rewritten(self, shared_r2h):
finally:
upstream.stop()

def test_segment_urls_rewritten_with_app_path_prefix(self, prefixed_r2h):
"""Rewritten segment URLs should include app-path-prefix."""
m3u = "#EXTM3U\n#EXT-X-TARGETDURATION:10\n#EXTINF:10,\nhttp://10.0.0.1:8080/seg1.ts\n"
upstream = _make_m3u_upstream("/live/playlist.m3u8", m3u)
try:
status, _, text = _m3u_get(
prefixed_r2h,
upstream.port,
"/live/playlist.m3u8",
path_prefix=APP_PREFIX,
)
assert status == 200
assert "http://10.0.0.1:8080/seg1.ts" not in text
assert f"{APP_PREFIX}/http/10.0.0.1:8080/seg1.ts" in text
finally:
upstream.stop()

def test_variant_playlist_urls_rewritten(self, shared_r2h):
"""http:// URLs in a master/variant playlist should be rewritten."""
m3u = (
Expand Down
Loading
Loading