feat(backends+domains): HTTP-Protokoll-Flags — force_http1 + disable_h3
Backend: force_http1 (bool, default false) — zwingt HAProxy auf der Backend-Verbindung zu HTTP/1.1 statt h2,http/1.1 zu verhandeln. Nötig für Legacy-Apps die kein h2 sprechen. Domain: disable_h3 (bool, default false) — unterdrückt Alt-Svc- Response-Header für diese Domain. Browser erhalten keinen h3/QUIC- Hinweis und bleiben auf h2/http1.1. Migration 0027, Model+Service+HAProxy-Template+UI+i18n. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -290,6 +290,8 @@
|
||||
"rateLimitHint": "Max. Requests pro Sekunde je Client-IP. HAProxy zählt über ein 10-Sekunden-Fenster pro Stick-Table (max. 100k IPs). 0 = aus.",
|
||||
"maxBody": "Max. Request-Body",
|
||||
"maxBodyHint": "Cap auf Content-Length-Header. Größere Requests bekommen 413. Nicht erkannt: Chunked-Bodies (HAProxy bufferd nicht standardmäßig). 0 = aus.",
|
||||
"disableH3": "HTTP/3 (QUIC) deaktivieren",
|
||||
"disableH3Hint": "Unterdrückt den Alt-Svc-Response-Header für diese Domain. Browser erhalten keinen Hinweis auf h3/QUIC und bleiben auf h2/http1.1. Sinnvoll bei Clients die Probleme mit QUIC-Verbindungen melden.",
|
||||
"headersBtn": "Headers",
|
||||
"headersTitle": "Response-Headers — {{name}}",
|
||||
"headersHint": "Diese Header werden von HAProxy auf jede Response für diese Domain gesetzt (http-response set-header). Reihenfolge via Position.",
|
||||
@@ -331,6 +333,8 @@
|
||||
"lbAlgoHint": "roundrobin = gleichmäßig, leastconn = an den Server mit wenigsten Verbindungen, source = sticky per Client-IP (für stateful Apps ohne shared session).",
|
||||
"websocket": "WebSocket-Support",
|
||||
"websocketHint": "An: erlaubt langlebige WebSocket-/Long-Poll-Verbindungen (z. B. Proxmox-Console, SSH-WS, AsyncAPI) — Tunnel-Idle 1h statt 60s. Aus: strikte HTTP-Timeouts.",
|
||||
"forceHttp1": "HTTP/1.1 erzwingen",
|
||||
"forceHttp1Hint": "Deaktiviert HTTP/2 (h2) auf der Backend-Verbindung — HAProxy handelt nur noch HTTP/1.1 aus. Nötig für Backends die kein h2 unterstützen (z. B. ältere nginx-Konfigurationen ohne h2-Modul, Legacy-Apps).",
|
||||
"servers": "Server",
|
||||
"noServers": "kein Server",
|
||||
"nServers": "{{n}} Server",
|
||||
|
||||
@@ -290,6 +290,8 @@
|
||||
"rateLimitHint": "Max requests per second per client IP. HAProxy counts over a 10-second window per stick table (max. 100k IPs). 0 = off.",
|
||||
"maxBody": "Max request body",
|
||||
"maxBodyHint": "Cap on the Content-Length header. Larger requests get 413. Chunked bodies are not detected (HAProxy doesn't buffer by default). 0 = off.",
|
||||
"disableH3": "Disable HTTP/3 (QUIC)",
|
||||
"disableH3Hint": "Suppresses the Alt-Svc response header for this domain. Browsers won't receive an h3/QUIC upgrade hint and stay on h2/http1.1. Useful when clients report issues with QUIC connections.",
|
||||
"headersBtn": "Headers",
|
||||
"headersTitle": "Response headers — {{name}}",
|
||||
"headersHint": "These headers are set by HAProxy on every response for this domain (http-response set-header). Ordering via position.",
|
||||
@@ -331,6 +333,8 @@
|
||||
"lbAlgoHint": "roundrobin = evenly, leastconn = pick the server with fewest active connections, source = sticky per client-IP hash (for stateful apps without shared session).",
|
||||
"websocket": "WebSocket support",
|
||||
"websocketHint": "On: allow long-lived WebSocket / long-poll connections (Proxmox console, SSH-over-WS, AsyncAPI) — tunnel idle 1h instead of 60s. Off: strict HTTP timeouts.",
|
||||
"forceHttp1": "Force HTTP/1.1",
|
||||
"forceHttp1Hint": "Disables HTTP/2 (h2) on the backend connection — HAProxy negotiates HTTP/1.1 only. Required for backends that don't support h2 (e.g. older nginx configs without the h2 module, legacy apps).",
|
||||
"servers": "Servers",
|
||||
"noServers": "no server",
|
||||
"nServers": "{{n}} servers",
|
||||
|
||||
@@ -20,13 +20,13 @@ interface Backend {
|
||||
id: number; name: string; scheme: string
|
||||
health_check_path?: string | null
|
||||
lb_algorithm: 'roundrobin' | 'leastconn' | 'source'
|
||||
websocket: boolean; active: boolean
|
||||
websocket: boolean; force_http1: boolean; active: boolean
|
||||
}
|
||||
interface BackendFormValues {
|
||||
name: string; scheme: 'http' | 'https'
|
||||
health_check_path?: string
|
||||
lb_algorithm: 'roundrobin' | 'leastconn' | 'source'
|
||||
websocket: boolean; active: boolean
|
||||
websocket: boolean; force_http1: boolean; active: boolean
|
||||
domain_ids?: number[]
|
||||
}
|
||||
interface BackendServer {
|
||||
@@ -157,6 +157,7 @@ export default function BackendDetailPage() {
|
||||
health_check_path: backend.health_check_path ?? undefined,
|
||||
lb_algorithm: backend.lb_algorithm,
|
||||
websocket: backend.websocket,
|
||||
force_http1: backend.force_http1,
|
||||
active: backend.active,
|
||||
domain_ids: attached.map(d => d.id),
|
||||
}}
|
||||
@@ -184,6 +185,10 @@ export default function BackendDetailPage() {
|
||||
extra={t('backends.websocketHint')}>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('backends.forceHttp1')} name="force_http1" valuePropName="checked"
|
||||
extra={t('backends.forceHttp1Hint')}>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('backends.active')} name="active" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
@@ -27,6 +27,7 @@ interface Domain {
|
||||
maintenance_mode: boolean; maintenance_message?: string | null
|
||||
www_redirect: '' | 'to-naked' | 'to-www'
|
||||
rate_limit_rps: number; max_body_kb: number
|
||||
disable_h3: boolean
|
||||
notes?: string | null
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ interface DomainFormValues {
|
||||
maintenance_mode: boolean; maintenance_message?: string
|
||||
www_redirect: '' | 'to-naked' | 'to-www'
|
||||
rate_limit_rps: number; max_body_kb: number
|
||||
disable_h3: boolean
|
||||
notes?: string
|
||||
}
|
||||
|
||||
@@ -164,6 +166,7 @@ export default function DomainDetailPage() {
|
||||
www_redirect: domain.www_redirect ?? '',
|
||||
rate_limit_rps: domain.rate_limit_rps ?? 0,
|
||||
max_body_kb: domain.max_body_kb ?? 0,
|
||||
disable_h3: domain.disable_h3 ?? false,
|
||||
notes: domain.notes ?? '',
|
||||
}}
|
||||
onFinish={(v) => update.mutate(v)}
|
||||
@@ -244,6 +247,11 @@ export default function DomainDetailPage() {
|
||||
<InputNumber min={0} step={64} style={{ width: '100%' }} addonAfter="KiB" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('domains.disableH3')} name="disable_h3" valuePropName="checked"
|
||||
extra={t('domains.disableH3Hint')}>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('domains.notes')} name="notes">
|
||||
<Input.TextArea rows={2} />
|
||||
</Form.Item>
|
||||
|
||||
Reference in New Issue
Block a user