permessage_deflate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build status](https://secure.travis-ci.org/faye/permessage-deflate-ruby.svg)](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: []
|