Skip to content

Commit 1cb666f

Browse files
committed
Update release notes & documentation.
1 parent dd2775d commit 1cb666f

6 files changed

Lines changed: 264 additions & 2 deletions

File tree

context/extensions.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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+
~~~

context/getting-started.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Getting Started
2+
3+
This guide explains how to use `protocol-websocket` for implementing a websocket client and server.
4+
5+
## Installation
6+
7+
Add the gem to your project:
8+
9+
~~~ bash
10+
$ bundle add protocol-websocket
11+
~~~
12+
13+
## Core Concepts
14+
15+
`protocol-websocket` has several core concepts:
16+
17+
- A {ruby Protocol::WebSocket::Frame} is the base class which is used to represent protocol-specific structured frames.
18+
- A {ruby Protocol::WebSocket::Framer} wraps an underlying {ruby Async::IO::Stream} for reading and writing binary data into structured frames.
19+
- A {ruby Protocol::WebSocket::Connection} wraps a framer and implements for implementing connection specific interactions like reading and writing text.
20+
- A {ruby Protocol::WebSocket::Message} is a higher-level abstraction for reading and writing messages.
21+
22+
## Bi-directional Communication
23+
24+
We can create a small bi-directional WebSocket client server:
25+
26+
~~~ ruby
27+
require 'protocol/websocket'
28+
require 'protocol/websocket/connection'
29+
require 'socket'
30+
31+
sockets = Socket.pair(Socket::PF_UNIX, Socket::SOCK_STREAM)
32+
33+
client = Protocol::WebSocket::Connection.new(Protocol::WebSocket::Framer.new(sockets.first))
34+
server = Protocol::WebSocket::Connection.new(Protocol::WebSocket::Framer.new(sockets.last))
35+
36+
client.send_text("Hello World")
37+
server.read
38+
# #<Protocol::WebSocket::TextMessage:0x000000011d2338e0 @buffer="Hello World">
39+
40+
client.send_binary("Hello World")
41+
server.read
42+
#<Protocol::WebSocket::BinaryMessage:0x000000011d371db0 @buffer="Hello World">
43+
~~~
44+
45+
## Messages
46+
47+
We can also use the {ruby Protocol::WebSocket::Message} class to read and write messages:
48+
49+
~~~ ruby
50+
require 'protocol/websocket'
51+
require 'protocol/websocket/connection'
52+
require 'socket'
53+
54+
sockets = Socket.pair(Socket::PF_UNIX, Socket::SOCK_STREAM)
55+
56+
client = Protocol::WebSocket::Connection.new(Protocol::WebSocket::Framer.new(sockets.first))
57+
server = Protocol::WebSocket::Connection.new(Protocol::WebSocket::Framer.new(sockets.last))
58+
59+
# Encode a value using JSON:
60+
message = Protocol::WebSocket::TextMessage.generate({hello: "world"})
61+
62+
client.write(message)
63+
server.read.to_h
64+
# {:hello=>"world"}
65+
~~~
66+
67+
### Text Messages
68+
69+
Text messages contain UTF-8 encoded text. Invalid UTF-8 sequences will result in errors. Text messages are useful for sending structured data like JSON.
70+
71+
### Binary Messages
72+
73+
Binary messages contain arbitrary binary data. They can be used to send any kind of data. Binary messages are useful for sending files or other binary data, like images or video.

context/index.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Automatically generated context index for Utopia::Project guides.
2+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3+
---
4+
description: A low level implementation of the WebSocket protocol.
5+
metadata:
6+
documentation_uri: https://socketry.github.io/protocol-websocket/
7+
source_code_uri: https://github.com/socketry/protocol-websocket.git
8+
files:
9+
- path: getting-started.md
10+
title: Getting Started
11+
description: This guide explains how to use `protocol-websocket` for implementing
12+
a websocket client and server.
13+
- path: extensions.md
14+
title: Extensions
15+
description: This guide explains how to use `protocol-websocket` for implementing
16+
a websocket client and server using extensions.

protocol-websocket.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
2020
"source_code_uri" => "https://github.com/socketry/protocol-websocket.git",
2121
}
2222

23-
spec.files = Dir.glob(["{lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__)
23+
spec.files = Dir.glob(["{context,lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__)
2424

2525
spec.required_ruby_version = ">= 3.3"
2626

readme.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,49 @@ Please see the [project documentation](https://socketry.github.io/protocol-webso
1414

1515
## Releases
1616

17-
There are no documented releases.
17+
Please see the [project releases](https://socketry.github.io/protocol-websocket/releases/index) for all releases.
18+
19+
### Unreleased
20+
21+
- All frame reading and writing logic has been consolidated into `Framer` to improve performance.
22+
23+
### v0.20.2
24+
25+
- Fix error messages for `Frame` to be more descriptive.
26+
27+
### v0.20.1
28+
29+
- Revert masking enforcement option introduced in v0.20.0 due to compatibility issues.
30+
31+
### v0.20.0
32+
33+
- Introduce option `requires_masking` to `Framer` for enforcing masking on received frames.
34+
35+
### v0.19.1
36+
37+
- Ensure ping reply payload is packed correctly.
38+
39+
### v0.19.0
40+
41+
- Default to empty string for message buffer when no data is provided.
42+
43+
### v0.18.0
44+
45+
- Add `PingMessage` alongside `TextMessage` and `BinaryMessage` for a consistent message interface.
46+
- Remove `JSONMessage` (use application-level encoding instead).
47+
48+
### v0.17.0
49+
50+
- Introduce `#close_write` and `#shutdown` methods on `Connection` for more precise connection lifecycle control.
51+
52+
### v0.16.0
53+
54+
- Move `#send` logic into `Message` for better encapsulation.
55+
- Improve error handling when a `nil` message is passed.
56+
57+
### v0.15.0
58+
59+
- Require `Message` class by default.
1860

1961
## Contributing
2062

releases.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
11
# Releases
22

33
## Unreleased
4+
5+
- All frame reading and writing logic has been consolidated into `Framer` to improve performance.
6+
7+
## v0.20.2
8+
9+
- Fix error messages for `Frame` to be more descriptive.
10+
11+
## v0.20.1
12+
13+
- Revert masking enforcement option introduced in v0.20.0 due to compatibility issues.
14+
15+
## v0.20.0
16+
17+
- Introduce option `requires_masking` to `Framer` for enforcing masking on received frames.
18+
19+
## v0.19.1
20+
21+
- Ensure ping reply payload is packed correctly.
22+
23+
## v0.19.0
24+
25+
- Default to empty string for message buffer when no data is provided.
26+
27+
## v0.18.0
28+
29+
- Add `PingMessage` alongside `TextMessage` and `BinaryMessage` for a consistent message interface.
30+
- Remove `JSONMessage` (use application-level encoding instead).
31+
32+
## v0.17.0
33+
34+
- Introduce `#close_write` and `#shutdown` methods on `Connection` for more precise connection lifecycle control.
35+
36+
## v0.16.0
37+
38+
- Move `#send` logic into `Message` for better encapsulation.
39+
- Improve error handling when a `nil` message is passed.
40+
41+
## v0.15.0
42+
43+
- Require `Message` class by default.

0 commit comments

Comments
 (0)