Fixed websockets with the new and improved sockets.
While I'm at it, changed a bit the way it works, the old one was a bit (a lot ?) quick and dirty, so here's that.
Pull-Request: #1096
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
fun ready_to_read(timeout: Int): Bool
do
if _buffer_pos < _buffer.length then return true
- if eof then return false
+ if end_reached then return false
var events = [new NativeSocketPollValues.pollin]
return pollin(events, timeout).length != 0
end
if closed then return
if socket.close >= 0 then
closed = true
+ end_reached = true
end
end
from = 0
end
- var realFrom = index_from + from
+ var new_from = index_from + from
- if (realFrom + count) > index_to then return new FlatString.with_infos(items, index_to - realFrom + 1, realFrom, index_to)
+ if (new_from + count) > index_to then
+ var new_len = index_to - new_from + 1
+ if new_len <= 0 then return empty
+ return new FlatString.with_infos(items, new_len, new_from, index_to)
+ end
- if count == 0 then return empty
+ if count <= 0 then return empty
- var to = realFrom + count - 1
+ var to = new_from + count - 1
- return new FlatString.with_infos(items, to - realFrom + 1, realFrom, to)
+ return new FlatString.with_infos(items, to - new_from + 1, new_from, to)
end
redef fun empty do return "".as(FlatString)
import websocket
-var sock = new WebSocket(8088, 1)
+var sock = new WebSocketListener(8088, 1)
var msg: String
print sys.errno.strerror
end
-sock.accept
+var cli: TCPStream
-while not sock.listener.closed do
- if not sock.connected then sock.accept
- if sys.stdin.poll_in then
- msg = gets
- printn "Received message : {msg}"
- if msg == "exit" then sock.close
- if msg == "disconnect" then sock.disconnect_client
- sock.write(msg)
- end
- if sock.can_read(10) then
- msg = sock.read_line
- if msg != "" then print msg
+while not sock.closed do
+ cli = sock.accept
+ while cli.connected do
+ if sys.stdin.poll_in then
+ msg = gets
+ printn "Received message : {msg}"
+ if msg == "disconnect" then cli.close
+ cli.write(msg)
+ end
+ if cli.can_read(10) then
+ msg = ""
+ while cli.can_read(0) do msg += cli.read(100)
+ if msg != "" then print msg
+ end
end
end
intrude import standard::stream
-# Websocket compatible server, works as an extra layer to the original Sockets
-class WebSocket
- super BufferedIStream
- super OStream
- super PollableIStream
-
- # Client connection to the server
- var client: TCPStream
+# Websocket compatible listener
+#
+# Produces Websocket client-server connections
+class WebSocketListener
+ super Socket
# Socket listening to connections on a defined port
var listener: TCPServer
# Creates a new Websocket server listening on given port with `max_clients` slots available
init(port: Int, max_clients: Int)
do
- _buffer = new FlatBuffer
- _buffer_pos = 0
listener = new TCPServer(port)
listener.listen max_clients
end
- # Accept an incoming connection and initializes the handshake
- fun accept
+ # Accepts an incoming connection
+ fun accept: WebsocketConnection
do
assert not listener.closed
var client = listener.accept
assert client != null
- self.client = client
+ return new WebsocketConnection(listener.port, "", client)
+ end
+
+ # Stop listening for incoming connections
+ fun close
+ do
+ listener.close
+ end
+end
+
+# Connection to a websocket client
+#
+# Can be used to communicate with a client
+class WebsocketConnection
+ super TCPStream
+
+ init do
+ _buffer = new FlatBuffer
+ _buffer_pos = 0
var headers = parse_handshake
var resp = handshake_response(headers)
client.write(resp)
end
- # Disconnect from a client
- fun disconnect_client
- do
- client.close
- end
+ # Client connection to the server
+ var client: TCPStream
- # Disconnects the client if one is connected
- # And stops the server
+ # Disconnect from a client
redef fun close
do
client.close
- listener.close
end
# Parses the input handshake sent by the client
# Reads an HTTP frame
protected fun read_http_frame(buf: Buffer): String
do
- client.append_line_to(buf)
- buf.chars.add('\n')
- if buf.has_substring("\r\n\r\n", buf.length - 4) then return buf.to_s
+ buf.append client.read_line
+ buf.append("\r\n")
+ if buf.has_suffix("\r\n\r\n") then return buf.to_s
return read_http_frame(buf)
end
end
# Checks if a connection to a client is available
- fun connected: Bool do return client.connected
+ redef fun connected do return client.connected
- redef fun write(msg: Text)
+ redef fun write(msg)
do
client.write(frame_message(msg.to_s))
end
unpad_message
end
- redef fun end_reached do return _buffer_pos >= _buffer.length and client.eof
+ redef fun end_reached do return client._buffer_pos >= client._buffer.length and client.end_reached
# Is there some data available to be read ?
fun can_read(timeout: Int): Bool do return client.ready_to_read(timeout)
sock.close
sys.set_io(ns,ns,ns)
else if self.toolcontext.opt_websocket_mode.value then
- var websock = new WebSocket(toolcontext.opt_debug_port.value, 1)
- websock.accept
- sys.set_io(websock,websock,websock)
+ var websock = new WebSocketListener(toolcontext.opt_debug_port.value, 1)
+ var cli = websock.accept
+ websock.close
+ sys.set_io(cli,cli,cli)
end
end
fun close_stdstreams
do
- if sys.stdin isa WebSocket or sys.stdin isa TCPStream then
+ if sys.stdin isa TCPStream then
sys.stdin.close
sys.stdout.close
sys.stderr.close
do
self.stdin = istream
self.stdout = ostream
- self.stderr = ostream
+ self.stderr = errstream
end
end
module primitive_types
intrude import standard::file
+intrude import standard::string
# Wrapper for `NativeFile`
class PrimitiveNativeFile
- var file: FStream
+ var file: IOS
init native_stdin do
- file = new IFStream.from_fd(0)
+ file = sys.stdin
end
init native_stdout do
- file = new OFStream.from_fd(1)
+ file = sys.stdout
end
init native_stderr do
- file = new OFStream.from_fd(2)
+ file = sys.stderr
end
init io_open_read(path: String) do
file = new OFStream.open(path.to_s)
end
- fun address_is_null: Bool do return file._file.address_is_null
+ fun address_is_null: Bool do
+ if file isa FStream then return file.as(FStream)._file.address_is_null
+ return false
+ end
- fun io_read(buf: NativeString, len: Int): Int do return file._file.io_read(buf, len)
+ fun io_read(buf: NativeString, len: Int): Int do
+ if file isa FStream then return file.as(FStream)._file.io_read(buf, len)
+ var str = file.as(IStream).read(len)
+ str.to_cstring.copy_to(buf, str.length, 0, 0)
+ return str.length
+ end
- fun io_write(buf: NativeString, len: Int): Int do return file._file.io_write(buf, len)
+ fun io_write(buf: NativeString, len: Int): Int do
+ if file isa FStream then return file.as(FStream)._file.io_write(buf, len)
+ file.as(OStream).write(buf.to_s_with_length(len))
+ return len
+ end
- fun io_close: Int do return file._file.io_close
+ fun io_close: Int do
+ if file isa FStream then return file.as(FStream)._file.io_close
+ file.close
+ return 0
+ end
- fun fileno: Int do return file._file.fileno
+ fun fileno: Int do
+ if file isa FStream then return file.as(FStream)._file.fileno
+ return 0
+ end
- fun flush: Int do return file._file.flush
+ fun flush: Int do
+ if file isa FStream then return file.as(FStream)._file.flush
+ return 0
+ end
fun set_buffering_type(size, mode: Int): Int do
- return file._file.set_buffering_type(size, mode)
+ if file isa FStream then return file.as(FStream)._file.set_buffering_type(size, mode)
+ return 0
end
end