proxy_service 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9fe64c3a39411580b172dee7b78d3881eb4c6adb
4
+ data.tar.gz: bef4e0293b96c681fff87a4ed1aee82bc0d5b596
5
+ SHA512:
6
+ metadata.gz: 8a32016d854eb268db463e123fa387e593cd15517ec87858851f5b5b64c1affdef403f8092d1133a7396606283f49cf4d4cd21753d04d985824dd0d8392f6211
7
+ data.tar.gz: adb5c27511b1b19aae97da7df82acf0c1ad51604bba504d02cb8d2c99f937e0fc70ed59d264197d422fbcdbc3f04e334b4121a30ceaf9cf3a92a6dfbd4fd9f55
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --require spec_helper
3
+
4
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in proxy_service.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ryan Buckley
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # ProxyService
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'proxy_service'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install proxy_service
18
+
19
+ ## Usage
20
+
21
+ ProxyService.new(:queue_name).with_mechanize do |agent|
22
+ agent.get('http://...')
23
+ end
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/ridiculous/proxy_service/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ require 'rdoc/task'
10
+ RDoc::Task.new do |rdoc|
11
+ rdoc.title = "proxy_service #{ProxyService::VERSION}"
12
+ rdoc.rdoc_files.include('README*')
13
+ rdoc.rdoc_files.include('lib/**/*.rb')
14
+ end
@@ -0,0 +1,82 @@
1
+ require 'json'
2
+
3
+ class ProxyService
4
+
5
+ # = Class
6
+ # @example Usage
7
+ #
8
+ # ProxyService.new(:queue_name).with_mechanize do |agent|
9
+ # agent.get('...')
10
+ # end
11
+ #
12
+
13
+ POLL_PERIOD = 1
14
+ MAX_FAILURES = 3
15
+
16
+ attr_accessor :source
17
+ attr_writer :proxies_enabled
18
+
19
+ # = Create a new proxy service with for a specific ODS
20
+ #
21
+ # @param [String|Symbol] source name of the ODS (e.g. :tripadvisor), will look for a queue with that name "proxy/#{source}"
22
+ # @param [Hash] options
23
+ # @option options [Boolean] :proxies_enabled override the app configuration
24
+ def initialize(source, options = {})
25
+ @source = source
26
+ @proxies_enabled = options.fetch(:proxies_enabled, false) # Rails.application.config.proxies.enabled
27
+ end
28
+
29
+ # @yield [agent] Passes a [proxied] Mechanize agent to the block
30
+ def with_mechanize
31
+ proxy = reserve_proxy
32
+ agent = MechanizeAgent.new
33
+ agent.set_proxy(proxy)
34
+ yield agent
35
+ proxy.reset_failures
36
+ rescue => e
37
+ if proxy.failures >= MAX_FAILURES
38
+ proxy.blocked!
39
+ else
40
+ proxy.increment_failures
41
+ end
42
+ puts "ERROR: #{e.message}"
43
+ ensure
44
+ proxy.release
45
+ end
46
+
47
+ #
48
+ # Private
49
+ #
50
+
51
+ def new_worker
52
+ if proxies_enabled?
53
+ Worker.new("proxy/#{source}")
54
+ else
55
+ NullWorker.new
56
+ end
57
+ end
58
+
59
+ def proxies_enabled?
60
+ @proxies_enabled
61
+ end
62
+
63
+ # = Creates a STOMP connection (consumer)
64
+ #
65
+ # @return [Proxy] a new proxy object
66
+ def reserve_proxy
67
+ worker = new_worker
68
+ worker.subscribe
69
+ loop do
70
+ if worker.ready?
71
+ return Proxy.new(worker)
72
+ else
73
+ sleep(POLL_PERIOD)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ require 'proxy_service/mechanize_agent'
80
+ require 'proxy_service/null_worker'
81
+ require 'proxy_service/proxy'
82
+ require 'proxy_service/worker'
@@ -0,0 +1,22 @@
1
+ require 'mechanize'
2
+
3
+ class ProxyService::MechanizeAgent < DelegateClass(Mechanize)
4
+ USER_AGENT_ALIASES = ['Windows Mozilla', 'Mac Safari', 'Mac FireFox', 'Mac Mozilla', 'Linux Mozilla', 'Linux Firefox']
5
+
6
+ def initialize
7
+ super Mechanize.new do |mech|
8
+ mech.open_timeout = 10
9
+ mech.read_timeout = 10
10
+ mech.follow_meta_refresh = true
11
+ mech.keep_alive = true
12
+ mech.max_history = 1
13
+ mech.user_agent_alias = USER_AGENT_ALIASES.sample
14
+ end
15
+ end
16
+
17
+ # @param [#ip, #port] proxy object that holds the ip and port
18
+ def set_proxy(proxy)
19
+ return unless proxy.ip
20
+ __getobj__.set_proxy(proxy.ip, proxy.port, Rails.application.config.proxies.user, Rails.application.config.proxies.password)
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ class ProxyService::NullWorker
2
+ NullMessage = Struct.new(:body)
3
+ attr_accessor :message
4
+
5
+ def initialize
6
+ @message = NullMessage.new('{}')
7
+ end
8
+
9
+ def call(*)
10
+ # no-op
11
+ end
12
+
13
+ def ready?
14
+ true
15
+ end
16
+
17
+ def subscribe
18
+ # no-op
19
+ end
20
+
21
+ def release(*)
22
+ # no-op
23
+ end
24
+ end
@@ -0,0 +1,69 @@
1
+ class ProxyService::Proxy
2
+
3
+ attr_accessor :worker
4
+ attr_writer :failures, :message, :ip, :port
5
+
6
+ # @param [Worker, NullWorker] worker that has a message and body
7
+ def initialize(worker)
8
+ @worker = worker
9
+ end
10
+
11
+ def ip
12
+ @ip ||= message['ip']
13
+ end
14
+
15
+ def port
16
+ @port ||= message['port']
17
+ end
18
+
19
+ def failures
20
+ @failures ||= message['failures']
21
+ end
22
+
23
+ def message
24
+ @message ||= { 'failures' => 0 }.merge(JSON.parse(worker.message.body))
25
+ end
26
+
27
+ def release
28
+ worker.release(self, blocked?)
29
+ end
30
+
31
+ def increment_failures
32
+ @failures += 1
33
+ end
34
+
35
+ def reset_failures
36
+ @failures = 0
37
+ end
38
+
39
+ def blocked?
40
+ !!@blocked
41
+ end
42
+
43
+ def blocked!
44
+ @blocked = true
45
+ end
46
+
47
+ #
48
+ # Coercion overrides
49
+ #
50
+
51
+ def to_h
52
+ {
53
+ ip: ip,
54
+ port: port,
55
+ failures: failures
56
+ }
57
+ end
58
+
59
+ def to_json
60
+ to_h.to_json
61
+ end
62
+
63
+ def to_s
64
+ %(#<Proxy:#{object_id} @ip="#{ip}", @port="#{port}", @failures=#{failures}, @blocked=#{blocked?}, @message=#{message}, @worker="...">)
65
+ end
66
+
67
+ alias inspect to_s
68
+
69
+ end
@@ -0,0 +1,3 @@
1
+ class ProxyService
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,23 @@
1
+ require 'queue_worker'
2
+
3
+ class ProxyService::Worker < QueueWorker
4
+
5
+ attr_accessor :message
6
+ attr_writer :ready
7
+
8
+ def call(message)
9
+ @message = message
10
+ @ready = true
11
+ end
12
+
13
+ def ready?
14
+ !!@ready
15
+ end
16
+
17
+ def release(proxy, is_blocked)
18
+ ack(message)
19
+ unsubscribe
20
+ publish(proxy) unless is_blocked
21
+ close
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'proxy_service/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "proxy_service"
8
+ spec.version = ProxyService::VERSION
9
+ spec.authors = ["Ryan Buckley"]
10
+ spec.email = ["arebuckley@gmail.com"]
11
+ spec.summary = %q{Manages rotation of proxies using a queue}
12
+ spec.description = %q{Manages rotation of proxies using a queuing system and STOMP}
13
+ spec.homepage = "https://github.com/ridiculous/proxy_service"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.1'
24
+
25
+ spec.add_runtime_dependency 'queue_worker', '~> 0.0', '>= 0.0.1'
26
+ spec.add_runtime_dependency 'mechanize', '~> 2.7', '>= 2.7.3'
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProxyService::Proxy do
4
+ subject { described_class.new(ProxyService::NullWorker.new) }
5
+
6
+ describe '#message' do
7
+ context 'when message.body is present' do
8
+ let(:message_body) { { ip: '127.0.0.1', port: '8080', failures: 1 }.to_json }
9
+
10
+ it 'returns the contents as a hash' do
11
+ expect(subject.worker.message).to receive(:body).and_return(message_body)
12
+ expect(subject.message).to eq(JSON.parse(message_body))
13
+ end
14
+ end
15
+
16
+ context 'when message.body is empty' do
17
+ it "returns a hash with a 'failures' key set to 0" do
18
+ expect(subject.message).to eq({ 'failures' => 0 })
19
+ end
20
+ end
21
+ end
22
+
23
+ describe '#release' do
24
+ it 'passes itself to worker.release with the @blocked status' do
25
+ expect(subject.worker).to receive(:release).with(subject, false)
26
+ subject.release
27
+ end
28
+ end
29
+
30
+ describe '#increment_failures' do
31
+ it 'increments the @failures count by 1' do
32
+ expect { subject.increment_failures }.to change(subject, :failures).from(0).to(1)
33
+ end
34
+ end
35
+
36
+ describe '#reset_failures' do
37
+ before { subject.failures = 2 }
38
+
39
+ it 'resets the @failures count to 0' do
40
+ expect { subject.reset_failures }.to change(subject, :failures).from(2).to(0)
41
+ end
42
+ end
43
+
44
+ describe '#blocked!' do
45
+ it 'sets @blocked to true' do
46
+ expect { subject.blocked! }.to change(subject, :blocked?).from(false).to(true)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProxyService::Worker do
4
+
5
+ let(:queue_name) { 'proxy/trip_advisor' }
6
+ let(:message) { {} }
7
+ let(:proxy) { double('Proxy') }
8
+
9
+ subject { described_class.new(queue_name) }
10
+
11
+ describe '#call' do
12
+ it 'sets @message to message and @ready to true' do
13
+ expect(subject).to_not be_ready
14
+ expect { subject.call(message) }.to change(subject, :message).from(nil).to(message)
15
+ expect(subject).to be_ready
16
+ end
17
+ end
18
+
19
+ describe '#release' do
20
+ before(:each) { subject.message = message }
21
+
22
+ it 'pops and then pushes the -proxy- onto the queue' do
23
+ expect(subject).to receive(:ack).with(message)
24
+ expect(subject).to receive(:unsubscribe)
25
+ expect(subject).to receive(:publish).with(proxy)
26
+ expect(subject).to receive(:close)
27
+ subject.release(proxy, false)
28
+ end
29
+
30
+ context 'when proxy is blocked' do
31
+ it 'pops the message from the queue but does not push it back on' do
32
+ expect(subject).to receive(:ack).with(message)
33
+ expect(subject).to receive(:unsubscribe)
34
+ expect(subject).to_not receive(:publish).with(proxy)
35
+ expect(subject).to receive(:close)
36
+ subject.release(proxy, true)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProxyService do
4
+
5
+ subject { described_class.new(:trip_advisor) }
6
+
7
+ describe '#new_worker' do
8
+ context 'when proxies are enabled' do
9
+ before { subject.proxies_enabled = true }
10
+
11
+ it 'returns a new ProxyWorker' do
12
+ worker = subject.new_worker
13
+ expect(worker).to be_a(ProxyService::Worker)
14
+ expect(worker.queue).to eq 'proxy/trip_advisor'
15
+ end
16
+ end
17
+
18
+ context 'when proxies are disabled' do
19
+ it 'returns a new NullWorker' do
20
+ expect(subject.new_worker).to be_a(ProxyService::NullWorker)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#with_mechanize' do
26
+ let(:worker) { ProxyService::NullWorker.new }
27
+ let(:proxy) { ProxyService::Proxy.new(worker) }
28
+
29
+ before(:each) { allow(subject).to receive(:reserve_proxy).and_return(proxy) }
30
+
31
+ it 'passes a new mechanize agent to the given block' do
32
+ expect_any_instance_of(ProxyService::MechanizeAgent).to receive(:set_proxy).with(proxy)
33
+ subject.with_mechanize do |agent|
34
+ expect(agent).to be_a(ProxyService::MechanizeAgent)
35
+ expect(agent).to respond_to(:get)
36
+ end
37
+ end
38
+
39
+ it 'releases the proxy and resets its failures count' do
40
+ expect(proxy).to receive(:reset_failures)
41
+ expect(proxy).to receive(:release)
42
+ subject.with_mechanize { |_| }
43
+ end
44
+
45
+ context 'when there is an exception' do
46
+ it 'does not reset the failures count' do
47
+ expect(proxy).to_not receive(:reset_failures)
48
+ expect(proxy).to receive(:release)
49
+ subject.with_mechanize { |_| raise 'Testing exception' }
50
+ end
51
+
52
+ context 'when +failures+ exceeds max failures' do
53
+ it 'blocks the proxy' do
54
+ expect(proxy).to receive(:failures).and_return(ProxyService::MAX_FAILURES + 1)
55
+ expect(proxy).to receive(:blocked!)
56
+ subject.with_mechanize { |_| raise 'Testing exception' }
57
+ end
58
+ end
59
+
60
+ context 'when +failures+ does not exceed max failures' do
61
+ it "increments the proxy's +failures+ count" do
62
+ expect(proxy).to receive(:increment_failures)
63
+ subject.with_mechanize { |_| raise 'Testing exception' }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ require 'proxy_service'
3
+
4
+ RSpec.configure do |c|
5
+ c.mock_with :rspec
6
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proxy_service
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Buckley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: queue_worker
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.0'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 0.0.1
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '0.0'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 0.0.1
75
+ - !ruby/object:Gem::Dependency
76
+ name: mechanize
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.7'
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 2.7.3
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '2.7'
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 2.7.3
95
+ description: Manages rotation of proxies using a queuing system and STOMP
96
+ email:
97
+ - arebuckley@gmail.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - ".gitignore"
103
+ - ".rspec"
104
+ - Gemfile
105
+ - LICENSE.txt
106
+ - README.md
107
+ - Rakefile
108
+ - lib/proxy_service.rb
109
+ - lib/proxy_service/mechanize_agent.rb
110
+ - lib/proxy_service/null_worker.rb
111
+ - lib/proxy_service/proxy.rb
112
+ - lib/proxy_service/version.rb
113
+ - lib/proxy_service/worker.rb
114
+ - proxy_service.gemspec
115
+ - spec/lib/proxy_service/proxy_spec.rb
116
+ - spec/lib/proxy_service/worker_spec.rb
117
+ - spec/lib/proxy_service_spec.rb
118
+ - spec/spec_helper.rb
119
+ homepage: https://github.com/ridiculous/proxy_service
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.4.3
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Manages rotation of proxies using a queue
143
+ test_files:
144
+ - spec/lib/proxy_service/proxy_spec.rb
145
+ - spec/lib/proxy_service/worker_spec.rb
146
+ - spec/lib/proxy_service_spec.rb
147
+ - spec/spec_helper.rb