protocol-http 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ffe5c98e49d6feb6ab81b7437ab5acbca2e3140b6d103844d4c99ad640e1186
4
- data.tar.gz: 951a549c2c522305e43e4f29b48709433ab07499477dab5938f76b9cd58a1e1b
3
+ metadata.gz: 6ac5dc1c7b3c8e0c0e6db720d7c3e6c5d716ee4d954702f99f94457c90cfec3a
4
+ data.tar.gz: da09daf131c30241e6b9c0518ecdb1b7dcea3ddcdf0402235a7ff7ad006b83fc
5
5
  SHA512:
6
- metadata.gz: 6a08508803e99eecf84d241dd840f295b825e66affe5e0424b58858907c06d4bfffd9a1f26dbea393d681a8438d7207e1e6d801a209e1a8da6909f1f248cec26
7
- data.tar.gz: 7269a6f639035c74b993c2135152681fbd3fd45e81b102015bab5ef45117f8bd3f4058c22dd6e4407c82639a6529463ed1dfb51e927caeada72630c1a96b9ef8
6
+ metadata.gz: d8595b25b59ddc3262447ae1489b2af0bb1e54a809c33c1fe9ad2f7490998a2f4aa73d48290109e2bccf00dee4b2dd77d6e1e9d4d10e1c1140b1960a4933511e
7
+ data.tar.gz: 7160f346668a6428c021824f435359c928060c0eb8541441efc1ccd034a6a46c67d85243a4ddcf1d71b9fbd426d484a5f1ce1d4d9013c3e8a2bf9c4340f2f13a
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  Gemfile.lock
13
+ .covered.db
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in protocol-http.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'async-io'
8
+ gem 'async-rspec'
9
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'middleware'
22
+
23
+ require_relative 'body/buffered'
24
+ require_relative 'body/inflate'
25
+
26
+ module Protocol
27
+ module HTTP
28
+ # Set a valid accept-encoding header and decode the response.
29
+ class AcceptEncoding < Middleware
30
+ ACCEPT_ENCODING = 'accept-encoding'.freeze
31
+ CONTENT_ENCODING = 'content-encoding'.freeze
32
+
33
+ DEFAULT_WRAPPERS = {
34
+ 'gzip' => Body::Inflate.method(:for),
35
+ 'identity' => ->(body){body},
36
+ }
37
+
38
+ def initialize(app, wrappers = DEFAULT_WRAPPERS)
39
+ super(app)
40
+
41
+ @accept_encoding = wrappers.keys.join(', ')
42
+ @wrappers = wrappers
43
+ end
44
+
45
+ def call(request)
46
+ request.headers[ACCEPT_ENCODING] = @accept_encoding
47
+
48
+ response = super
49
+
50
+ if body = response.body and !body.empty? and content_encoding = response.headers.delete(CONTENT_ENCODING)
51
+ # We want to unwrap all encodings
52
+ content_encoding.reverse_each do |name|
53
+ if wrapper = @wrappers[name]
54
+ body = wrapper.call(body)
55
+ end
56
+ end
57
+
58
+ response.body = body
59
+ end
60
+
61
+ return response
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'readable'
22
+
23
+ module Protocol
24
+ module HTTP
25
+ module Body
26
+ # A body which buffers all it's contents.
27
+ class Buffered < Readable
28
+ # Wraps an array into a buffered body.
29
+ # @return [Readable, nil] the wrapped body or nil if nil was given.
30
+ def self.wrap(body)
31
+ if body.is_a? Readable
32
+ return body
33
+ elsif body.is_a? Array
34
+ return self.new(body)
35
+ elsif body.is_a?(String)
36
+ return self.new([body])
37
+ elsif body
38
+ return self.for(body)
39
+ end
40
+ end
41
+
42
+ def self.for(body)
43
+ chunks = []
44
+
45
+ body.each do |chunk|
46
+ chunks << chunk
47
+ end
48
+
49
+ self.new(chunks)
50
+ end
51
+
52
+ def initialize(chunks = [], length = nil)
53
+ @chunks = chunks
54
+ @length = length
55
+
56
+ @index = 0
57
+ end
58
+
59
+ def finish
60
+ self
61
+ end
62
+
63
+ def length
64
+ @length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize}
65
+ end
66
+
67
+ def empty?
68
+ @index >= @chunks.length
69
+ end
70
+
71
+ def read
72
+ if chunk = @chunks[@index]
73
+ @index += 1
74
+
75
+ return chunk.dup
76
+ end
77
+ end
78
+
79
+ def write(chunk)
80
+ @chunks << chunk
81
+ end
82
+
83
+ def close(error = nil)
84
+ @chunks << nil
85
+ end
86
+
87
+ def rewind
88
+ @index = 0
89
+ end
90
+
91
+ def inspect
92
+ "\#<#{self.class} #{@chunks.count} chunks, #{self.length} bytes>"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,113 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'wrapper'
22
+
23
+ require 'zlib'
24
+
25
+ module Protocol
26
+ module HTTP
27
+ module Body
28
+ class ZStream < Wrapper
29
+ DEFAULT_LEVEL = 7
30
+
31
+ DEFLATE = -Zlib::MAX_WBITS
32
+ GZIP = Zlib::MAX_WBITS | 16
33
+
34
+ ENCODINGS = {
35
+ 'deflate' => DEFLATE,
36
+ 'gzip' => GZIP,
37
+ }
38
+
39
+ def self.encoding_name(window_size)
40
+ if window_size <= -8
41
+ return 'deflate'
42
+ elsif window_size >= 16
43
+ return 'gzip'
44
+ else
45
+ return 'compress'
46
+ end
47
+ end
48
+
49
+ def initialize(body, stream)
50
+ super(body)
51
+
52
+ @stream = stream
53
+
54
+ @input_length = 0
55
+ @output_length = 0
56
+ end
57
+
58
+ def close(error = nil)
59
+ @stream.close unless @stream.closed?
60
+
61
+ super
62
+ end
63
+
64
+ def length
65
+ # We don't know the length of the output until after it's been compressed.
66
+ nil
67
+ end
68
+
69
+ attr :input_length
70
+ attr :output_length
71
+
72
+ def ratio
73
+ if @input_length != 0
74
+ @output_length.to_f / @input_length.to_f
75
+ else
76
+ 1.0
77
+ end
78
+ end
79
+
80
+ def inspect
81
+ "#{super} | \#<#{self.class} #{(ratio*100).round(2)}%>"
82
+ end
83
+ end
84
+
85
+ class Deflate < ZStream
86
+ def self.for(body, window_size = GZIP, level = DEFAULT_LEVEL)
87
+ self.new(body, Zlib::Deflate.new(level, window_size))
88
+ end
89
+
90
+ def read
91
+ return if @stream.finished?
92
+
93
+ # The stream might have been closed while waiting for the chunk to come in.
94
+ if chunk = super
95
+ @input_length += chunk.bytesize
96
+
97
+ chunk = @stream.deflate(chunk, Zlib::SYNC_FLUSH)
98
+
99
+ @output_length += chunk.bytesize
100
+
101
+ return chunk
102
+ elsif !@stream.closed?
103
+ chunk = @stream.finish
104
+
105
+ @output_length += chunk.bytesize
106
+
107
+ return chunk.empty? ? nil : chunk
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,98 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'readable'
22
+ require 'async/io/stream'
23
+
24
+ module Protocol
25
+ module HTTP
26
+ module Body
27
+ class File < Readable
28
+ BLOCK_SIZE = Async::IO::Stream::BLOCK_SIZE
29
+ MODE = ::File::RDONLY | ::File::BINARY
30
+
31
+ def self.open(path, *args)
32
+ self.new(::File.open(path, MODE), *args)
33
+ end
34
+
35
+ def initialize(file, range = nil, block_size: BLOCK_SIZE)
36
+ @file = file
37
+
38
+ @block_size = block_size
39
+
40
+ if range
41
+ @file.seek(range.min)
42
+ @offset = range.min
43
+ @length = @remaining = range.size
44
+ else
45
+ @offset = 0
46
+ @length = @remaining = @file.size
47
+ end
48
+ end
49
+
50
+ def close(error = nil)
51
+ @file.close
52
+ @remaining = 0
53
+
54
+ super
55
+ end
56
+
57
+ attr :file
58
+
59
+ attr :offset
60
+ attr :length
61
+
62
+ def empty?
63
+ @remaining == 0
64
+ end
65
+
66
+ def rewind
67
+ @file.seek(@offset)
68
+ end
69
+
70
+ def read
71
+ if @remaining > 0
72
+ amount = [@remaining, @block_size].min
73
+
74
+ if chunk = @file.read(amount)
75
+ @remaining -= chunk.bytesize
76
+
77
+ return chunk
78
+ end
79
+ end
80
+ end
81
+
82
+ def join
83
+ return "" if @remaining == 0
84
+
85
+ buffer = @file.read(@remaining)
86
+
87
+ @remaining = 0
88
+
89
+ return buffer
90
+ end
91
+
92
+ def inspect
93
+ "\#<#{self.class} file=#{@file.inspect} offset=#{@offset} remaining=#{@remaining}>"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'zlib'
22
+
23
+ require_relative 'deflate'
24
+
25
+ module Protocol
26
+ module HTTP
27
+ module Body
28
+ class Inflate < ZStream
29
+ def self.for(body, encoding = GZIP)
30
+ self.new(body, Zlib::Inflate.new(encoding))
31
+ end
32
+
33
+ def read
34
+ return if @stream.finished?
35
+
36
+ # The stream might have been closed while waiting for the chunk to come in.
37
+ if chunk = super
38
+ @input_length += chunk.bytesize
39
+
40
+ # It's possible this triggers the stream to finish.
41
+ chunk = @stream.inflate(chunk)
42
+
43
+ @output_length += chunk.bytesize
44
+ elsif !@stream.closed?
45
+ chunk = @stream.finish
46
+
47
+ @output_length += chunk.bytesize
48
+ end
49
+
50
+ if chunk.empty? and @stream.finished?
51
+ return nil
52
+ end
53
+
54
+ return chunk
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end