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.
@@ -0,0 +1,65 @@
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
+ # Wrapping body instance. Typically you'd override `#read`.
27
+ class Wrapper < Readable
28
+ def initialize(body)
29
+ @body = body
30
+ end
31
+
32
+ # The wrapped body.
33
+ attr :body
34
+
35
+ # Buffer any remaining body.
36
+ def finish
37
+ @body.finish
38
+ end
39
+
40
+ def close(error = nil)
41
+ @body.close(error)
42
+
43
+ super
44
+ end
45
+
46
+ def empty?
47
+ @body.empty?
48
+ end
49
+
50
+ def length
51
+ @body.length
52
+ end
53
+
54
+ # Read the next available chunk.
55
+ def read
56
+ @body.read
57
+ end
58
+
59
+ def inspect
60
+ @body.inspect
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,76 @@
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/deflate'
25
+
26
+ module Protocol
27
+ module HTTP
28
+ # Encode a response according the the request's acceptable encodings.
29
+ class ContentEncoding < Middleware
30
+ DEFAULT_WRAPPERS = {
31
+ 'gzip' => Body::Deflate.method(:for)
32
+ }
33
+
34
+ DEFAULT_CONTENT_TYPES = %r{^(text/.*?)|(.*?/json)|(.*?/javascript)$}
35
+
36
+ def initialize(app, content_types = DEFAULT_CONTENT_TYPES, wrappers = DEFAULT_WRAPPERS)
37
+ super(app)
38
+
39
+ @content_types = content_types
40
+ @wrappers = wrappers
41
+ end
42
+
43
+ def call(request)
44
+ response = super
45
+
46
+ # Early exit if the response has already specified a content-encoding.
47
+ return response if response.headers['content-encoding']
48
+
49
+ # This is a very tricky issue, so we avoid it entirely.
50
+ # https://lists.w3.org/Archives/Public/ietf-http-wg/2014JanMar/1179.html
51
+ return response if response.partial?
52
+
53
+ # TODO use http-accept and sort by priority
54
+ if !response.body.empty? and accept_encoding = request.headers['accept-encoding']
55
+ if content_type = response.headers['content-type'] and @content_types =~ content_type
56
+ body = response.body
57
+
58
+ accept_encoding.each do |name|
59
+ if wrapper = @wrappers[name]
60
+ response.headers['content-encoding'] = name
61
+
62
+ body = wrapper.call(body)
63
+
64
+ break
65
+ end
66
+ end
67
+
68
+ response.body = body
69
+ end
70
+ end
71
+
72
+ return response
73
+ end
74
+ end
75
+ end
76
+ end
@@ -23,7 +23,7 @@
23
23
  module Protocol
24
24
  module HTTP
25
25
  # HTTP method verbs
26
- module Methods
26
+ class Methods
27
27
  GET = 'GET'
28
28
  POST = 'POST'
29
29
  PUT = 'PUT'
@@ -34,8 +34,22 @@ module Protocol
34
34
  LINK = 'LINK'
35
35
  UNLINK = 'UNLINK'
36
36
  TRACE = 'TRACE'
37
+ CONNECT = 'CONNECT'
38
+
39
+ def self.each
40
+ constants.each do |name|
41
+ yield name, const_get(name)
42
+ end
43
+ end
37
44
 
38
45
  # Use Methods.constants to get all constants.
46
+ self.each do |name, verb|
47
+ define_method(verb.downcase) do |location, headers = [], body = nil|
48
+ self.call(
49
+ Request[verb, location.to_str, Headers[headers], body]
50
+ )
51
+ end
52
+ end
39
53
  end
40
54
  end
41
55
  end
@@ -0,0 +1,62 @@
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 'methods'
22
+ require_relative 'headers'
23
+ require_relative 'request'
24
+ require_relative 'response'
25
+
26
+ module Protocol
27
+ module HTTP
28
+ class Middleware < Methods
29
+ def initialize(delegate)
30
+ @delegate = delegate
31
+ end
32
+
33
+ attr :delegate
34
+
35
+ def close
36
+ @delegate.close
37
+ end
38
+
39
+ def call(request)
40
+ @delegate.call(request)
41
+ end
42
+
43
+ module Okay
44
+ def self.close
45
+ end
46
+
47
+ def self.call(request)
48
+ Response[200, {}, []]
49
+ end
50
+ end
51
+
52
+ module HelloWorld
53
+ def self.close
54
+ end
55
+
56
+ def self.call(request)
57
+ Response[200, Headers['content-type' => 'text/plain'], ["Hello World!"]]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
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 '../headers'
22
+
23
+ module Protocol
24
+ module HTTP
25
+ class Middleware
26
+ module NotFound
27
+ def self.close
28
+ end
29
+
30
+ def self.call(request)
31
+ Response[404, Headers[], []]
32
+ end
33
+ end
34
+
35
+ class Builder
36
+ def initialize(default_app = NotFound, &block)
37
+ @use = []
38
+ @app = default_app
39
+
40
+ instance_eval(&block) if block_given?
41
+ end
42
+
43
+ def use(middleware, *args, &block)
44
+ @use << proc {|app| middleware.new(app, *args, &block)}
45
+ end
46
+
47
+ def run(app)
48
+ @app = app
49
+ end
50
+
51
+ def to_app
52
+ @use.reverse.inject(@app) {|app, use| use.call(app).freeze}
53
+ end
54
+ end
55
+
56
+ def self.build(&block)
57
+ Builder.new(&block).to_app
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,77 @@
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 'body/buffered'
22
+ require_relative 'body/reader'
23
+
24
+ module Protocol
25
+ module HTTP
26
+ class Request
27
+ prepend Body::Reader
28
+
29
+ def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = [], body = nil, protocol = nil)
30
+ @scheme = scheme
31
+ @authority = authority
32
+ @method = method
33
+ @path = path
34
+ @version = version
35
+ @headers = headers
36
+ @body = body
37
+ @protocol = protocol
38
+ end
39
+
40
+ attr_accessor :scheme
41
+ attr_accessor :authority
42
+ attr_accessor :method
43
+ attr_accessor :path
44
+ attr_accessor :version
45
+ attr_accessor :headers
46
+ attr_accessor :body
47
+ attr_accessor :protocol
48
+
49
+ # Send the request to the given connection.
50
+ def call(connection)
51
+ connection.call(self)
52
+ end
53
+
54
+ def head?
55
+ self.method == Methods::HEAD
56
+ end
57
+
58
+ def connect?
59
+ self.method == Methods::CONNECT
60
+ end
61
+
62
+ def self.[](method, path, headers, body)
63
+ body = Body::Buffered.wrap(body)
64
+
65
+ self.new(nil, nil, method, path, nil, headers, body)
66
+ end
67
+
68
+ def idempotent?
69
+ method != Methods::POST && (body.nil? || body.empty?)
70
+ end
71
+
72
+ def to_s
73
+ "#{@scheme}://#{@authority}: #{@method} #{@path} #{@version}"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,99 @@
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 'body/buffered'
22
+ require_relative 'body/reader'
23
+
24
+ module Protocol
25
+ module HTTP
26
+ class Response
27
+ prepend Body::Reader
28
+
29
+ def initialize(version = nil, status = 200, reason = nil, headers = [], body = nil, protocol = nil)
30
+ @version = version
31
+ @status = status
32
+ @reason = reason
33
+ @headers = headers
34
+ @body = body
35
+ @protocol = protocol
36
+ end
37
+
38
+ attr_accessor :version
39
+ attr_accessor :status
40
+ attr_accessor :headers
41
+ attr_accessor :body
42
+ attr_accessor :protocol
43
+
44
+ def hijack?
45
+ false
46
+ end
47
+
48
+ def continue?
49
+ status == 100
50
+ end
51
+
52
+ def success?
53
+ status >= 200 && status < 300
54
+ end
55
+
56
+ def partial?
57
+ status == 206
58
+ end
59
+
60
+ def redirection?
61
+ status >= 300 && status < 400
62
+ end
63
+
64
+ def preserve_method?
65
+ status == 307 || status == 308
66
+ end
67
+
68
+ def failure?
69
+ status >= 400 && status < 600
70
+ end
71
+
72
+ def bad_request?
73
+ status == 400
74
+ end
75
+
76
+ def server_failure?
77
+ status == 500
78
+ end
79
+
80
+ def self.[](status, headers = [], body = nil, protocol = nil)
81
+ body = Body::Buffered.wrap(body)
82
+
83
+ self.new(nil, status, nil, headers, body, protocol)
84
+ end
85
+
86
+ def self.for_exception(exception)
87
+ Response[500, Headers['content-type' => 'text/plain'], ["#{exception.class}: #{exception.message}"]]
88
+ end
89
+
90
+ def to_s
91
+ "#{@status} #{@reason} #{@version}"
92
+ end
93
+
94
+ def to_ary
95
+ return @status, @headers, @body
96
+ end
97
+ end
98
+ end
99
+ end