|
| 1 | +# Extensions |
| 2 | + |
| 3 | +This guide explains how to use `protocol-websocket` for implementing a websocket client and server using extensions. |
| 4 | + |
| 5 | +## Per-message Deflate |
| 6 | + |
| 7 | +WebSockets have a mechanism for implementing extensions. At the time of writing, the only published extension is `permessage-deflate` for per-message compression. It operates on complete messages rather than individual frames. |
| 8 | + |
| 9 | +Clients and servers can negotiate a set of extensions to use. The server can accept or reject these extensions. The client can then instantiate the extensions and apply them to the connection. More specifically, clients need to define a set of extensions they want to support: |
| 10 | + |
| 11 | +~~~ ruby |
| 12 | +require 'protocol/websocket' |
| 13 | +require 'protocol/websocket/extensions' |
| 14 | + |
| 15 | +client_extensions = Protocol::WebSocket::Extensions::Client.new([ |
| 16 | + [Protocol::WebSocket::Extension::Compression, {}] |
| 17 | +]) |
| 18 | + |
| 19 | +offer_headers = [] |
| 20 | + |
| 21 | +client_extensions.offer do |header| |
| 22 | + offer_headers << header.join(';') |
| 23 | +end |
| 24 | + |
| 25 | +offer_headers # => ["permessage-deflate;client_max_window_bits"] |
| 26 | +~~~ |
| 27 | + |
| 28 | +This is transmitted to the server via the `Sec-WebSocket-Extensions` header. The server processes this and returns a subset of accepted extensions. The client receives a list of accepted extensions and instantiates them: |
| 29 | + |
| 30 | +~~~ ruby |
| 31 | +server_extensions = Protocol::WebSocket::Extensions::Server.new([ |
| 32 | + [Protocol::WebSocket::Extension::Compression, {}] |
| 33 | +]) |
| 34 | + |
| 35 | +accepted_headers = [] |
| 36 | + |
| 37 | +server_extensions.accept(offer_headers) do |header| |
| 38 | + accepted_headers << header.join(';') |
| 39 | +end |
| 40 | + |
| 41 | +accepted_headers # => ["permessage-deflate;client_max_window_bits=15"] |
| 42 | + |
| 43 | +client_extensions.accept(accepted_headers) |
| 44 | +~~~ |
| 45 | + |
| 46 | +We can check the extensions are accepted: |
| 47 | + |
| 48 | +~~~ ruby |
| 49 | +server_extensions.accepted |
| 50 | +# => [[Protocol::WebSocket::Extension::Compression, {:client_max_window_bits=>15}]] |
| 51 | + |
| 52 | +client_extensions.accepted |
| 53 | +# => [[Protocol::WebSocket::Extension::Compression, {:client_max_window_bits=>15}]] |
| 54 | +~~~ |
| 55 | + |
| 56 | +Once the extensions are negotiated, they can be applied to the connection: |
| 57 | + |
| 58 | +~~~ ruby |
| 59 | +require 'protocol/websocket/connection' |
| 60 | +require 'socket' |
| 61 | + |
| 62 | +sockets = Socket.pair(Socket::PF_UNIX, Socket::SOCK_STREAM) |
| 63 | + |
| 64 | +client = Protocol::WebSocket::Connection.new(Protocol::WebSocket::Framer.new(sockets.first)) |
| 65 | +server = Protocol::WebSocket::Connection.new(Protocol::WebSocket::Framer.new(sockets.last)) |
| 66 | + |
| 67 | +client_extensions.apply(client) |
| 68 | +server_extensions.apply(server) |
| 69 | + |
| 70 | +# We can see that the appropriate wrappers have been added to the connections: |
| 71 | +client.reader.class # => Protocol::WebSocket::Extension::Compression::Inflate |
| 72 | +client.writer.class # => Protocol::WebSocket::Extension::Compression::Deflate |
| 73 | +server.reader.class # => Protocol::WebSocket::Extension::Compression::Inflate |
| 74 | +server.writer.class # => Protocol::WebSocket::Extension::Compression::Deflate |
| 75 | + |
| 76 | +client.send_text("Hello World") |
| 77 | +# => #<Protocol::WebSocket::TextFrame:0x000000011d555460 @finished=true, @flags=4, @length=13, @mask=nil, @opcode=1, @payload="\xF2H\xCD\xC9\xC9W\b\xCF/\xCAI\x01\x00"> |
| 78 | + |
| 79 | +server.read |
| 80 | +# => #<Protocol::WebSocket::TextMessage:0x000000011e1e5248 @buffer="Hello World"> |
| 81 | +~~~ |
| 82 | + |
| 83 | +It's possible to disable compression on a per-message basis: |
| 84 | + |
| 85 | +~~~ ruby |
| 86 | +client.send_text("Hello World", compress: false) |
| 87 | +# => #<Protocol::WebSocket::TextFrame:0x00000001028945b0 @finished=true, @flags=0, @length=11, @mask=nil, @opcode=1, @payload="Hello World"> |
| 88 | + |
| 89 | +server.read |
| 90 | +# => #<Protocol::WebSocket::TextMessage:0x000000011e77eb50 @buffer="Hello World"> |
| 91 | +~~~ |
0 commit comments