Merge: File no buffered
authorJean Privat <jean@pryen.org>
Fri, 11 May 2018 20:01:08 +0000 (16:01 -0400)
committerJean Privat <jean@pryen.org>
Fri, 11 May 2018 20:01:08 +0000 (16:01 -0400)
Still as part of the Stream refactor, this commit removes the BufferedReader dependency for FileReader.

Note: last commit only to review, the previous one is from #2648

Pull-Request: #2649
Reviewed-by: Jean Privat <jean@pryen.org>

lib/bitmap/bitmap.nit
lib/core/codecs/utf8.nit
lib/core/core.nit
lib/core/protocol.nit [new file with mode: 0644]
lib/core/stream.nit
lib/curl/curl.nit
lib/curl/examples/curl_rest.nit [new file with mode: 0644]
lib/socket/socket.nit
lib/websocket/examples/websocket_server.nit
lib/websocket/websocket.nit

index 2bfb252..3c66f07 100644 (file)
@@ -130,10 +130,8 @@ class Bitmap
                # =============== Bitmap header ================
                for x in [0..13] do
                        var b = fileReader.read_byte
-                       if b == null then
-                               return
-                       end
-                       bitmap_header[x] = b.to_i
+                       if b < 0 then return
+                       bitmap_header[x] = b
                end
                self.file_size = get_value(bitmap_header.subarray(2, 4))
                self.data_offset = get_value(bitmap_header.subarray(10, 4))
@@ -141,8 +139,8 @@ class Bitmap
                # =============== DIB header ================
                for x in [0..39] do
                        var b = fileReader.read_byte
-                       if b == null then return
-                       dib_header[x] = b.to_i
+                       if b < 0 then return
+                       dib_header[x] = b
                end
                var dib_size = get_value(dib_header.subarray(0, 4))
                # only support BITMAPINFOHEADER
index 43f6235..d4d4a38 100644 (file)
@@ -68,6 +68,7 @@ private class UTF8Codec
        end
 
        redef fun decode_string(ns, len) do
+               assert len >= 0
                var ret = ns.to_s_unsafe(len, copy=false)
                var rit = ret.as(FlatString).items
                if rit == ns then
index 97a4271..54f2550 100644 (file)
@@ -33,3 +33,4 @@ import error
 import re
 import bytes
 import fixed_ints
+import protocol
diff --git a/lib/core/protocol.nit b/lib/core/protocol.nit
new file mode 100644 (file)
index 0000000..62a5f8a
--- /dev/null
@@ -0,0 +1,63 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+import stream
+
+# Stream class used as a Decorator over a stream
+class Protocol
+       super Stream
+
+       type STREAM: Stream
+
+       var origin: STREAM
+
+       redef fun close do origin.close
+end
+
+# Reader decorator over a read-capable stream
+class ReaderProtocol
+       super Reader
+       super Protocol
+
+       redef type STREAM: Reader
+
+       redef fun raw_read_byte do
+               return origin.read_byte
+       end
+
+       redef fun raw_read_bytes(ns, len) do
+               return origin.read_bytes_to_cstring(ns, len)
+       end
+end
+
+# Writer decorator over a write-capable stream
+class WriterProtocol
+       super Writer
+       super Protocol
+
+       redef type STREAM: Writer
+
+       redef fun write_byte(b) do
+               origin.write_byte(b)
+       end
+
+       redef fun write_bytes_from_cstring(ns, len) do
+               origin.write_bytes_from_cstring(ns, len)
+       end
+end
+
+# Reader/Writer decorator over a duplex-capable stream
+class DuplexProtocol
+       super Duplex
+       super WriterProtocol
+       super ReaderProtocol
+
+       redef type STREAM: Duplex
+end
index 03838a6..2ecf61b 100644 (file)
@@ -215,13 +215,16 @@ abstract class Reader
 
        # Reads a String of at most `i` length
        fun read(i: Int): String do
+               assert i >= 0
                var cs = new CString(i)
                var rd = read_bytes_to_cstring(cs, i)
+               if rd < 0 then return ""
                return codec.decode_string(cs, rd)
        end
 
        # Reads up to `max` bytes from source
        fun read_bytes(max: Int): Bytes do
+               assert max >= 0
                var cs = new CString(max)
                var rd = read_bytes_to_cstring(cs, max)
                return new Bytes(cs, rd, max)
index 64fbff2..34b2576 100644 (file)
@@ -122,71 +122,158 @@ class CurlHTTPRequest
        # instead of a TCP connection and DNS hostname resolution.
        var unix_socket_path: nullable String is writable
 
+       # The HTTP method, GET by default
+       #
+       # Must be a capitalized string with request name complying with RFC7231
+       var method: String = "GET" is optional, writable
+
        # Execute HTTP request
        #
        # By default, the response body is returned in an instance of `CurlResponse`.
        # This behavior can be customized by setting a custom `delegate`.
        fun execute: CurlResponse
        do
+               # Reset libcurl parameters as the lib is shared and options
+               # might affect requests from one another.
+               self.curl.native = new NativeCurl.easy_init
                if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
 
                var success_response = new CurlResponseSuccess
                var callback_receiver: CurlCallbacks = success_response
-               if self.delegate != null then callback_receiver = self.delegate.as(not null)
+               var err : CURLCode
+
+               # Prepare request
+               err = prepare_request(callback_receiver)
+               if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+               # Perform request
+               var err_resp = perform
+               if err_resp != null then return err_resp
+
+               var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code)
+               if not st_code == null then success_response.status_code = st_code
 
+               self.curl.native.easy_clean
+
+               return success_response
+       end
+
+       # Internal function that sets cURL options and request' parameters
+       private fun prepare_request(callback_receiver: CurlCallbacks) : CURLCode
+       do
+               var err
+
+               # cURL options and delegates
+               err = set_curl_options
+               if not err.is_ok then return err
+
+               # Callbacks
+               err = set_curl_callback(callback_receiver)
+               if not err.is_ok then return err
+
+               # HTTP Header
+               err = set_curl_http_header
+               if not err.is_ok then return err
+
+               # Set HTTP method and body
+               err = set_method
+               if not err.is_ok then return err
+               err = set_body
+
+               return err
+       end
+
+       # Set cURL parameters according to assigned HTTP method set in method
+       # attribute and body if the method allows it according to RFC7231
+       private fun set_method : CURLCode
+       do
+               var err : CURLCode
+
+               if self.method=="GET" then
+                       err=self.curl.native.easy_setopt(new CURLOption.get, 1)
+
+               else if self.method=="POST" then
+                       err=self.curl.native.easy_setopt(new CURLOption.post, 1)
+
+               else if self.method=="HEAD" then
+                       err=self.curl.native.easy_setopt(new CURLOption.no_body,1)
+
+               else
+                       err=self.curl.native.easy_setopt(new CURLOption.custom_request,self.method)
+               end
+               return err
+       end
+
+       # Set request's body
+       private fun set_body : CURLCode
+       do
+               var err
+               var data = self.data
+               var body = self.body
+
+               if data != null then
+                       var postdatas = data.to_url_encoded(self.curl)
+                       err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas)
+                       if not err.is_ok then return err
+               else if body != null then
+                       err = self.curl.native.easy_setopt(new CURLOption.postfields, body)
+                       if not err.is_ok then return err
+               end
+               return new CURLCode.ok
+       end
+
+       # Set cURL options
+       # such as delegate, follow location, URL, user agent and address family
+       private fun set_curl_options : CURLCode
+       do
                var err
 
                err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1)
-               if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               if not err.is_ok then return err
 
                err = self.curl.native.easy_setopt(new CURLOption.url, url)
-               if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               if not err.is_ok then return err
 
                var user_agent = user_agent
                if user_agent != null then
                        err = curl.native.easy_setopt(new CURLOption.user_agent, user_agent)
-                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+                       if not err.is_ok then return err
                end
 
                var unix_socket_path = unix_socket_path
                if unix_socket_path != null then
                        err = self.curl.native.easy_setopt(new CURLOption.unix_socket_path, unix_socket_path)
-                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+                       if not err.is_ok then return err
                end
+               return err
+       end
+
+       # Set cURL callback
+       private fun set_curl_callback(callback_receiver : CurlCallbacks) : CURLCode
+       do
+               var err
+
+               if self.delegate != null then callback_receiver = self.delegate.as(not null)
 
-               # Callbacks
                err = self.curl.native.register_callback_header(callback_receiver)
-               if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               if not err.is_ok then return err
 
                err = self.curl.native.register_callback_body(callback_receiver)
-               if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               if not err.is_ok then return err
 
-               # HTTP Header
+               return err
+       end
+
+       # Set cURL request header according to attribute headers
+       private fun set_curl_http_header : CURLCode
+       do
                var headers = self.headers
                if headers != null then
                        var headers_joined = headers.join_pairs(": ")
-                       err = self.curl.native.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist)
-                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
-               end
-
-               # Datas
-               var data = self.data
-               if data != null then
-                       var postdatas = data.to_url_encoded(self.curl)
-                       err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas)
-                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
-               else if body != null then
-                       err = self.curl.native.easy_setopt(new CURLOption.postfields, body.as(not null))
-                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+                       var err = self.curl.native.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist)
+                       if not err.is_ok then return err
                end
-
-               var err_resp = perform
-               if err_resp != null then return err_resp
-
-               var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code)
-               if not st_code == null then success_response.status_code = st_code
-
-               return success_response
+               return new CURLCode.ok
        end
 
        # Download to file given resource
@@ -246,6 +333,7 @@ class CurlHTTPRequest
        end
 end
 
+
 # CURL Mail Request
 #
 # ~~~
@@ -359,6 +447,7 @@ class CurlMail
        # Execute Mail request with settings configured through attribute
        fun execute: nullable CurlResponseFailed
        do
+               self.curl.native = new NativeCurl.easy_init
                if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
 
                var lines = new Array[String]
@@ -432,6 +521,8 @@ class CurlMail
                var err_resp = perform
                if err_resp != null then return err_resp
 
+               self.curl.native.easy_clean
+
                return null
        end
 end
diff --git a/lib/curl/examples/curl_rest.nit b/lib/curl/examples/curl_rest.nit
new file mode 100644 (file)
index 0000000..b6d69e3
--- /dev/null
@@ -0,0 +1,43 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2018 Matthieu Samuel Le Guellaut <leguellaut.matthieu@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# This example will send a Person object to the specified address
+#
+import curl
+import json
+
+class Person
+       serialize
+
+       var name : String
+       var age : Int
+
+end
+
+var url = "http://example.com"
+
+# POST REQUEST
+var my_request = new CurlHTTPRequest(url, method="POST")
+var person = new Person("Jean",12)
+my_request.body = person.serialize_to_json
+my_request.execute
+
+# # USE WITH SOCKET ADDRESS FAMILY
+# var my_unix_request = new CurlHTTPRequest("http:///tmp/", method="POST")
+# my_unix_request.body = person.serialize_to_json
+# my_unix_request.unix_socket_path = "/tmp/test.sock"
+# my_unix_request.execute
index 0a1d5b1..983164c 100644 (file)
@@ -55,33 +55,28 @@ end
 # Simple communication stream with a remote socket
 class TCPStream
        super TCPSocket
-       super BufferedReader
-       super Writer
        super PollableReader
+       super Duplex
 
        # Real canonical name of the host to which `self` is connected
        var host: String
 
        private var addrin: NativeSocketAddrIn is noinit
 
-       redef var end_reached = false
+       redef var closed = false
 
        # TODO make init private
 
        # Creates a socket connection to host `host` on port `port`
        init connect(host: String, port: Int)
        do
-               _buffer = new CString(1024)
-               _buffer_pos = 0
                native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
                        new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_unspec)
                if native.address_is_null then
-                       end_reached = true
                        closed = true
                        return
                end
                if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
-                       end_reached = true
                        closed = true
                        return
                end
@@ -92,7 +87,6 @@ class TCPStream
                        last_error = new IOError.from_h_errno
 
                        closed = true
-                       end_reached = true
 
                        return
                end
@@ -105,13 +99,10 @@ class TCPStream
                init(addrin.port, hostent.h_name.to_s)
 
                closed = not internal_connect
-               end_reached = closed
                if closed then
                        # Connection failed
                        last_error = new IOError.from_errno
                end
-
-               prepare_buffer(1024)
        end
 
        # Creates a client socket, this is meant to be used by accept only
@@ -122,8 +113,6 @@ class TCPStream
                address = addrin.address.to_s
 
                init(addrin.port, address)
-
-               prepare_buffer(1024)
        end
 
        redef fun poll_in do return ready_to_read(0)
@@ -134,7 +123,7 @@ class TCPStream
        #
        # timeout : Time in milliseconds before stopping listening for events on this socket
        private fun pollin(event_types: Array[NativeSocketPollValues], timeout: Int): Array[NativeSocketPollValues] do
-               if end_reached then return new Array[NativeSocketPollValues]
+               if closed then return new Array[NativeSocketPollValues]
                return native.socket_poll(new PollFD.from_poll_values(native.descriptor, event_types), timeout)
        end
 
@@ -143,8 +132,6 @@ class TCPStream
        # timeout : Time in milliseconds before stopping to wait for events
        fun ready_to_read(timeout: Int): Bool
        do
-               if _buffer_pos < _buffer_length then return true
-               if end_reached then return false
                var events = [new NativeSocketPollValues.pollin]
                return pollin(events, timeout).length != 0
        end
@@ -161,7 +148,7 @@ class TCPStream
                end
        end
 
-       redef fun is_writable do return not end_reached
+       redef fun is_writable do return not closed
 
        # Establishes a connection to socket addrin
        #
@@ -172,6 +159,19 @@ class TCPStream
                return native.connect(addrin) >= 0
        end
 
+       redef fun raw_read_byte do
+               var rd = native.read(write_buffer, 1)
+               if rd < 1 then return -1
+               return write_buffer[0].to_i
+       end
+
+       redef fun raw_read_bytes(ns, max) do
+               var rd = native.read(ns, max)
+               print "Read {rd} bytes"
+               if rd < 0 then return -1
+               return rd
+       end
+
        # If socket.end_reached, nothing will happen
        redef fun write(msg)
        do
@@ -198,36 +198,11 @@ class TCPStream
                write "\n"
        end
 
-       redef fun fill_buffer
-       do
-               if not connected then return
-
-               var read = native.read(_buffer, _buffer_capacity)
-               if read == -1 then
-                       close
-                       end_reached = true
-               end
-
-               _buffer_length = read
-               _buffer_pos = 0
-       end
-
-       # Enlarge `_buffer` to at least `len` bytes
-       fun enlarge(len: Int) do
-               if _buffer_capacity >= len then return
-               _buffer_capacity = len
-
-               var ns = new CString(_buffer_capacity)
-               _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
-               _buffer = ns
-       end
-
        redef fun close
        do
                if closed then return
                if native.close >= 0 then
                        closed = true
-                       end_reached = true
                end
        end
 
index 6b9e3b8..d8bc4e3 100644 (file)
@@ -19,7 +19,7 @@ module websocket_server is example
 
 import websocket
 
-var sock = new WebSocketListener(8088, 1)
+var sock = new WebsocketServer.with_infos(8088, 1)
 
 var msg: String
 
@@ -27,14 +27,14 @@ if sock.listener.closed then
        print sys.errno.strerror
 end
 
-var cli: TCPStream
+var cli: WebsocketConnection
 
 while not sock.closed do
        cli = sock.accept
        while cli.connected do
                if sys.stdin.poll_in then
                        msg = gets
-                       printn "Received message : {msg}"
+                       printn "Sending message : {msg}"
                        if msg == "disconnect" then cli.close
                        cli.write(msg)
                end
index ba00707..c5ea6bb 100644 (file)
@@ -1,7 +1,5 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2014 Lucas Bajolet <r4pass@hotmail.com>
-#
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
@@ -21,24 +19,26 @@ module websocket
 import socket
 import sha1
 import base64
+import crypto
 
-intrude import core::stream
-intrude import core::bytes
-
-# Websocket compatible listener
+# Websocket compatible server
 #
 # Produces Websocket client-server connections
-class WebSocketListener
-       super Socket
+class WebsocketServer
 
-       # Socket listening to connections on a defined port
+       # Socket listening for incoming Websocket connections
        var listener: TCPServer
 
-       # Creates a new Websocket server listening on given port with `max_clients` slots available
-       init(port: Int, max_clients: Int)
+       # Is `self` closed?
+       var closed = false
+
+       # Creates a new Websocket server listening on given port
+       # with `max_clients` slots available
+       init with_infos(port: Int, max_clients: Int)
        do
-               listener = new TCPServer(port)
+               var listener = new TCPServer(port)
                listener.listen max_clients
+               init(listener)
        end
 
        # Accepts an incoming connection
@@ -49,13 +49,14 @@ class WebSocketListener
                var client = listener.accept
                assert client != null
 
-               return new WebsocketConnection(listener.port, "", client)
+               return new WebsocketConnection(client)
        end
 
-       # Stop listening for incoming connections
+       # Close the server and the socket below it
        fun close
        do
                listener.close
+               closed = true
        end
 end
 
@@ -63,29 +64,49 @@ end
 #
 # Can be used to communicate with a client
 class WebsocketConnection
-       super TCPStream
+       super DuplexProtocol
+       super PollableReader
+
+       redef type STREAM: TCPStream
+
+       # Does the current frame have a mask?
+       private var has_mask = false
+
+       # Mask with which to XOR input data
+       private var mask = new CString(4)
+
+       # Offset of the mask to use when decoding input data
+       private var mask_offset = -1
+
+       # Length of the current frame
+       private var frame_length = -1
+
+       # Position in current frame
+       private var frame_cursor = -1
+
+       # Type of the current frame
+       var frame_type = -1
+
+       # Is `self` closed?
+       var closed = false
 
        init do
-               _buffer = new CString(1024)
-               _buffer_pos = 0
-               _buffer_capacity = 1024
-               _buffer_length = 0
                var headers = parse_handshake
                var resp = handshake_response(headers)
 
-               client.write(resp)
+               origin.write(resp)
        end
 
-       # Client connection to the server
-       var client: TCPStream
-
        # Disconnect from a client
-       redef fun close
-       do
-               client.close
+       redef fun close do
+               origin.close
+               closed = true
        end
 
-       # Parses the input handshake sent by the client
+       # Ping response message
+       private fun pong_msg: Bytes do return once b"\x8a\x00"
+
+       # Parse the input handshake sent by the client
        # See RFC 6455 for information
        private fun parse_handshake: Map[String,String]
        do
@@ -105,7 +126,7 @@ class WebsocketConnection
                return headmap
        end
 
-       # Generates the handshake
+       # Generate a handshake response
        private fun handshake_response(heads: Map[String,String]): String
        do
                var resp_map = new HashMap[String,String]
@@ -121,10 +142,10 @@ class WebsocketConnection
                return resp
        end
 
-       # Frames a text message to be sent to a client
-       private fun frame_message(msg: String): Bytes
+       # Frame a text message to be sent to a client
+       private fun frame_message(msg: Text): Bytes
        do
-               var ans_buffer = new Bytes.with_capacity(msg.length)
+               var ans_buffer = new Bytes.with_capacity(msg.byte_length + 2)
                # Flag for final frame set to 1
                # opcode set to 1 (for text)
                ans_buffer.add(129u8)
@@ -136,138 +157,138 @@ class WebsocketConnection
                        ans_buffer.add((msg.length >> 8).to_b)
                        ans_buffer.add(msg.length.to_b)
                end
-               if msg isa FlatString then
-                       ans_buffer.append_ns_from(msg.items, msg.length, msg.first_byte)
-               else
-                       for i in msg.substrings do
-                               ans_buffer.append_ns_from(i.as(FlatString).items, i.length, i.as(FlatString).first_byte)
-                       end
-               end
+               msg.append_to_bytes(ans_buffer)
                return ans_buffer
        end
 
-       # Reads an HTTP frame
+       # Read an HTTP frame
        protected fun read_http_frame(buf: Buffer): String
        do
-               var ln = client.read_line
+               var ln = origin.read_line
                buf.append ln
                buf.append "\r\n"
                if buf.has_suffix("\r\n\r\n") then return buf.to_s
                return read_http_frame(buf)
        end
 
-       # Gets the message from the client, unpads it and reconstitutes the message
-       private fun unpad_message do
-               var fin = false
-               var bf = new Bytes.empty
-               while not fin do
-                       var fst_byte = client.read_byte
-                       var snd_byte = client.read_byte
-                       if fst_byte < 0 or snd_byte < 0 then
-                               last_error = new IOError("Error: bad frame")
-                               client.close
-                               return
-                       end
-                       # First byte in msg is formatted this way :
-                       # |(fin - 1bit)|(RSV1 - 1bit)|(RSV2 - 1bit)|(RSV3 - 1bit)|(opcode - 4bits)
-                       # fin = Flag indicating if current frame is the last one
-                       # RSV1/2/3 = Extension flags, unsupported
-                       # Opcode values :
-                       #       %x0 denotes a continuation frame
-                       #       %x1 denotes a text frame
-                       #       %x2 denotes a binary frame
-                       #       %x3-7 are reserved for further non-control frames
-                       #       %x8 denotes a connection close
-                       #       %x9 denotes a ping
-                       #       %xA denotes a pong
-                       #       %xB-F are reserved for further control frames
-                       var fin_flag = fst_byte & 0b1000_0000
-                       if fin_flag != 0 then fin = true
-                       var opcode = fst_byte & 0b0000_1111
-                       if opcode == 9 then
-                               bf.add(138u8)
-                               bf.add(0u8)
-                               client.write(bf.to_s)
-                               _buffer_pos = _buffer_length
+       # Get a frame's information
+       private fun read_frame_info do
+               var fst_byte = origin.read_byte
+               var snd_byte = origin.read_byte
+               if fst_byte < 0 or snd_byte < 0 then
+                       last_error = new IOError("Error: bad frame")
+                       close
+                       return
+               end
+               # First byte in msg is formatted this way :
+               # |(fin - 1bit)|(RSV1 - 1bit)|(RSV2 - 1bit)|(RSV3 - 1bit)|(opcode - 4bits)
+               # fin = Flag indicating if current frame is the last one for the current message
+               # RSV1/2/3 = Extension flags, unsupported
+               # Opcode values :
+               #       %x0 denotes a continuation frame
+               #       %x1 denotes a text frame
+               #       %x2 denotes a binary frame
+               #       %x3-7 are reserved for further non-control frames
+               #       %x8 denotes a connection close
+               #       %x9 denotes a ping
+               #       %xA denotes a pong
+               #       %xB-F are reserved for further control frames
+               var opcode = fst_byte & 0b0000_1111
+               if opcode == 9 then
+                       origin.write_bytes(pong_msg)
+                       return
+               end
+               if opcode == 8 then
+                       close
+                       return
+               end
+               frame_type = opcode
+               # Second byte is formatted this way :
+               # |(mask - 1bit)|(payload length - 7 bits)
+               # As specified, if the payload length is 126 or 127
+               # The next 16 or 64 bits contain an extended payload length
+               var mask_flag = snd_byte & 0b1000_0000
+               var len = snd_byte & 0b0111_1111
+               var payload_ext_len = 0
+               if len == 126 then
+                       var tmp = origin.read_bytes(2)
+                       if tmp.length != 2 then
+                               last_error = new IOError("Error: received interrupted frame")
+                               origin.close
                                return
                        end
-                       if opcode == 8 then
-                               self.client.close
+                       payload_ext_len += tmp[0].to_i << 8
+                       payload_ext_len += tmp[1].to_i
+               else if len == 127 then
+                       var tmp = origin.read_bytes(8)
+                       if tmp.length != 8 then
+                               last_error = new IOError("Error: received interrupted frame")
+                               origin.close
                                return
                        end
-                       # Second byte is formatted this way :
-                       # |(mask - 1bit)|(payload length - 7 bits)
-                       # As specified, if the payload length is 126 or 127
-                       # The next 16 or 64 bits contain an extended payload length
-                       var mask_flag = snd_byte & 0b1000_0000
-                       var len = snd_byte & 0b0111_1111
-                       var payload_ext_len = 0
-                       if len == 126 then
-                               var tmp = client.read_bytes(2)
-                               if tmp.length != 2 then
-                                       last_error = new IOError("Error: received interrupted frame")
-                                       client.close
-                                       return
-                               end
-                               payload_ext_len += tmp[0].to_i << 8
-                               payload_ext_len += tmp[1].to_i
-                       else if len == 127 then
-                               var tmp = client.read_bytes(8)
-                               if tmp.length != 8 then
-                                       last_error = new IOError("Error: received interrupted frame")
-                                       client.close
-                                       return
-                               end
-                               for i in [0 .. 8[ do
-                                       payload_ext_len += tmp[i].to_i << (8 * (7 - i))
-                               end
-                       end
-                       if mask_flag != 0 then
-                               var mask = client.read_bytes(4).items
-                               if payload_ext_len != 0 then
-                                       len = payload_ext_len
-                               end
-                               var msg = client.read_bytes(len).items
-                               bf.append_ns(unmask_message(mask, msg, len), len)
+                       for i in [0 .. 8[ do
+                               payload_ext_len += tmp[i].to_i << (8 * (7 - i))
                        end
                end
-               _buffer = bf.items
-               _buffer_length = bf.length
+               if mask_flag != 0 then
+                       origin.read_bytes_to_cstring(mask, 4)
+                       has_mask = true
+               else
+                       mask.memset(0, 4)
+                       has_mask = false
+               end
+               if payload_ext_len != 0 then
+                       len = payload_ext_len
+               end
+               frame_length = len
+               frame_cursor = 0
        end
 
-       # Unmasks a message sent by a client
-       private fun unmask_message(key: CString, message: CString, len: Int): CString
-       do
-               var return_message = new CString(len)
-
-               for i in [0 .. len[ do
-                       return_message[i] = message[i] ^ key[i % 4]
+       redef fun raw_read_byte do
+               while not closed and frame_cursor >= frame_length do
+                       read_frame_info
                end
+               if closed then return -1
+               var b = origin.read_byte
+               if b >= 0 then
+                       frame_cursor += 1
+               end
+               return b
+       end
 
-               return return_message
+       redef fun raw_read_bytes(ns, len) do
+               while not closed and frame_cursor >= frame_length do
+                       read_frame_info
+               end
+               if closed then return -1
+               var available = frame_length - frame_cursor
+               var to_rd = len.min(available)
+               var rd = origin.read_bytes_to_cstring(ns, to_rd)
+               if rd < 0 then
+                       close
+                       return 0
+               end
+               if has_mask then
+                       ns.xor(mask, rd, 4, mask_offset)
+                       mask_offset = rd % 4
+               end
+               frame_cursor += rd
+               return rd
        end
 
        # Checks if a connection to a client is available
-       redef fun connected do return client.connected
+       fun connected: Bool do return not closed and origin.connected
 
        redef fun write_bytes_from_cstring(ns, len) do
-               client.write_bytes(frame_message(ns.to_s_unsafe(len)))
+               origin.write_bytes(frame_message(ns.to_s_unsafe(len)))
        end
 
-       redef fun write(msg) do client.write(frame_message(msg.to_s).to_s)
-
-       redef fun is_writable do return client.connected
-
-       redef fun fill_buffer
-       do
-               buffer_reset
-               unpad_message
-       end
+       redef fun write(msg) do origin.write_bytes(frame_message(msg))
 
-       redef fun end_reached do return client._buffer_pos >= client._buffer_length and client.end_reached
+       redef fun is_writable do return origin.connected
 
        # Is there some data available to be read ?
-       fun can_read(timeout: Int): Bool do return client.ready_to_read(timeout)
+       fun can_read(timeout: Int): Bool do return  not closed and origin.ready_to_read(timeout)
 
-       redef fun poll_in do return client.poll_in
+       redef fun poll_in do return origin.poll_in
 end