I encountered an issue with the binance futures websocket server when using the permessage-deflate extension with window_bits set to 15.
The server responds with an error indicating an invalid JSON, as it appears the JSON payload is missing the last two bytes. However, changing the window_bits to a value from 9 to 14 resolves the issue and everything works as expected.
This behavior seems specific to when both permessage-deflate is set to use WindowBit::Fifteen and a correct listenKey is provided.
It is worth noting that a similar setup works without issue on the OKX exchange WebSocket(which also support deflate).
detail
I am using Rust to subscribe to the bookTicker stream combined with a listenKey via WebSocket. The relevant part of the code is demonstrated below:
client_max_window_bits: WindowBit::Fifteen
wss://fstream.binance.com/stream
{
"id":1,
"method":"SUBSCRIBE",
"params":[
"btcusdt@bookTicker",
// listen_key
"1234"
]
}
The error response I receive when window_bits is set to 15 is as follows:
{“error”:{“code”:3,“msg”:“Invalid JSON: EOF while parsing a list at line 1 column 66”}}
This indicates that the JSON payload is truncated, missing ]}
at the end.
Interestingly, if I set window_bits to any value between 9 and 14, or if I provide an incorrect listenKey, no error occurs and the connection performs as expected.
Here is an example of the request/response headers when the error occurs:
GET /stream HTTP/1.1
Host: fstream.binance.com
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Key: [redacted]
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15; server_max_window_bits=15
resposne:
"date": "Fri, 24 Nov 2023 02:03:45 GMT",
"connection": "upgrade",
"upgrade": "websocket",
"sec-websocket-accept": "[redacted]",
"sec-websocket-extensions": "permessage-deflate; server_no_context_takeover; server_max_window_bits=15; client_max_window_bits=15"
code to reproduce
The dependency versions used are outlined in Cargo.toml:
[dependencies]
ws-tool = "0.10.2"
rustls-connector = { version = "0.16", features = ["webpki-roots-certs"] }
tracing-subscriber = "0.3.18"
tracing = "0.1.40"
serde_json = "1.0.108"
main.rs
use ws_tool::frame::OpCode;
pub type WsStream = ws_tool::codec::DeflateCodec<
rustls_connector::rustls::StreamOwned<
rustls_connector::rustls::ClientConnection,
std::net::TcpStream,
>,
>;
fn ws_extension_header() -> String {
use ws_tool::codec::WindowBit;
use ws_tool::ClientConfig;
ws_tool::codec::PMDConfig {
server_no_context_takeover: ClientConfig::default().context_take_over,
client_no_context_takeover: ClientConfig::default().context_take_over,
// okx support WindowBit 9-15, why binnance 9-14 is ok but 15 would get error {"error":{"code":3,"msg":"Invalid JSON: EOF while parsing a list at line 1 c
// replace below two WindowBit to WindowBit::Fourteen would request ok, only WindowBit::Fifteen request error
server_max_window_bits: WindowBit::Fifteen,
client_max_window_bits: WindowBit::Fifteen,
// server_max_window_bits: WindowBit::Fourteen,
// client_max_window_bits: WindowBit::Fourteen,
}
.ext_string()
}
pub fn ws_connect(
url: &ws_tool::http::Uri,
permessage_deflate: bool,
) -> WsStream {
let host = url.host().unwrap();
let schema = url.scheme_str().unwrap();
let port = if let Some(port) = url.port() {
port.as_u16()
} else if schema == "wss" {
443
} else if schema == "ws" {
80
} else {
unreachable!()
};
let stream = std::net::TcpStream::connect((host, port)).unwrap();
// let stream = socks::Socks5Stream::connect(("192.168.110.123", 10808), (host, port)).unwrap().into_inner();
stream
.set_read_timeout(Some(std::time::Duration::from_secs(5)))
.unwrap();
let stream = ws_tool::connector::wrap_rustls(stream, host, Vec::new()).unwrap();
let mut builder = ws_tool::ClientBuilder::new();
if permessage_deflate {
builder = builder.extension(ws_extension_header());
}
builder.with_stream(url.clone(), stream, ws_tool::codec::DeflateCodec::check_fn).unwrap()
}
fn main() {
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::DEBUG)
.with_file(true)
.with_line_number(true)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("Failed to set tracing subscriber");
// let url = "wss://ws.okx.com:8443/ws/v5/public";
let url = "wss://fstream.binance.com/stream";
let mut stream = ws_connect(&url.parse().unwrap(), true);
if url.contains("okx") {
stream.send(OpCode::Text, br#"{
"op": "subscribe",
"args": [{
"channel": "tickers",
"instId": "BTC-USDT-SWAP"
}]
}"#).unwrap();
} else {
// let listen_key = std::env::var("LISTEN_KEY").expect("env LISTEN_KEY not set");
// assert!(listen_key.len() == 64);
let json = serde_json::to_string(&serde_json::json!({
"id":1,
"method":"SUBSCRIBE",
"params":[
"btcusdt@bookTicker",
// listen_key
"1234"
]
})).unwrap();
stream.send(OpCode::Text, json.as_bytes()).unwrap();
}
for _ in 0..10 {
let (_code, data) = stream.receive().unwrap();
println!("{}", std::str::from_utf8(data).unwrap());
}
}
Additional Context
I have verified that the ws-tool WebSocket client library works correctly with the OKX exchange WebSocket server when using WindowBit::Fifteen. This leads me to believe that the issue may lie with the Binance WebSocket server’s handling of permessage-deflate with window_bits set to the maximum value of 15.
I would appreciate any insights or suggestions on how to resolve this issue.