mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
test(gateway): clarify hash compatibility and relocate forwarding docs
This commit is contained in:
parent
a75c982d9c
commit
e6014961af
3 changed files with 14 additions and 9 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Python forwarding compatibility
|
||||
|
||||
The opt-in forwarding gateway serves HTTP/1.1 and WebSockets in front of Python. Python still owns every route and policy. Its protocol contract and environment settings are in [gateway-forward/AGENTS.md](crates/gateway-forward/AGENTS.md)
|
||||
The opt-in forwarding gateway serves HTTP/1.1 and WebSockets in front of Python. Python still owns every route and policy. Its protocol contract and environment settings are in [AGENTS.md](AGENTS.md)
|
||||
|
||||
## Known differences from uvicorn with h11
|
||||
|
||||
|
|
@ -8,12 +8,14 @@ These cases remain executable as ignored socket tests. They are not passing pari
|
|||
|
||||
| Input | Direct Python | Through Rust | Regression test |
|
||||
|---|---|---|---|
|
||||
| Literal `#` in a request path or query | Keeps every byte | `http::Uri` truncates at `#`, potentially changing the route or query. Must be fixed before shipping | [forwards_an_unescaped_hash_in_the_target](crates/gateway-forward/tests/exchange.rs) |
|
||||
| Header after a removed hop-by-hop field | Keeps the remaining header order | `HeaderMap::remove` can move a later header | [dropping_a_hop_by_hop_header_keeps_the_others_in_order](crates/gateway-forward/tests/exchange.rs) |
|
||||
| Repeated header names interleaved with other names | Keeps the interleaving | Groups values by name, keeping their order within that name | [interleaved_repeated_headers_reach_python_interleaved](crates/gateway-forward/tests/exchange.rs) |
|
||||
| Obsolete folded header, such as `x-a: b\r\n c` | Unfolds to `b c` | Rejects with 400 | [a_folded_header_reaches_python_unfolded](crates/gateway-forward/tests/exchange.rs) |
|
||||
| Identical Content-Length list, such as `7, 7` | Accepts length 7 | Rejects with 400 | [a_content_length_list_of_one_value_reaches_python](crates/gateway-forward/tests/framing.rs) |
|
||||
| HTTP/1.0 request with chunked transfer coding | Decodes the body | Rejects with 400 | [an_http10_chunked_upload_reaches_python](crates/gateway-forward/tests/framing.rs) |
|
||||
| Literal `#` in a request path or query | Keeps every byte | `http::Uri` truncates at `#`, potentially changing the route or query. Must be fixed before shipping | [forwards_an_unescaped_hash_in_the_target](tests/exchange.rs) |
|
||||
| Header after a removed hop-by-hop field | Keeps the remaining header order | `HeaderMap::remove` can move a later header | [dropping_a_hop_by_hop_header_keeps_the_others_in_order](tests/exchange.rs) |
|
||||
| Repeated header names interleaved with other names | Keeps the interleaving | Groups values by name, keeping their order within that name | [interleaved_repeated_headers_reach_python_interleaved](tests/exchange.rs) |
|
||||
| Obsolete folded header, such as `x-a: b\r\n c` | Unfolds to `b c` | Rejects with 400 | [a_folded_header_reaches_python_unfolded](tests/exchange.rs) |
|
||||
| Identical Content-Length list, such as `7, 7` | Accepts length 7 | Rejects with 400 | [a_content_length_list_of_one_value_reaches_python](tests/framing.rs) |
|
||||
| HTTP/1.0 request with chunked transfer coding | Decodes the body | Rejects with 400 | [an_http10_chunked_upload_reaches_python](tests/framing.rs) |
|
||||
|
||||
The literal `#` issue does not affect browser hash routing, since browsers never send the fragment to the server. A hash encoded as `%23` is forwarded unchanged
|
||||
|
||||
Clients can avoid the parser rejections by sending unfolded headers, one Content-Length value, and HTTP/1.1 for chunked uploads. Integrations must not depend on header ordering across different field names
|
||||
|
||||
|
|
@ -201,6 +201,9 @@ async fn forwards_every_method(#[case] method: &str) {
|
|||
#[rstest]
|
||||
#[case::double_slash("//v1//models")]
|
||||
#[case::encoded_slash("/v1/files/a%2Fb/content")]
|
||||
#[case::encoded_hash_in_query("/v1/files?purpose=batch%23retry&limit=1")]
|
||||
#[case::encoded_hash_in_path("/v1/files/report%231.pdf/content")]
|
||||
#[case::encoded_hash_before_query("/gemini/v1beta/files/a%23b?key=k")]
|
||||
#[case::encoded_query("/v1/models?q=%E2%9C%93&empty=&flag")]
|
||||
#[case::utf8_bytes_escaped("/mcp/%F0%9F%A6%80")]
|
||||
#[case::lowercase_encoded_slash("/v1/files/a%2fb/content")]
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ async fn handshake_headers_cross_unchanged_both_ways() {
|
|||
&gateway,
|
||||
handshake(
|
||||
target,
|
||||
"sec-websocket-protocol: realtime, openai-insecure-api-key.sk-1234, openai-beta.realtime-v1\r\nsec-websocket-protocol: second-line\r\nsec-websocket-extensions: permessage-deflate; client_max_window_bits\r\ncookie: token=abc\r\n",
|
||||
"sec-websocket-protocol: realtime, openai-insecure-api-key.sk-websocket-test, openai-beta.realtime-v1\r\nsec-websocket-protocol: second-line\r\nsec-websocket-extensions: permessage-deflate; client_max_window_bits\r\ncookie: token=abc\r\n",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
|
@ -260,7 +260,7 @@ async fn handshake_headers_cross_unchanged_both_ways() {
|
|||
assert_eq!(
|
||||
values(&handshake, "sec-websocket-protocol"),
|
||||
[
|
||||
"realtime, openai-insecure-api-key.sk-1234, openai-beta.realtime-v1",
|
||||
"realtime, openai-insecure-api-key.sk-websocket-test, openai-beta.realtime-v1",
|
||||
"second-line"
|
||||
]
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue