mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
adding initial support for websocket subprotocol negotation
This commit is contained in:
parent
99fd946130
commit
7d0bede8ba
12 changed files with 87 additions and 49 deletions
|
@ -5,16 +5,18 @@
|
||||||
use hyper::header::Host;
|
use hyper::header::Host;
|
||||||
use net_traits::MessageData;
|
use net_traits::MessageData;
|
||||||
use net_traits::hosts::replace_hosts;
|
use net_traits::hosts::replace_hosts;
|
||||||
|
use net_traits::unwrap_websocket_protocol;
|
||||||
use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
|
use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
|
||||||
|
use std::ascii::AsciiExt;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use util::task::spawn_named;
|
use util::task::spawn_named;
|
||||||
use websocket::client::receiver::Receiver;
|
use websocket::client::receiver::Receiver;
|
||||||
use websocket::client::request::Url;
|
use websocket::client::request::Url;
|
||||||
use websocket::client::sender::Sender;
|
use websocket::client::sender::Sender;
|
||||||
use websocket::header::Origin;
|
use websocket::header::{Headers, Origin, WebSocketProtocol};
|
||||||
use websocket::message::Type;
|
use websocket::message::Type;
|
||||||
use websocket::result::WebSocketResult;
|
use websocket::result::{WebSocketError, WebSocketResult};
|
||||||
use websocket::stream::WebSocketStream;
|
use websocket::stream::WebSocketStream;
|
||||||
use websocket::ws::receiver::Receiver as WSReceiver;
|
use websocket::ws::receiver::Receiver as WSReceiver;
|
||||||
use websocket::ws::sender::Sender as Sender_Object;
|
use websocket::ws::sender::Sender as Sender_Object;
|
||||||
|
@ -23,8 +25,8 @@ use websocket::{Client, Message};
|
||||||
|
|
||||||
/// *Establish a WebSocket Connection* as defined in RFC 6455.
|
/// *Establish a WebSocket Connection* as defined in RFC 6455.
|
||||||
fn establish_a_websocket_connection(resource_url: &Url, net_url: (Host, String, bool),
|
fn establish_a_websocket_connection(resource_url: &Url, net_url: (Host, String, bool),
|
||||||
origin: String)
|
origin: String, protocols: Vec<String>)
|
||||||
-> WebSocketResult<(Sender<WebSocketStream>, Receiver<WebSocketStream>)> {
|
-> WebSocketResult<(Headers, Sender<WebSocketStream>, Receiver<WebSocketStream>)> {
|
||||||
|
|
||||||
let host = Host {
|
let host = Host {
|
||||||
hostname: resource_url.serialize_host().unwrap(),
|
hostname: resource_url.serialize_host().unwrap(),
|
||||||
|
@ -34,11 +36,26 @@ fn establish_a_websocket_connection(resource_url: &Url, net_url: (Host, String,
|
||||||
let mut request = try!(Client::connect(net_url));
|
let mut request = try!(Client::connect(net_url));
|
||||||
request.headers.set(Origin(origin));
|
request.headers.set(Origin(origin));
|
||||||
request.headers.set(host);
|
request.headers.set(host);
|
||||||
|
if !protocols.is_empty() {
|
||||||
|
request.headers.set(WebSocketProtocol(protocols.clone()));
|
||||||
|
};
|
||||||
|
|
||||||
let response = try!(request.send());
|
let response = try!(request.send());
|
||||||
try!(response.validate());
|
try!(response.validate());
|
||||||
|
|
||||||
Ok(response.begin().split())
|
{
|
||||||
|
let protocol_in_use = unwrap_websocket_protocol(response.protocol());
|
||||||
|
if let Some(protocol_name) = protocol_in_use {
|
||||||
|
if !protocols.is_empty() && !protocols.iter().any(|p| p.eq_ignore_ascii_case(protocol_name)) {
|
||||||
|
return Err(WebSocketError::ProtocolError("Protocol in Use not in client-supplied protocol list"));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let headers = response.headers.clone();
|
||||||
|
let (sender, receiver) = response.begin().split();
|
||||||
|
Ok((headers, sender, receiver))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData) {
|
pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData) {
|
||||||
|
@ -60,10 +77,12 @@ pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData) {
|
||||||
};
|
};
|
||||||
let channel = establish_a_websocket_connection(&connect_data.resource_url,
|
let channel = establish_a_websocket_connection(&connect_data.resource_url,
|
||||||
net_url,
|
net_url,
|
||||||
connect_data.origin);
|
connect_data.origin,
|
||||||
let (ws_sender, mut receiver) = match channel {
|
connect_data.protocols.clone());
|
||||||
|
let (_, ws_sender, mut receiver) = match channel {
|
||||||
Ok(channel) => {
|
Ok(channel) => {
|
||||||
let _ = connect.event_sender.send(WebSocketNetworkEvent::ConnectionEstablished);
|
let _ = connect.event_sender.send(WebSocketNetworkEvent::ConnectionEstablished(channel.0.clone(),
|
||||||
|
connect_data.protocols));
|
||||||
channel
|
channel
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -37,6 +37,7 @@ use std::rc::Rc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::mem::HeapSizeOf;
|
use util::mem::HeapSizeOf;
|
||||||
|
use websocket::header;
|
||||||
|
|
||||||
pub mod hosts;
|
pub mod hosts;
|
||||||
pub mod image_cache_task;
|
pub mod image_cache_task;
|
||||||
|
@ -239,7 +240,7 @@ pub enum WebSocketDomAction {
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum WebSocketNetworkEvent {
|
pub enum WebSocketNetworkEvent {
|
||||||
ConnectionEstablished,
|
ConnectionEstablished(header::Headers, Vec<String>),
|
||||||
MessageReceived(MessageData),
|
MessageReceived(MessageData),
|
||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
@ -254,6 +255,7 @@ pub struct WebSocketCommunicate {
|
||||||
pub struct WebSocketConnectData {
|
pub struct WebSocketConnectData {
|
||||||
pub resource_url: Url,
|
pub resource_url: Url,
|
||||||
pub origin: String,
|
pub origin: String,
|
||||||
|
pub protocols: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
@ -429,6 +431,11 @@ pub fn load_whole_resource(resource_task: &ResourceTask, url: Url, pipeline_id:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defensively unwraps the protocol string from the response object's protocol
|
||||||
|
pub fn unwrap_websocket_protocol(wsp: Option<&header::WebSocketProtocol>) -> Option<&str> {
|
||||||
|
wsp.and_then(|protocol_list| protocol_list.get(0).map(|protocol| protocol.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
/// An unique identifier to keep track of each load message in the resource handler
|
/// An unique identifier to keep track of each load message in the resource handler
|
||||||
#[derive(Clone, PartialEq, Eq, Copy, Hash, Debug, Deserialize, Serialize, HeapSizeOf)]
|
#[derive(Clone, PartialEq, Eq, Copy, Hash, Debug, Deserialize, Serialize, HeapSizeOf)]
|
||||||
pub struct ResourceId(pub u32);
|
pub struct ResourceId(pub u32);
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface WebSocket : EventTarget {
|
||||||
attribute EventHandler onerror;
|
attribute EventHandler onerror;
|
||||||
attribute EventHandler onclose;
|
attribute EventHandler onclose;
|
||||||
//readonly attribute DOMString extensions;
|
//readonly attribute DOMString extensions;
|
||||||
//readonly attribute DOMString protocol;
|
readonly attribute DOMString protocol;
|
||||||
[Throws] void close([Clamp] optional unsigned short code, optional USVString reason);
|
[Throws] void close([Clamp] optional unsigned short code, optional USVString reason);
|
||||||
|
|
||||||
//messaging
|
//messaging
|
||||||
|
|
|
@ -29,6 +29,7 @@ use libc::{uint32_t, uint8_t};
|
||||||
use net_traits::ControlMsg::WebsocketConnect;
|
use net_traits::ControlMsg::WebsocketConnect;
|
||||||
use net_traits::MessageData;
|
use net_traits::MessageData;
|
||||||
use net_traits::hosts::replace_hosts;
|
use net_traits::hosts::replace_hosts;
|
||||||
|
use net_traits::unwrap_websocket_protocol;
|
||||||
use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
|
use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
|
||||||
use ref_slice::ref_slice;
|
use ref_slice::ref_slice;
|
||||||
use script_task::ScriptTaskEventCategory::WebSocketEvent;
|
use script_task::ScriptTaskEventCategory::WebSocketEvent;
|
||||||
|
@ -39,6 +40,7 @@ use std::ptr;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
use websocket::client::request::Url;
|
use websocket::client::request::Url;
|
||||||
|
use websocket::header::{Headers, WebSocketProtocol};
|
||||||
use websocket::ws::util::url::parse_url;
|
use websocket::ws::util::url::parse_url;
|
||||||
|
|
||||||
#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)]
|
#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)]
|
||||||
|
@ -146,6 +148,7 @@ pub struct WebSocket {
|
||||||
code: Cell<u16>, //Closing code
|
code: Cell<u16>, //Closing code
|
||||||
reason: DOMRefCell<String>, //Closing reason
|
reason: DOMRefCell<String>, //Closing reason
|
||||||
binary_type: Cell<BinaryType>,
|
binary_type: Cell<BinaryType>,
|
||||||
|
protocol: DOMRefCell<String>, //Subprotocol selected by server
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebSocket {
|
impl WebSocket {
|
||||||
|
@ -164,6 +167,7 @@ impl WebSocket {
|
||||||
code: Cell::new(0),
|
code: Cell::new(0),
|
||||||
reason: DOMRefCell::new("".to_owned()),
|
reason: DOMRefCell::new("".to_owned()),
|
||||||
binary_type: Cell::new(BinaryType::Blob),
|
binary_type: Cell::new(BinaryType::Blob),
|
||||||
|
protocol: DOMRefCell::new("".to_owned()),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -208,6 +212,8 @@ impl WebSocket {
|
||||||
return Err(Error::Syntax);
|
return Err(Error::Syntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: also check that no separator characters are used
|
||||||
|
// https://tools.ietf.org/html/rfc6455#section-4.1
|
||||||
if protocol.chars().any(|c| c < '\u{0021}' || c > '\u{007E}') {
|
if protocol.chars().any(|c| c < '\u{0021}' || c > '\u{007E}') {
|
||||||
return Err(Error::Syntax);
|
return Err(Error::Syntax);
|
||||||
}
|
}
|
||||||
|
@ -220,10 +226,12 @@ impl WebSocket {
|
||||||
let address = Trusted::new(global.get_cx(), ws.r(), global.networking_task_source());
|
let address = Trusted::new(global.get_cx(), ws.r(), global.networking_task_source());
|
||||||
|
|
||||||
let origin = global.get_url().serialize();
|
let origin = global.get_url().serialize();
|
||||||
|
let protocols: Vec<String> = protocols.iter().map(|x| String::from(x.clone())).collect();
|
||||||
|
|
||||||
let connect_data = WebSocketConnectData {
|
let connect_data = WebSocketConnectData {
|
||||||
resource_url: resource_url.clone(),
|
resource_url: resource_url.clone(),
|
||||||
origin: origin,
|
origin: origin,
|
||||||
|
protocols: protocols,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the interface for communication with the resource task
|
// Create the interface for communication with the resource task
|
||||||
|
@ -246,13 +254,14 @@ impl WebSocket {
|
||||||
|
|
||||||
let moved_address = address.clone();
|
let moved_address = address.clone();
|
||||||
let sender = global.networking_task_source();
|
let sender = global.networking_task_source();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Ok(event) = dom_event_receiver.recv() {
|
while let Ok(event) = dom_event_receiver.recv() {
|
||||||
match event {
|
match event {
|
||||||
WebSocketNetworkEvent::ConnectionEstablished => {
|
WebSocketNetworkEvent::ConnectionEstablished(headers, protocols) => {
|
||||||
let open_task = box ConnectionEstablishedTask {
|
let open_task = box ConnectionEstablishedTask {
|
||||||
addr: moved_address.clone(),
|
addr: moved_address.clone(),
|
||||||
|
headers: headers,
|
||||||
|
protocols: protocols,
|
||||||
};
|
};
|
||||||
sender.send(CommonScriptMsg::RunnableMsg(WebSocketEvent, open_task)).unwrap();
|
sender.send(CommonScriptMsg::RunnableMsg(WebSocketEvent, open_task)).unwrap();
|
||||||
},
|
},
|
||||||
|
@ -358,6 +367,11 @@ impl WebSocketMethods for WebSocket {
|
||||||
self.binary_type.set(btype)
|
self.binary_type.set(btype)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#dom-websocket-protocol
|
||||||
|
fn Protocol(&self) -> DOMString {
|
||||||
|
DOMString::from(self.protocol.borrow().clone())
|
||||||
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-websocket-send
|
// https://html.spec.whatwg.org/multipage/#dom-websocket-send
|
||||||
fn Send(&self, data: USVString) -> Fallible<()> {
|
fn Send(&self, data: USVString) -> Fallible<()> {
|
||||||
|
|
||||||
|
@ -448,22 +462,42 @@ impl WebSocketMethods for WebSocket {
|
||||||
/// Task queued when *the WebSocket connection is established*.
|
/// Task queued when *the WebSocket connection is established*.
|
||||||
struct ConnectionEstablishedTask {
|
struct ConnectionEstablishedTask {
|
||||||
addr: Trusted<WebSocket>,
|
addr: Trusted<WebSocket>,
|
||||||
|
protocols: Vec<String>,
|
||||||
|
headers: Headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runnable for ConnectionEstablishedTask {
|
impl Runnable for ConnectionEstablishedTask {
|
||||||
fn handler(self: Box<Self>) {
|
fn handler(self: Box<Self>) {
|
||||||
let ws = self.addr.root();
|
let ws = self.addr.root();
|
||||||
|
let global = ws.global.root();
|
||||||
|
|
||||||
// Step 1: Protocols.
|
// Step 1: Protocols.
|
||||||
|
if !self.protocols.is_empty() && self.headers.get::<WebSocketProtocol>().is_none() {
|
||||||
|
ws.failed.set(true);
|
||||||
|
ws.ready_state.set(WebSocketRequestState::Closing);
|
||||||
|
let task = box CloseTask {
|
||||||
|
addr: self.addr,
|
||||||
|
};
|
||||||
|
let sender = global.r().networking_task_source();
|
||||||
|
sender.send(CommonScriptMsg::RunnableMsg(WebSocketEvent, task)).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
ws.ready_state.set(WebSocketRequestState::Open);
|
ws.ready_state.set(WebSocketRequestState::Open);
|
||||||
|
|
||||||
// Step 3: Extensions.
|
// Step 3: Extensions.
|
||||||
|
//TODO: Set extensions to extensions in use
|
||||||
|
|
||||||
// Step 4: Protocols.
|
// Step 4: Protocols.
|
||||||
|
let protocol_in_use = unwrap_websocket_protocol(self.headers.get::<WebSocketProtocol>());
|
||||||
|
if let Some(protocol_name) = protocol_in_use {
|
||||||
|
*ws.protocol.borrow_mut() = protocol_name.to_owned();
|
||||||
|
};
|
||||||
|
|
||||||
// Step 5: Cookies.
|
// Step 5: Cookies.
|
||||||
|
|
||||||
// Step 6.
|
// Step 6.
|
||||||
let global = ws.global.root();
|
|
||||||
let event = Event::new(global.r(), atom!("open"),
|
let event = Event::new(global.r(), atom!("open"),
|
||||||
EventBubbles::DoesNotBubble,
|
EventBubbles::DoesNotBubble,
|
||||||
EventCancelable::NotCancelable);
|
EventCancelable::NotCancelable);
|
||||||
|
|
|
@ -7845,9 +7845,6 @@
|
||||||
[WebSocket interface: attribute extensions]
|
[WebSocket interface: attribute extensions]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[WebSocket interface: attribute protocol]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[CloseEvent interface: existence and properties of interface object]
|
[CloseEvent interface: existence and properties of interface object]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
[Create-Secure-valid-url-protocol-setCorrectly.htm]
|
[Create-Secure-valid-url-array-protocols.htm]
|
||||||
type: testharness
|
type: testharness
|
||||||
expected: TIMEOUT
|
expected: TIMEOUT
|
||||||
[W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - protocol should be set correctly - Connection should be opened]
|
[W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and array of protocol strings - Connection should be opened]
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - Connection should be closed]
|
|
||||||
expected: NOTRUN
|
expected: NOTRUN
|
||||||
|
|
||||||
|
[W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and array of protocol strings - Connection should be closed]
|
||||||
|
expected: FAIL
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Create-valid-url-array-protocols.htm]
|
||||||
|
type: testharness
|
||||||
|
expected: TIMEOUT
|
||||||
|
[W3C WebSocket API - Create WebSocket - Pass a valid URL and array of protocol strings - Connection should be opened]
|
||||||
|
expected: NOTRUN
|
||||||
|
|
||||||
|
[W3C WebSocket API - Create WebSocket - Pass a valid URL and array of protocol strings - Connection should be closed]
|
||||||
|
expected: FAIL
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[Create-valid-url-protocol-empty.htm]
|
|
||||||
type: testharness
|
|
||||||
[W3C WebSocket API - Create WebSocket - wsocket.protocol should be empty before connection is established]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[009.html]
|
|
||||||
type: testharness
|
|
||||||
expected: TIMEOUT
|
|
||||||
[WebSockets: protocol]
|
|
||||||
expected: TIMEOUT
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[012.html]
|
|
||||||
type: testharness
|
|
||||||
expected: TIMEOUT
|
|
||||||
[WebSockets: no protocol in response]
|
|
||||||
expected: TIMEOUT
|
|
||||||
|
|
|
@ -9,18 +9,12 @@
|
||||||
[WebSocket interface: attribute extensions]
|
[WebSocket interface: attribute extensions]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[WebSocket interface: attribute protocol]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Stringification of new WebSocket("ws://foo")]
|
[Stringification of new WebSocket("ws://foo")]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[WebSocket interface: new WebSocket("ws://foo") must inherit property "extensions" with the proper type (10)]
|
[WebSocket interface: new WebSocket("ws://foo") must inherit property "extensions" with the proper type (10)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[WebSocket interface: new WebSocket("ws://foo") must inherit property "protocol" with the proper type (11)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[CloseEvent interface: existence and properties of interface object]
|
[CloseEvent interface: existence and properties of interface object]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[protocol-initial.html]
|
|
||||||
type: testharness
|
|
||||||
[WebSockets: getting protocol in connecting]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue