permessage_deflate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +91 -0
- data/lib/permessage_deflate.rb +41 -0
- data/lib/permessage_deflate/client_session.rb +73 -0
- data/lib/permessage_deflate/server_session.rb +59 -0
- data/lib/permessage_deflate/session.rb +97 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5a45dd8e4933a9a41365043f0b770ad2f9df343c
|
4
|
+
data.tar.gz: 59b4bbba3b0fc00a970604c1a307bf8b80fc034a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b1c220ecbfe1f83d19c6a59d6045478bc827cb36f72e1f97ad914dc04002f188239bba1adde415f65cb85911a7c423e545e12d6ff1230d57e7bc62626c871af0
|
7
|
+
data.tar.gz: ae327691407e387275f0f0e8a5ce54817108a9f4b4dc2fbd9245f23514873ac3585e380ccfe04b38f3a64a5c93d9d8b6a828f87e06550b759cf2ba8f2a29e8f5
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# permessage_deflate [](http://travis-ci.org/faye/permessage-deflate-ruby)
|
2
|
+
|
3
|
+
Implements the
|
4
|
+
[permessage-deflate](https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression)
|
5
|
+
WebSocket protocol extension as a plugin for
|
6
|
+
[websocket-extensions](https://github.com/faye/websocket-extensions-ruby).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```
|
11
|
+
$ gem install permessage_deflate
|
12
|
+
```
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
Add the plugin to your extensions:
|
17
|
+
|
18
|
+
```rb
|
19
|
+
require 'websocket/extensions'
|
20
|
+
require 'permessage_deflate'
|
21
|
+
|
22
|
+
exts = WebSocket::Extensions.new
|
23
|
+
exts.add(PermessageDeflate)
|
24
|
+
```
|
25
|
+
|
26
|
+
The extension can be configured, for example:
|
27
|
+
|
28
|
+
```rb
|
29
|
+
require 'websocket/extensions'
|
30
|
+
require 'permessage_deflate'
|
31
|
+
|
32
|
+
deflate = PermessageDeflate.configure(
|
33
|
+
:level => Zlib::BEST_COMPRESSION,
|
34
|
+
:max_window_bits => 13
|
35
|
+
)
|
36
|
+
|
37
|
+
exts = WebSocket::Extensions.new
|
38
|
+
exts.add(deflate)
|
39
|
+
```
|
40
|
+
|
41
|
+
The set of available options can be split into two sets: those that control the
|
42
|
+
session's compressor for outgoing messages and do not need to be communicated to
|
43
|
+
the peer, and those that are negoatiated as part of the protocol. The settings
|
44
|
+
only affecting the compressor are described fully in the [Zlib
|
45
|
+
documentation](http://ruby-doc.org/stdlib-2.1.0/libdoc/zlib/rdoc/Zlib/Deflate.html#method-c-new):
|
46
|
+
|
47
|
+
* `:level`: sets the compression level, can be an integer from `0` to `9`, or
|
48
|
+
one of the contants `Zlib::NO_COMPRESSION`, `Zlib::BEST_SPEED`,
|
49
|
+
`Zlib::BEST_COMPRESSION`, or `Zlib::DEFAULT_COMPRESSION`
|
50
|
+
* `:mem_level`: sets how much memory the compressor allocates, can be an integer
|
51
|
+
from `1` to `9`, or one of the constants `Zlib::MAX_MEM_LEVEL`, or
|
52
|
+
`Zlib::DEF_MEM_LEVEL`
|
53
|
+
* `:strategy`: can be one of the constants `Zlib::FILTERED`,
|
54
|
+
`Zlib::HUFFMAN_ONLY`, `Zlib::RLE`, `Zlib::FIXED`, or `Zlib::DEFAULT_STRATEGY`
|
55
|
+
|
56
|
+
The other options relate to settings that are negotiated via the protocol and
|
57
|
+
can be used to set the local session's behaviour and control that of the peer:
|
58
|
+
|
59
|
+
* `:no_context_takeover`: if `true`, stops the session reusing a deflate context
|
60
|
+
between messages
|
61
|
+
* `:request_no_context_takeover`: if `true`, makes the session tell the other
|
62
|
+
peer not to reuse a deflate context between messages
|
63
|
+
* `:max_window_bits`: an integer from `8` to `15` inclusive that sets the
|
64
|
+
maximum size of the session's sliding window; a lower window size will be used
|
65
|
+
if requested by the peer
|
66
|
+
* `:request_max_window_bits`: an integer from `8` to `15` inclusive to ask the
|
67
|
+
other peer to use to set its maximum sliding window size, if supported
|
68
|
+
|
69
|
+
## License
|
70
|
+
|
71
|
+
(The MIT License)
|
72
|
+
|
73
|
+
Copyright (c) 2014 James Coglan
|
74
|
+
|
75
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
76
|
+
this software and associated documentation files (the 'Software'), to deal in
|
77
|
+
the Software without restriction, including without limitation the rights to
|
78
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
79
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
80
|
+
so, subject to the following conditions:
|
81
|
+
|
82
|
+
The above copyright notice and this permission notice shall be included in all
|
83
|
+
copies or substantial portions of the Software.
|
84
|
+
|
85
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
86
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
87
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
88
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
89
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
90
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
91
|
+
SOFTWARE.
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
class PermessageDeflate
|
4
|
+
root = File.expand_path('..', __FILE__)
|
5
|
+
require root + '/permessage_deflate/session'
|
6
|
+
require root + '/permessage_deflate/client_session'
|
7
|
+
require root + '/permessage_deflate/server_session'
|
8
|
+
|
9
|
+
ConfigurationError = Class.new(ArgumentError)
|
10
|
+
|
11
|
+
module Extension
|
12
|
+
define_method(:name) { 'permessage-deflate' }
|
13
|
+
define_method(:type) { 'permessage' }
|
14
|
+
define_method(:rsv1) { true }
|
15
|
+
define_method(:rsv2) { false }
|
16
|
+
define_method(:rsv3) { false }
|
17
|
+
|
18
|
+
def configure(options)
|
19
|
+
options = (@options || {}).merge(options)
|
20
|
+
PermessageDeflate.new(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_client_session
|
24
|
+
ClientSession.new(@options || {})
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_server_session(offers)
|
28
|
+
offers.each do |offer|
|
29
|
+
return ServerSession.new(@options || {}, offer) if ServerSession.valid_params?(offer)
|
30
|
+
end
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
include Extension
|
36
|
+
extend Extension
|
37
|
+
|
38
|
+
def initialize(options)
|
39
|
+
@options = options
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
class PermessageDeflate
|
2
|
+
class ClientSession < Session
|
3
|
+
|
4
|
+
def self.valid_params?(params)
|
5
|
+
return false unless super
|
6
|
+
|
7
|
+
if params.has_key?('client_max_window_bits')
|
8
|
+
return false unless VALID_WINDOW_BITS.include?(params['client_max_window_bits'])
|
9
|
+
end
|
10
|
+
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_offer
|
15
|
+
offer = {}
|
16
|
+
|
17
|
+
if @accept_no_context_takeover
|
18
|
+
offer['client_no_context_takeover'] = true
|
19
|
+
end
|
20
|
+
|
21
|
+
if @accept_max_window_bits
|
22
|
+
unless VALID_WINDOW_BITS.include?(@accept_max_window_bits)
|
23
|
+
raise ConfigurationError, 'Invalid value for max_window_bits'
|
24
|
+
end
|
25
|
+
offer['client_max_window_bits'] = @accept_max_window_bits
|
26
|
+
else
|
27
|
+
offer['client_max_window_bits'] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
if @request_no_context_takeover
|
31
|
+
offer['server_no_context_takeover'] = true
|
32
|
+
end
|
33
|
+
|
34
|
+
if @request_max_window_bits
|
35
|
+
unless VALID_WINDOW_BITS.include?(@request_max_window_bits)
|
36
|
+
raise ConfigurationError, 'Invalid value for request_max_window_bits'
|
37
|
+
end
|
38
|
+
offer['server_max_window_bits'] = @request_max_window_bits
|
39
|
+
end
|
40
|
+
|
41
|
+
offer
|
42
|
+
end
|
43
|
+
|
44
|
+
def activate(params)
|
45
|
+
return false unless ClientSession.valid_params?(params)
|
46
|
+
|
47
|
+
if @accept_max_window_bits and params['client_max_window_bits']
|
48
|
+
return false if params['client_max_window_bits'] > @accept_max_window_bits
|
49
|
+
end
|
50
|
+
|
51
|
+
if @request_no_context_takeover and !params['server_no_context_takeover']
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
if @request_max_window_bits
|
56
|
+
return false unless params['server_max_window_bits']
|
57
|
+
return false if params['server_max_window_bits'] > @request_max_window_bits
|
58
|
+
end
|
59
|
+
|
60
|
+
@own_context_takeover = !(@accept_no_context_takeover || params['client_no_context_takeover'])
|
61
|
+
@own_window_bits = [
|
62
|
+
@accept_max_window_bits || DEFAULT_MAX_WINDOW_BITS,
|
63
|
+
params['client_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
|
64
|
+
].min
|
65
|
+
|
66
|
+
@peer_context_takeover = !params['server_no_context_takeover']
|
67
|
+
@peer_window_bits = params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
|
68
|
+
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class PermessageDeflate
|
2
|
+
class ServerSession < Session
|
3
|
+
|
4
|
+
def self.valid_params?(params)
|
5
|
+
return false unless super
|
6
|
+
|
7
|
+
if params.has_key?('client_max_window_bits')
|
8
|
+
return false unless ([true] + VALID_WINDOW_BITS).include?(params['client_max_window_bits'])
|
9
|
+
end
|
10
|
+
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options, params)
|
15
|
+
super(options)
|
16
|
+
@params = params
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_response
|
20
|
+
params = {}
|
21
|
+
|
22
|
+
# https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.1
|
23
|
+
if @accept_no_context_takeover or @params['server_no_context_takeover']
|
24
|
+
params['server_no_context_takeover'] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
# https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.2
|
28
|
+
if @request_no_context_takeover or @params['client_no_context_takeover']
|
29
|
+
params['client_no_context_takeover'] = true
|
30
|
+
end
|
31
|
+
|
32
|
+
# https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.1
|
33
|
+
if @accept_max_window_bits or @params['server_max_window_bits']
|
34
|
+
accept_max = @accept_max_window_bits || DEFAULT_MAX_WINDOW_BITS
|
35
|
+
server_max = @params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
|
36
|
+
params['server_max_window_bits'] = [accept_max, server_max].min
|
37
|
+
end
|
38
|
+
|
39
|
+
# https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.2
|
40
|
+
if client_max = @params['client_max_window_bits']
|
41
|
+
if client_max == true
|
42
|
+
params['client_max_window_bits'] = @request_max_window_bits if @request_max_window_bits
|
43
|
+
else
|
44
|
+
request_max = @request_max_window_bits || DEFAULT_MAX_WINDOW_BITS
|
45
|
+
params['client_max_window_bits'] = [request_max, client_max].min
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
@own_context_takeover = !params['server_no_context_takeover']
|
50
|
+
@own_window_bits = params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
|
51
|
+
|
52
|
+
@peer_context_takeover = !params['client_no_context_takeover']
|
53
|
+
@peer_window_bits = params['client_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
|
54
|
+
|
55
|
+
params
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class PermessageDeflate
|
2
|
+
class Session
|
3
|
+
|
4
|
+
VALID_PARAMS = [
|
5
|
+
'server_no_context_takeover',
|
6
|
+
'client_no_context_takeover',
|
7
|
+
'server_max_window_bits',
|
8
|
+
'client_max_window_bits'
|
9
|
+
]
|
10
|
+
|
11
|
+
DEFAULT_MAX_WINDOW_BITS = 15
|
12
|
+
VALID_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]
|
13
|
+
|
14
|
+
def self.valid_params?(params)
|
15
|
+
return false unless params.keys.all? { |k| VALID_PARAMS.include?(k) }
|
16
|
+
return false if params.values.grep(Array).any?
|
17
|
+
|
18
|
+
if params.has_key?('server_no_context_takeover')
|
19
|
+
return false unless params['server_no_context_takeover'] == true
|
20
|
+
end
|
21
|
+
|
22
|
+
if params.has_key?('client_no_context_takeover')
|
23
|
+
return false unless params['client_no_context_takeover'] == true
|
24
|
+
end
|
25
|
+
|
26
|
+
if params.has_key?('server_max_window_bits')
|
27
|
+
return false unless VALID_WINDOW_BITS.include?(params['server_max_window_bits'])
|
28
|
+
end
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(options)
|
34
|
+
@level = options.fetch(:level, Zlib::DEFAULT_COMPRESSION)
|
35
|
+
@mem_level = options.fetch(:mem_level, Zlib::DEF_MEM_LEVEL)
|
36
|
+
@strategy = options.fetch(:strategy, Zlib::DEFAULT_STRATEGY)
|
37
|
+
|
38
|
+
@accept_no_context_takeover = options.fetch(:no_context_takeover, false)
|
39
|
+
@accept_max_window_bits = options.fetch(:max_window_bits, nil)
|
40
|
+
@request_no_context_takeover = options.fetch(:request_no_context_takeover, false)
|
41
|
+
@request_max_window_bits = options.fetch(:request_max_window_bits, nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_incoming_message(message)
|
45
|
+
return message unless message.rsv1
|
46
|
+
|
47
|
+
inflate = get_inflate
|
48
|
+
|
49
|
+
message.data = inflate.inflate(message.data) +
|
50
|
+
inflate.inflate([0x00, 0x00, 0xff, 0xff].pack('C*'))
|
51
|
+
|
52
|
+
free(inflate) unless @inflate
|
53
|
+
message
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_outgoing_message(message)
|
57
|
+
deflate = get_deflate
|
58
|
+
|
59
|
+
message.data = deflate.deflate(message.data, Zlib::SYNC_FLUSH)[0...-4]
|
60
|
+
message.rsv1 = true
|
61
|
+
|
62
|
+
free(deflate) unless @deflate
|
63
|
+
message
|
64
|
+
end
|
65
|
+
|
66
|
+
def close
|
67
|
+
free(@inflate)
|
68
|
+
@inflate = nil
|
69
|
+
|
70
|
+
free(@deflate)
|
71
|
+
@deflate = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def free(codec)
|
77
|
+
return if codec.nil?
|
78
|
+
codec.finish rescue nil
|
79
|
+
codec.close
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_inflate
|
83
|
+
return @inflate if @inflate
|
84
|
+
inflate = Zlib::Inflate.new(-@peer_window_bits)
|
85
|
+
@inflate = inflate if @peer_context_takeover
|
86
|
+
inflate
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_deflate
|
90
|
+
return @deflate if @deflate
|
91
|
+
deflate = Zlib::Deflate.new(@level, -@own_window_bits, @mem_level, @strategy)
|
92
|
+
@deflate = deflate if @own_context_takeover
|
93
|
+
deflate
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: permessage_deflate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Coglan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email: jcoglan@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.md
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- lib/permessage_deflate.rb
|
36
|
+
- lib/permessage_deflate/client_session.rb
|
37
|
+
- lib/permessage_deflate/server_session.rb
|
38
|
+
- lib/permessage_deflate/session.rb
|
39
|
+
homepage: http://github.com/faye/permessage-deflate-ruby
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- "--main"
|
46
|
+
- README.md
|
47
|
+
- "--markup"
|
48
|
+
- markdown
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.2.2
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Per-message DEFLATE compression extension for WebSocket connections
|
67
|
+
test_files: []
|