api2cart-daemon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 02311a7251c3822d41797800ea794496605e5d7f
4
+ data.tar.gz: ba81ae725d85c75a352a769b26106fd509618cb3
5
+ SHA512:
6
+ metadata.gz: 81427570dbeec402e9605beadf0f4cb7396d32388e819cb44a9ac8a6d9449b1eeb09ea32a8dc03466e313d8c039876a0d8366063793c934c744d0a935ddd2f45
7
+ data.tar.gz: 1667fcaf89f99096affec51f666b92ad3a3f47827ab9f8cf75e166e0bf589e7b60699d671dff0c72876b43840f4cb328e376c67bceb68d2156cf1a647e1dc853
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in api2cart-daemon.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Daniel Vartanov
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.
@@ -0,0 +1,33 @@
1
+ # API2Cart Daemon
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'api2cart-daemon'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install api2cart-daemon
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ Api2cart::Daemon.run port
25
+ ```
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/[my-github-username]/api2cart-daemon/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'api2cart/daemon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "api2cart-daemon"
8
+ spec.version = Api2cart::Daemon::VERSION
9
+ spec.authors = ["Daniel Vartanov"]
10
+ spec.email = ["dan@vartanov.net"]
11
+ spec.summary = %q{Anti throttling proxy server for API2Cart requests}
12
+ spec.description = %q{Anti throttling proxy server for API2Cart requests}
13
+ spec.homepage = "https://github.com/DanielVartanov/api2cart-daemon"
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_dependency 'celluloid-io'
22
+ spec.add_dependency 'http_parser.rb'
23
+ spec.add_dependency 'activesupport', '~> 4.1'
24
+ spec.add_dependency 'reel'
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "http"
29
+ end
@@ -0,0 +1,17 @@
1
+ require 'api2cart/daemon/version'
2
+ require 'api2cart/daemon/http_message_reader'
3
+ require 'api2cart/daemon/proxy_server'
4
+ require 'api2cart/daemon/proxy_connection_handler'
5
+ require 'active_support/core_ext/module/delegation'
6
+
7
+ module Api2cart
8
+ module Daemon
9
+ def self.run(port)
10
+ ProxyServer.new.run(port)
11
+ end
12
+
13
+ def self.run_async(port)
14
+ ProxyServer.new.run_async(port)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ require 'ostruct'
2
+ require 'http_parser'
3
+
4
+ module Api2cart::Daemon
5
+ class HTTPMessageReader < Struct.new(:socket)
6
+ READ_BUFFER_SIZE = 16384
7
+
8
+ def initialize(*args)
9
+ super
10
+ initialize_parser!
11
+ end
12
+
13
+ def read_http_message
14
+ message = read_entire_message_from_socket!
15
+ host, port = parse_host_and_port(parser.headers['Host'])
16
+ OpenStruct.new message: message, request_host: host, request_port: port, request_url: parser.request_url
17
+ end
18
+
19
+ protected
20
+
21
+ attr_reader :parser
22
+
23
+ def initialize_parser!
24
+ @complete_http_message_received = false
25
+
26
+ @parser = HTTP::Parser.new
27
+
28
+ parser.on_message_complete = ->() do
29
+ @complete_http_message_received = true
30
+ end
31
+ end
32
+
33
+ def read_next_chunk
34
+ socket.readpartial(READ_BUFFER_SIZE).tap do |chunk|
35
+ parser << chunk
36
+ end
37
+ end
38
+
39
+ def read_entire_message_from_socket!
40
+ ''.tap do |raw_data|
41
+ raw_data << read_next_chunk until complete_http_message_received?
42
+ end
43
+ end
44
+
45
+ def complete_http_message_received?
46
+ !! @complete_http_message_received
47
+ end
48
+
49
+ def parse_host_and_port(host_and_port)
50
+ return nil if host_and_port.nil?
51
+
52
+ host, port = host_and_port.split(':')
53
+ port ||= 80
54
+ [host, port]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,74 @@
1
+ require 'reel/spy'
2
+
3
+ module Api2cart::Daemon
4
+ class ProxyConnectionHandler
5
+ def handle_proxy_connection(client_socket)
6
+ client_socket = Reel::Spy.new client_socket
7
+
8
+ http_message = read_http_message(client_socket)
9
+
10
+ response = if not_proxy_request?(http_message)
11
+ bad_request
12
+ else
13
+ begin
14
+ processed_message = process_client_request(http_message)
15
+ send_request_to_remote_server(http_message.request_host, http_message.request_port, processed_message)
16
+ rescue Exception => e
17
+ puts "! Problem connecting to server: #{e.inspect}"
18
+ internal_server_error(e)
19
+ end
20
+ end
21
+
22
+ send_response_to_client(client_socket, response)
23
+ end
24
+
25
+ protected
26
+
27
+ def process_client_request(client_request)
28
+ parsed_request_url = URI.parse client_request.request_url
29
+ client_request.message.gsub client_request.request_url, parsed_request_url.request_uri
30
+ end
31
+
32
+ def read_http_message(socket)
33
+ HTTPMessageReader.new(socket).read_http_message
34
+ end
35
+
36
+ def send_request_to_remote_server(host, port, request)
37
+ remote_server_socket = TCPSocket.new host, port
38
+ remote_server_socket = Celluloid::IO::TCPSocket.from_ruby_socket remote_server_socket
39
+ remote_server_socket = Reel::Spy.new remote_server_socket
40
+
41
+ remote_server_socket.write request
42
+ read_http_message(remote_server_socket).message
43
+ end
44
+
45
+ def send_response_to_client(client_socket, response)
46
+ client_socket.write response
47
+ client_socket.close
48
+ end
49
+
50
+ def not_proxy_request?(http_message)
51
+ URI.parse(http_message.request_url).host.nil?
52
+ end
53
+
54
+ def internal_server_error(exception)
55
+ <<MESSAGE + exception.inspect
56
+ HTTP/1.1 500 Internal Server Error
57
+ Content-Length: #{exception.inspect.bytesize}
58
+ Connection: close
59
+ Status: 500 Internal Server Error
60
+
61
+ MESSAGE
62
+ end
63
+
64
+ def bad_request
65
+ <<MESSAGE
66
+ HTTP/1.1 400 Bad Request
67
+ Content-Length: 0
68
+ Connection: close
69
+ Status: 400 Bad Request
70
+
71
+ MESSAGE
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,35 @@
1
+ require 'celluloid/io'
2
+
3
+ module Api2cart::Daemon
4
+ class ProxyServer
5
+ include Celluloid::IO
6
+
7
+ def run(port)
8
+ accept_connections bind_proxy_socket(port)
9
+ end
10
+
11
+ def run_async(port)
12
+ async.accept_connections bind_proxy_socket(port)
13
+ end
14
+
15
+ protected
16
+
17
+ def bind_proxy_socket(port)
18
+ TCPServer.new('', port).tap do
19
+ puts "API2Cart Daemon is running at 0.0.0.0:#{port}"
20
+ end
21
+ end
22
+
23
+ def accept_connections(proxy_socket)
24
+ loop { async.handle_connection proxy_socket.accept }
25
+ end
26
+
27
+ def handle_connection(client_socket)
28
+ begin
29
+ ProxyConnectionHandler.new.handle_proxy_connection(client_socket)
30
+ rescue Exception => e
31
+ puts "! Exception: #{e.inspect}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Api2cart
2
+ module Daemon
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,72 @@
1
+ describe Api2cart::Daemon, 'happy path' do
2
+ let(:daemon_proxy) { Api2cart::Daemon::ProxyServer.new }
3
+
4
+ before do
5
+ Celluloid.shutdown
6
+ Celluloid.boot
7
+ end
8
+
9
+ before { daemon_proxy.run_async 2048 }
10
+
11
+ after { Celluloid::Actor.kill(daemon_proxy) }
12
+
13
+ shared_examples 'it continues to serve' do
14
+ let(:mock_remote_server) { MockRemoteServer.new(4096, 'I am a mock server') }
15
+
16
+ before { mock_remote_server.run_async }
17
+
18
+ let(:proxy_response) { HTTP.via('localhost', 2048).get('http://localhost:4096').to_s }
19
+
20
+ specify do
21
+ expect(proxy_response).to eq("I am a mock server")
22
+ end
23
+ end
24
+
25
+ context 'when it is a direct request, not proxying one' do
26
+ let!(:response) { HTTP.get('http://localhost:2048') }
27
+
28
+ it 'responds with 400 [Bad Reqest]' do
29
+ expect(response.status).to eq(400)
30
+ end
31
+
32
+ include_examples 'it continues to serve'
33
+ end
34
+
35
+ context 'when request is recursive' do
36
+ let!(:response) { HTTP.via('localhost', 2048).get('http://localhost:2048').to_s }
37
+
38
+ it 'returns nothing' do
39
+ expect(response).to eq('')
40
+ end
41
+
42
+ include_examples 'it continues to serve'
43
+ end
44
+
45
+ context 'when server is unreachable' do
46
+ let!(:response) { HTTP.via('localhost', 2048).get('http://localhost:9999') }
47
+
48
+ it 'responds with 500 [Internal Server Error]' do
49
+ expect(response.status).to eq(500)
50
+ end
51
+
52
+ include_examples 'it continues to serve'
53
+ end
54
+
55
+ context 'when request is malformed' do
56
+ before do
57
+ client_socket = TCPSocket.new 'localhost', 2048
58
+ client_socket.write 'ok google, where is London Eye?'
59
+ end
60
+
61
+ include_examples 'it continues to serve'
62
+ end
63
+
64
+ context 'when client suddenly breaks connection' do
65
+ before do
66
+ client_socket = TCPSocket.new 'localhost', 2048
67
+ client_socket.close
68
+ end
69
+
70
+ include_examples 'it continues to serve'
71
+ end
72
+ end
@@ -0,0 +1,73 @@
1
+ describe Api2cart::Daemon, 'happy path' do
2
+ let(:mock_server) { MockRemoteServer.new(4096, 'I am a mock server') }
3
+ let(:daemon_proxy) { Api2cart::Daemon::ProxyServer.new }
4
+
5
+ before do
6
+ Celluloid.shutdown
7
+ Celluloid.boot
8
+ end
9
+
10
+ before do
11
+ mock_server.run_async
12
+ daemon_proxy.run_async 2048
13
+ end
14
+
15
+ after do
16
+ Celluloid::Actor.kill(daemon_proxy)
17
+ Celluloid::Actor.kill(mock_server)
18
+ end
19
+
20
+ shared_examples 'HTTP proxy server' do
21
+ let!(:proxy_response) { HTTP.via('localhost', 2048).get(request_url).to_s }
22
+
23
+ it 'proxies request to remote server' do
24
+ expect(proxy_response).to eq("I am a mock server")
25
+ expect(mock_server.request_log).to eq(expected_request_log)
26
+ end
27
+ end
28
+
29
+ context 'when URL is normalized' do
30
+ let(:request_url) { 'http://localhost:4096/' }
31
+
32
+ let(:expected_request_log) do
33
+ <<EXPECTED_SERVER_LOG
34
+ GET / HTTP/1.1\r
35
+ Host: localhost:4096\r
36
+ User-Agent: RubyHTTPGem/0.6.2\r
37
+ \r
38
+ EXPECTED_SERVER_LOG
39
+ end
40
+
41
+ include_examples 'HTTP proxy server'
42
+ end
43
+
44
+ context 'when URL is not normalized' do
45
+ let(:request_url) { 'http://localhost:4096' }
46
+
47
+ let(:expected_request_log) do
48
+ <<EXPECTED_SERVER_LOG
49
+ GET / HTTP/1.1\r
50
+ Host: localhost:4096\r
51
+ User-Agent: RubyHTTPGem/0.6.2\r
52
+ \r
53
+ EXPECTED_SERVER_LOG
54
+ end
55
+
56
+ include_examples 'HTTP proxy server'
57
+ end
58
+
59
+ context 'when URL contains path and query' do
60
+ let(:request_url) { 'http://localhost:4096/path?key=value&another_key=another_value' }
61
+
62
+ let(:expected_request_log) do
63
+ <<EXPECTED_SERVER_LOG
64
+ GET /path?key=value&another_key=another_value HTTP/1.1\r
65
+ Host: localhost:4096\r
66
+ User-Agent: RubyHTTPGem/0.6.2\r
67
+ \r
68
+ EXPECTED_SERVER_LOG
69
+ end
70
+
71
+ include_examples 'HTTP proxy server'
72
+ end
73
+ end
@@ -0,0 +1,139 @@
1
+ describe Api2cart::Daemon::HTTPMessageReader do
2
+ let(:socket) { StringIO.new(message) }
3
+ let(:http_message_reader) { Api2cart::Daemon::HTTPMessageReader.new(socket) }
4
+
5
+ let(:http_message) { http_message_reader.read_http_message }
6
+
7
+ context 'when message is a valid HTTP request' do
8
+ describe 'raw HTTP message' do
9
+ subject { http_message.message }
10
+
11
+ let(:message) do
12
+ <<MESSAGE
13
+ GET http://api.api2cart.com/v1.0/product.count.json HTTP/1.1
14
+ Host: api.api2cart.com
15
+ User-Agent: RubyHTTPGem/0.6.2
16
+
17
+ MESSAGE
18
+ end
19
+
20
+ it 'returns the full message' do
21
+ should == message
22
+ end
23
+
24
+ context 'when message is very long' do
25
+ let(:long_text) { '0' * Api2cart::Daemon::HTTPMessageReader::READ_BUFFER_SIZE * 3 }
26
+
27
+ let(:message) do
28
+ <<HEADERS + long_text
29
+ POST / HTTP/1.1
30
+ Host: localhost:4096
31
+ Content-Length: #{long_text.bytesize}
32
+
33
+ HEADERS
34
+ end
35
+
36
+ it 'parses it all not depending on buffer size' do
37
+ should == message
38
+ end
39
+ end
40
+ end
41
+
42
+ describe 'request host' do
43
+ subject { http_message.request_host }
44
+
45
+ let(:message) do
46
+ <<MESSAGE
47
+ GET http://api.api2cart.com/v1.0/product.count.json HTTP/1.1
48
+ Host: api.api2cart.com
49
+ User-Agent: RubyHTTPGem/0.6.2
50
+
51
+ MESSAGE
52
+ end
53
+
54
+ it 'parses request host from headers' do
55
+ should == 'api.api2cart.com'
56
+ end
57
+ end
58
+
59
+ describe 'request port' do
60
+ subject { http_message.request_port }
61
+
62
+ context 'when port is not specified' do
63
+ let(:message) do
64
+ <<MESSAGE
65
+ GET http://api.api2cart.com/v1.0/product.count.json HTTP/1.1
66
+ Host: api.api2cart.com
67
+ User-Agent: RubyHTTPGem/0.6.2
68
+
69
+ MESSAGE
70
+ end
71
+
72
+ it { should == 80 }
73
+ end
74
+
75
+ context 'when port is specified explicitly' do
76
+ let(:message) do
77
+ <<MESSAGE
78
+ GET http://localhost:4096 HTTP/1.1
79
+ Host: localhost:4096
80
+ User-Agent: RubyHTTPGem/0.6.2
81
+
82
+ MESSAGE
83
+ end
84
+
85
+ it 'parses port from headers' do
86
+ should == '4096'
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'request url' do
92
+ subject { http_message.request_url }
93
+
94
+ let(:message) do
95
+ <<MESSAGE
96
+ GET http://localhost:4096/path?key=value HTTP/1.1
97
+ Host: localhost:4096
98
+ User-Agent: RubyHTTPGem/0.6.2
99
+
100
+ MESSAGE
101
+ end
102
+
103
+ it 'parses request URL from request line' do
104
+ should == 'http://localhost:4096/path?key=value'
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'when message is a valid HTTP response' do
110
+ subject { http_message.message }
111
+
112
+ let(:message) do
113
+ <<MESSAGE
114
+ HTTP/1.1 200 OK
115
+ Date: Tue, 18 Nov 2014 10:19:56 GMT
116
+ Server: Apache/2.2.27 (Unix) mod_ssl/2.2.27 OpenSSL/1.0.1e-fips mod_bwlimited/1.4 mod_fcgid/2.3.9
117
+ X-Powered-By: PHP/5.3.28
118
+ Expires: Thu, 19 Nov 1981 08:52:00 GMT
119
+ Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
120
+ Pragma: no-cache
121
+ Set-Cookie: PHPSESSID=61cc757b3cd2d0048f1796d8e27b0f34; expires=Tue, 18-Nov-2014 12:20:30 GMT; path=/
122
+ Access-Control-Allow-Origin: *
123
+ Access-Control-Allow-Headers: origin, x-requested-with, content-type
124
+ Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS
125
+ Transfer-Encoding: chunked
126
+ Content-Type: application/json; charset=utf-8
127
+
128
+ 4b\r
129
+ {"return_code" : 0, "return_message": "", "result" : {"products_count":76}}\r
130
+ 0\r
131
+ \r
132
+ MESSAGE
133
+ end
134
+
135
+ it 'returns the full message' do
136
+ should == message
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,14 @@
1
+ require_relative File.join(__dir__, '../lib/api2cart/daemon')
2
+
3
+ require 'rspec/core'
4
+ require 'http'
5
+
6
+ Dir[File.join(__dir__, "support/**/*.rb")].each { |f| require f }
7
+ Dir[File.join(__dir__, "shared_examples/**/*.rb")].sort.each { |f| require f }
8
+
9
+ RSpec.configure do |config|
10
+ config.before do
11
+ Celluloid.shutdown
12
+ Celluloid.boot
13
+ end
14
+ end
@@ -0,0 +1,39 @@
1
+ require 'stringio'
2
+
3
+ class MockRemoteServer < Struct.new(:port, :response)
4
+ include Celluloid::IO
5
+
6
+ def run_async
7
+ server_socket = TCPServer.new '', port
8
+ async.accept_connections server_socket
9
+ end
10
+
11
+ def request_log
12
+ request_logger.string
13
+ end
14
+
15
+ protected
16
+
17
+ def request_logger
18
+ @request_logger ||= StringIO.new
19
+ end
20
+
21
+ def accept_connections(server_socket)
22
+ loop do
23
+ client_socket = server_socket.accept
24
+ request_logger.write client_socket.readpartial(16384)
25
+ client_socket.write compose_http_response
26
+ client_socket.close
27
+ end
28
+ end
29
+
30
+ def compose_http_response
31
+ <<RESPONSE + response
32
+ HTTP/1.1 200 OK
33
+ Content-Type: text/plain
34
+ Content-Length: #{response.bytesize}
35
+ Connection: close
36
+
37
+ RESPONSE
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: api2cart-daemon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Vartanov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: celluloid-io
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: http_parser.rb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '4.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '4.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: reel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: http
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Anti throttling proxy server for API2Cart requests
126
+ email:
127
+ - dan@vartanov.net
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .rspec
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - api2cart-daemon.gemspec
139
+ - lib/api2cart/daemon.rb
140
+ - lib/api2cart/daemon/http_message_reader.rb
141
+ - lib/api2cart/daemon/proxy_connection_handler.rb
142
+ - lib/api2cart/daemon/proxy_server.rb
143
+ - lib/api2cart/daemon/version.rb
144
+ - spec/api2cart/daemon/errors_spec.rb
145
+ - spec/api2cart/daemon/happy_path_spec.rb
146
+ - spec/api2cart/daemon/http_message_reader_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/support/mock_remote_server.rb
149
+ homepage: https://github.com/DanielVartanov/api2cart-daemon
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.4.2
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Anti throttling proxy server for API2Cart requests
173
+ test_files:
174
+ - spec/api2cart/daemon/errors_spec.rb
175
+ - spec/api2cart/daemon/happy_path_spec.rb
176
+ - spec/api2cart/daemon/http_message_reader_spec.rb
177
+ - spec/spec_helper.rb
178
+ - spec/support/mock_remote_server.rb