http 0.5.1 → 0.6.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of http might be problematic. Click here for more details.

Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -3
  3. data/.rspec +3 -2
  4. data/.rubocop.yml +101 -0
  5. data/.travis.yml +19 -8
  6. data/Gemfile +24 -6
  7. data/LICENSE.txt +1 -1
  8. data/README.md +144 -29
  9. data/Rakefile +23 -1
  10. data/examples/parallel_requests_with_celluloid.rb +2 -2
  11. data/http.gemspec +14 -14
  12. data/lib/http.rb +5 -4
  13. data/lib/http/authorization_header.rb +37 -0
  14. data/lib/http/authorization_header/basic_auth.rb +24 -0
  15. data/lib/http/authorization_header/bearer_token.rb +29 -0
  16. data/lib/http/backports.rb +2 -0
  17. data/lib/http/backports/base64.rb +6 -0
  18. data/lib/http/{uri_backport.rb → backports/uri.rb} +10 -10
  19. data/lib/http/chainable.rb +24 -25
  20. data/lib/http/client.rb +97 -67
  21. data/lib/http/content_type.rb +27 -0
  22. data/lib/http/errors.rb +13 -0
  23. data/lib/http/headers.rb +154 -0
  24. data/lib/http/headers/mixin.rb +11 -0
  25. data/lib/http/mime_type.rb +61 -36
  26. data/lib/http/mime_type/adapter.rb +24 -0
  27. data/lib/http/mime_type/json.rb +23 -0
  28. data/lib/http/options.rb +21 -48
  29. data/lib/http/redirector.rb +12 -7
  30. data/lib/http/request.rb +82 -33
  31. data/lib/http/request/writer.rb +79 -0
  32. data/lib/http/response.rb +39 -68
  33. data/lib/http/response/body.rb +62 -0
  34. data/lib/http/{response_parser.rb → response/parser.rb} +3 -1
  35. data/lib/http/version.rb +1 -1
  36. data/logo.png +0 -0
  37. data/spec/http/authorization_header/basic_auth_spec.rb +29 -0
  38. data/spec/http/authorization_header/bearer_token_spec.rb +36 -0
  39. data/spec/http/authorization_header_spec.rb +41 -0
  40. data/spec/http/backports/base64_spec.rb +13 -0
  41. data/spec/http/client_spec.rb +181 -0
  42. data/spec/http/content_type_spec.rb +47 -0
  43. data/spec/http/headers/mixin_spec.rb +36 -0
  44. data/spec/http/headers_spec.rb +417 -0
  45. data/spec/http/options/body_spec.rb +6 -7
  46. data/spec/http/options/form_spec.rb +4 -5
  47. data/spec/http/options/headers_spec.rb +9 -17
  48. data/spec/http/options/json_spec.rb +17 -0
  49. data/spec/http/options/merge_spec.rb +18 -19
  50. data/spec/http/options/new_spec.rb +5 -19
  51. data/spec/http/options/proxy_spec.rb +6 -6
  52. data/spec/http/options_spec.rb +3 -9
  53. data/spec/http/redirector_spec.rb +100 -0
  54. data/spec/http/request/writer_spec.rb +25 -0
  55. data/spec/http/request_spec.rb +54 -14
  56. data/spec/http/response/body_spec.rb +24 -0
  57. data/spec/http/response_spec.rb +61 -32
  58. data/spec/http_spec.rb +77 -86
  59. data/spec/spec_helper.rb +25 -2
  60. data/spec/support/example_server.rb +58 -49
  61. data/spec/support/proxy_server.rb +27 -11
  62. metadata +60 -55
  63. data/lib/http/header.rb +0 -11
  64. data/lib/http/mime_types/json.rb +0 -19
  65. data/lib/http/request_stream.rb +0 -77
  66. data/spec/http/options/callbacks_spec.rb +0 -62
  67. data/spec/http/options/response_spec.rb +0 -24
  68. data/spec/http/request_stream_spec.rb +0 -25
@@ -1,18 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HTTP::Options, "body" do
3
+ describe HTTP::Options, 'body' do
4
4
 
5
- let(:opts){ HTTP::Options.new }
5
+ let(:opts) { HTTP::Options.new }
6
6
 
7
7
  it 'defaults to nil' do
8
- expect(opts.body).to be_nil
8
+ expect(opts.body).to be nil
9
9
  end
10
10
 
11
11
  it 'may be specified with with_body' do
12
- opts2 = opts.with_body("foo")
13
- expect(opts.body).to be_nil
14
- expect(opts2.body).to eq("foo")
12
+ opts2 = opts.with_body('foo')
13
+ expect(opts.body).to be nil
14
+ expect(opts2.body).to eq('foo')
15
15
  end
16
16
 
17
17
  end
18
-
@@ -1,18 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HTTP::Options, "form" do
3
+ describe HTTP::Options, 'form' do
4
4
 
5
- let(:opts){ HTTP::Options.new }
5
+ let(:opts) { HTTP::Options.new }
6
6
 
7
7
  it 'defaults to nil' do
8
- expect(opts.form).to be_nil
8
+ expect(opts.form).to be nil
9
9
  end
10
10
 
11
11
  it 'may be specified with with_form_data' do
12
12
  opts2 = opts.with_form(:foo => 42)
13
- expect(opts.form).to be_nil
13
+ expect(opts.form).to be nil
14
14
  expect(opts2.form).to eq(:foo => 42)
15
15
  end
16
16
 
17
17
  end
18
-
@@ -1,30 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HTTP::Options, "headers" do
3
+ describe HTTP::Options, 'headers' do
4
4
 
5
- let(:opts) { HTTP::Options.new }
6
- let(:user_agent) { "RubyHTTPGem/#{HTTP::VERSION}" }
5
+ let(:opts) { HTTP::Options.new }
7
6
 
8
- it 'defaults to just the user agent' do
9
- expect(opts.headers).to eq("User-Agent" => user_agent)
7
+ it 'defaults to be empty' do
8
+ expect(opts.headers).to be_empty
10
9
  end
11
10
 
12
11
  it 'may be specified with with_headers' do
13
- opts2 = opts.with_headers("accept" => "json")
14
- expect(opts.headers).to eq("User-Agent" => user_agent)
15
- expect(opts2.headers).to eq("accept" => "json", "User-Agent" => user_agent)
12
+ opts2 = opts.with_headers('accept' => 'json')
13
+ expect(opts.headers).to be_empty
14
+ expect(opts2.headers).to eq([%w[Accept json]])
16
15
  end
17
16
 
18
17
  it 'accepts any object that respond to :to_hash' do
19
- x = Struct.new(:to_hash).new("accept" => "json")
20
- expect(opts.with_headers(x).headers["accept"]).to eq("json")
21
- end
22
-
23
- it 'recognizes invalid headers' do
24
- expect {
25
- opts.with_headers(self)
26
- }.to raise_error(ArgumentError)
18
+ x = Struct.new(:to_hash).new('accept' => 'json')
19
+ expect(opts.with_headers(x).headers['accept']).to eq('json')
27
20
  end
28
21
 
29
22
  end
30
-
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe HTTP::Options, 'json' do
4
+
5
+ let(:opts) { HTTP::Options.new }
6
+
7
+ it 'defaults to nil' do
8
+ expect(opts.json).to be nil
9
+ end
10
+
11
+ it 'may be specified with with_json data' do
12
+ opts2 = opts.with_json(:foo => 42)
13
+ expect(opts.json).to be nil
14
+ expect(opts2.json).to eq(:foo => 42)
15
+ end
16
+
17
+ end
@@ -1,9 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HTTP::Options, "merge" do
4
-
5
- let(:opts) { HTTP::Options.new }
6
- let(:user_agent) { "RubyHTTPGem/#{HTTP::VERSION}" }
3
+ describe HTTP::Options, 'merge' do
4
+ let(:opts) { HTTP::Options.new }
7
5
 
8
6
  it 'supports a Hash' do
9
7
  old_response = opts.response
@@ -23,30 +21,31 @@ describe HTTP::Options, "merge" do
23
21
  :response => :body,
24
22
  :params => {:baz => 'bar'},
25
23
  :form => {:foo => 'foo'},
26
- :body => "body-foo",
27
- :headers => {:accept => "json", :foo => 'foo'},
28
- :proxy => {},
29
- :callbacks => {:request => ["common"], :response => ["foo"]})
24
+ :body => 'body-foo',
25
+ :json => {:foo => 'foo'},
26
+ :headers => {:accept => 'json', :foo => 'foo'},
27
+ :proxy => {})
28
+
30
29
  bar = HTTP::Options.new(
31
30
  :response => :parsed_body,
32
31
  :params => {:plop => 'plip'},
33
32
  :form => {:bar => 'bar'},
34
- :body => "body-bar",
35
- :headers => {:accept => "xml", :bar => 'bar'},
36
- :proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080},
37
- :callbacks => {:request => ["common"], :response => ["bar"]})
33
+ :body => 'body-bar',
34
+ :json => {:bar => 'bar'},
35
+ :headers => {:accept => 'xml', :bar => 'bar'},
36
+ :proxy => {:proxy_address => '127.0.0.1', :proxy_port => 8080})
37
+
38
38
  expect(foo.merge(bar).to_hash).to eq(
39
39
  :response => :parsed_body,
40
- :params=>{:plop=>"plip"},
40
+ :params => {:plop => 'plip'},
41
41
  :form => {:bar => 'bar'},
42
- :body => "body-bar",
43
- :headers => {:accept => "xml", :foo => "foo", :bar => 'bar', "User-Agent" => user_agent},
44
- :proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080},
45
- :callbacks => {:request => ["common"], :response => ["foo", "bar"]},
42
+ :body => 'body-bar',
43
+ :json => {:bar => 'bar'},
44
+ :headers => {'Accept' => 'xml', 'Foo' => 'foo', 'Bar' => 'bar'},
45
+ :proxy => {:proxy_address => '127.0.0.1', :proxy_port => 8080},
46
46
  :follow => nil,
47
47
  :socket_class => described_class.default_socket_class,
48
48
  :ssl_socket_class => described_class.default_ssl_socket_class,
49
- :ssl_context => nil
50
- )
49
+ :ssl_context => nil)
51
50
  end
52
51
  end
@@ -1,8 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HTTP::Options, "new" do
4
- let(:user_agent) { "RubyHTTPGem/#{HTTP::VERSION}" }
5
-
3
+ describe HTTP::Options, 'new' do
6
4
  it 'supports a Options instance' do
7
5
  opts = HTTP::Options.new
8
6
  expect(HTTP::Options.new(opts)).to eq(opts)
@@ -15,30 +13,18 @@ describe HTTP::Options, "new" do
15
13
  end
16
14
 
17
15
  it 'coerces :headers correctly' do
18
- opts = HTTP::Options.new(:headers => {:accept => "json"})
19
- expect(opts.headers).to eq(:accept => "json", "User-Agent" => user_agent)
16
+ opts = HTTP::Options.new(:headers => {:accept => 'json'})
17
+ expect(opts.headers).to eq([%w[Accept json]])
20
18
  end
21
19
 
22
20
  it 'coerces :proxy correctly' do
23
- opts = HTTP::Options.new(:proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080})
24
- expect(opts.proxy).to eq(:proxy_address => "127.0.0.1", :proxy_port => 8080)
21
+ opts = HTTP::Options.new(:proxy => {:proxy_address => '127.0.0.1', :proxy_port => 8080})
22
+ expect(opts.proxy).to eq(:proxy_address => '127.0.0.1', :proxy_port => 8080)
25
23
  end
26
24
 
27
25
  it 'coerces :form correctly' do
28
26
  opts = HTTP::Options.new(:form => {:foo => 42})
29
27
  expect(opts.form).to eq(:foo => 42)
30
28
  end
31
-
32
- it 'coerces :callbacks correctly' do
33
- before, after = Proc.new{|r| :before}, Proc.new{|r| :after}
34
- callbacks = {:request => [before], :response => [after]}
35
- opts = HTTP::Options.new(:callbacks => callbacks)
36
- expect(opts.callbacks).to eq({
37
- :request => [before],
38
- :response => [after]
39
- })
40
- end
41
-
42
29
  end
43
-
44
30
  end
@@ -1,21 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HTTP::Options, "proxy" do
3
+ describe HTTP::Options, 'proxy' do
4
4
 
5
- let(:opts){ HTTP::Options.new }
5
+ let(:opts) { HTTP::Options.new }
6
6
 
7
7
  it 'defaults to {}' do
8
8
  expect(opts.proxy).to eq({})
9
9
  end
10
10
 
11
11
  it 'may be specified with with_proxy' do
12
- opts2 = opts.with_proxy(:proxy_address => "127.0.0.1", :proxy_port => 8080)
12
+ opts2 = opts.with_proxy(:proxy_address => '127.0.0.1', :proxy_port => 8080)
13
13
  expect(opts.proxy).to eq({})
14
- expect(opts2.proxy).to eq(:proxy_address => "127.0.0.1", :proxy_port => 8080)
14
+ expect(opts2.proxy).to eq(:proxy_address => '127.0.0.1', :proxy_port => 8080)
15
15
  end
16
16
 
17
17
  it 'accepts proxy address, port, username, and password' do
18
- opts2 = opts.with_proxy(:proxy_address => "127.0.0.1", :proxy_port => 8080, :proxy_username => "username", :proxy_password => "password")
19
- expect(opts2.proxy).to eq(:proxy_address => "127.0.0.1", :proxy_port => 8080, :proxy_username => "username", :proxy_password => "password")
18
+ opts2 = opts.with_proxy(:proxy_address => '127.0.0.1', :proxy_port => 8080, :proxy_username => 'username', :proxy_password => 'password')
19
+ expect(opts2.proxy).to eq(:proxy_address => '127.0.0.1', :proxy_port => 8080, :proxy_username => 'username', :proxy_password => 'password')
20
20
  end
21
21
  end
@@ -3,18 +3,12 @@ require 'spec_helper'
3
3
  describe HTTP::Options do
4
4
  subject { described_class.new(:response => :body) }
5
5
 
6
- it "behaves like a Hash for reading" do
6
+ it 'behaves like a Hash for reading' do
7
7
  expect(subject[:response]).to eq(:body)
8
- expect(subject[:nosuchone]).to be_nil
8
+ expect(subject[:nosuchone]).to be nil
9
9
  end
10
10
 
11
- it "it's gois able to coerce to a Hash" do
11
+ it 'coerces to a Hash' do
12
12
  expect(subject.to_hash).to be_a(Hash)
13
- expect(subject.to_hash[:response]).to eq(:body)
14
13
  end
15
-
16
- it "raises ArgumentError with invalid options" do
17
- expect { subject.with_response(:notrecognized) }.to raise_exception(ArgumentError)
18
- end
19
-
20
14
  end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe HTTP::Redirector do
4
+ def simple_response(status, body = '', headers = {})
5
+ HTTP::Response.new(status, '1.1', headers, body)
6
+ end
7
+
8
+ def redirect_response(location, status)
9
+ simple_response status, '', 'Location' => location
10
+ end
11
+
12
+ let(:max_hops) { 5 }
13
+ subject(:redirector) { described_class.new max_hops }
14
+
15
+ context 'following 300 redirect' do
16
+ let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' }
17
+ let(:orig_response) { redirect_response 'http://example.com/', 300 }
18
+
19
+ it 'follows without changing verb' do
20
+ redirector.perform(orig_request, orig_response) do |request|
21
+ expect(request.verb).to be orig_request.verb
22
+ simple_response 200
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'following 301 redirect' do
28
+ let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' }
29
+ let(:orig_response) { redirect_response 'http://example.com/', 301 }
30
+
31
+ it 'follows without changing verb' do
32
+ redirector.perform(orig_request, orig_response) do |request|
33
+ expect(request.verb).to be orig_request.verb
34
+ simple_response 200
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'following 302 redirect' do
40
+ let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' }
41
+ let(:orig_response) { redirect_response 'http://example.com/', 302 }
42
+
43
+ it 'follows without changing verb' do
44
+ redirector.perform(orig_request, orig_response) do |request|
45
+ expect(request.verb).to be orig_request.verb
46
+ simple_response 200
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'following 303 redirect' do
52
+ context 'upon POST request' do
53
+ let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' }
54
+ let(:orig_response) { redirect_response 'http://example.com/', 303 }
55
+
56
+ it 'follows without changing verb' do
57
+ redirector.perform(orig_request, orig_response) do |request|
58
+ expect(request.verb).to be :get
59
+ simple_response 200
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'upon HEAD request' do
65
+ let(:orig_request) { HTTP::Request.new :head, 'http://www.example.com/' }
66
+ let(:orig_response) { redirect_response 'http://example.com/', 303 }
67
+
68
+ it 'follows without changing verb' do
69
+ redirector.perform(orig_request, orig_response) do |request|
70
+ expect(request.verb).to be :get
71
+ simple_response 200
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'following 307 redirect' do
78
+ let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' }
79
+ let(:orig_response) { redirect_response 'http://example.com/', 307 }
80
+
81
+ it 'follows without changing verb' do
82
+ redirector.perform(orig_request, orig_response) do |request|
83
+ expect(request.verb).to be orig_request.verb
84
+ simple_response 200
85
+ end
86
+ end
87
+ end
88
+
89
+ context 'following 308 redirect' do
90
+ let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' }
91
+ let(:orig_response) { redirect_response 'http://example.com/', 308 }
92
+
93
+ it 'follows without changing verb' do
94
+ redirector.perform(orig_request, orig_response) do |request|
95
+ expect(request.verb).to be orig_request.verb
96
+ simple_response 200
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe HTTP::Request::Writer do
4
+ describe '#initalize' do
5
+ def construct(body)
6
+ HTTP::Request::Writer.new(nil, body, [], '')
7
+ end
8
+
9
+ it "doesn't throw on a nil body" do
10
+ expect { construct [] }.not_to raise_error
11
+ end
12
+
13
+ it "doesn't throw on a String body" do
14
+ expect { construct 'string body' }.not_to raise_error
15
+ end
16
+
17
+ it "doesn't throw on an Enumerable body" do
18
+ expect { construct %w[bees cows] }.not_to raise_error
19
+ end
20
+
21
+ it "does throw on a body that isn't string, enumerable or nil" do
22
+ expect { construct true }.to raise_error
23
+ end
24
+ end
25
+ end
@@ -1,19 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe HTTP::Request do
4
- describe "headers" do
5
- subject { HTTP::Request.new(:get, "http://example.com/", :accept => "text/html") }
4
+ it 'includes HTTP::Headers::Mixin' do
5
+ expect(described_class).to include HTTP::Headers::Mixin
6
+ end
7
+
8
+ it 'requires URI to have scheme part' do
9
+ expect { HTTP::Request.new(:get, 'example.com/') }.to \
10
+ raise_error(HTTP::Request::UnsupportedSchemeError)
11
+ end
12
+
13
+ it 'provides a #scheme accessor' do
14
+ request = HTTP::Request.new(:get, 'http://example.com/')
15
+ expect(request.scheme).to eq(:http)
16
+ end
6
17
 
7
- it "sets explicit headers" do
8
- expect(subject["Accept"]).to eq("text/html")
18
+ describe 'headers' do
19
+ subject { HTTP::Request.new(:get, 'http://example.com/', :accept => 'text/html') }
20
+
21
+ it 'sets explicit headers' do
22
+ expect(subject['Accept']).to eq('text/html')
23
+ end
24
+
25
+ it 'sets implicit headers' do
26
+ expect(subject['Host']).to eq('example.com')
9
27
  end
10
28
 
11
- it "sets implicit headers" do
12
- expect(subject["Host"]).to eq("example.com")
29
+ it 'provides a #verb accessor' do
30
+ expect(subject.verb).to eq(:get)
13
31
  end
14
32
 
15
- it "provides a #headers accessor" do
16
- expect(subject.headers).to eq("Accept" => "text/html", "Host" => "example.com")
33
+ it 'provides a #method accessor that outputs a deprecation warning and returns the verb' do
34
+ warning = capture_warning do
35
+ expect(subject.method).to eq(subject.verb)
36
+ end
37
+ expect(warning).to match(/\[DEPRECATION\] HTTP::Request#method is deprecated\. Use #verb instead\. For Object#method, use #__method__\.$/)
38
+ end
39
+
40
+ it 'provides a #__method__ method that delegates to Object#method' do
41
+ expect(subject.__method__(:verb)).to be_a(Method)
17
42
  end
18
43
  end
19
44
 
@@ -27,12 +52,12 @@ describe HTTP::Request do
27
52
 
28
53
  its(:uri) { should eq URI.parse 'http://blog.example.com/' }
29
54
 
30
- its(:method) { should eq request.method }
55
+ its(:verb) { should eq request.verb }
31
56
  its(:body) { should eq request.body }
32
57
  its(:proxy) { should eq request.proxy }
33
58
 
34
59
  it 'presets new Host header' do
35
- expect(redirected.headers['Host']).to eq 'blog.example.com'
60
+ expect(redirected['Host']).to eq 'blog.example.com'
36
61
  end
37
62
 
38
63
  context 'with relative URL given' do
@@ -40,12 +65,17 @@ describe HTTP::Request do
40
65
 
41
66
  its(:uri) { should eq URI.parse 'http://example.com/blog' }
42
67
 
43
- its(:method) { should eq request.method }
68
+ its(:verb) { should eq request.verb }
44
69
  its(:body) { should eq request.body }
45
70
  its(:proxy) { should eq request.proxy }
46
71
 
47
72
  it 'keeps Host header' do
48
- expect(redirected.headers['Host']).to eq 'example.com'
73
+ expect(redirected['Host']).to eq 'example.com'
74
+ end
75
+
76
+ context 'with original URI having non-standard port' do
77
+ let(:request) { HTTP::Request.new(:post, 'http://example.com:8080/', headers, proxy, body) }
78
+ its(:uri) { should eq URI.parse 'http://example.com:8080/blog' }
49
79
  end
50
80
  end
51
81
 
@@ -54,13 +84,23 @@ describe HTTP::Request do
54
84
 
55
85
  its(:uri) { should eq URI.parse 'http://example.com/blog' }
56
86
 
57
- its(:method) { should eq request.method }
87
+ its(:verb) { should eq request.verb }
58
88
  its(:body) { should eq request.body }
59
89
  its(:proxy) { should eq request.proxy }
60
90
 
61
91
  it 'keeps Host header' do
62
- expect(redirected.headers['Host']).to eq 'example.com'
92
+ expect(redirected['Host']).to eq 'example.com'
63
93
  end
94
+
95
+ context 'with original URI having non-standard port' do
96
+ let(:request) { HTTP::Request.new(:post, 'http://example.com:8080/', headers, proxy, body) }
97
+ its(:uri) { should eq URI.parse 'http://example.com:8080/blog' }
98
+ end
99
+ end
100
+
101
+ context 'with new verb given' do
102
+ subject { request.redirect 'http://blog.example.com/', :get }
103
+ its(:verb) { should be :get }
64
104
  end
65
105
  end
66
106
  end