ec2-api-proxy 0.1.0
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.
- data/README +58 -0
- data/bin/eapctl +58 -0
- data/bin/ec2-api-proxy +49 -0
- data/lib/ec2-api-proxy/backend.rb +46 -0
- data/lib/ec2-api-proxy/constants.rb +2 -0
- data/lib/ec2-api-proxy/proxy.rb +129 -0
- data/lib/ec2-api-proxy/server.rb +48 -0
- metadata +101 -0
data/README
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= ec2-api-proxy
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
ec2-api-proxy is a proxy server for EC2 API.
|
6
|
+
|
7
|
+
== Source Code
|
8
|
+
|
9
|
+
https://bitbucket.org/winebarrel/ec2-api-proxy
|
10
|
+
|
11
|
+
== Dependency
|
12
|
+
|
13
|
+
* EventMachine
|
14
|
+
* RExec
|
15
|
+
* Dalli
|
16
|
+
|
17
|
+
== Install
|
18
|
+
|
19
|
+
gem install ec2-api-proxy
|
20
|
+
|
21
|
+
== Example
|
22
|
+
|
23
|
+
shell> ec2-api-proxy start
|
24
|
+
Starting daemon...
|
25
|
+
Waiting for daemon to start...
|
26
|
+
Daemon status: running pid=56921
|
27
|
+
|
28
|
+
shell> eapctl status
|
29
|
+
---
|
30
|
+
memcached: localhost:11211
|
31
|
+
expires: 60
|
32
|
+
compress: false
|
33
|
+
debug: false
|
34
|
+
shell> eapctl set-debug true
|
35
|
+
|
36
|
+
shell> export HTTP_PROXY=localhost:8080
|
37
|
+
shell> aws ec2 describe-instances --endpoint-url=http://ec2.ap-northeast-1.amazonaws.com # It does not support HTTPS still
|
38
|
+
{
|
39
|
+
"Reservations": [
|
40
|
+
{
|
41
|
+
...
|
42
|
+
shell> aws ec2 describe-instances --endpoint-url=http://ec2.ap-northeast-1.amazonaws.com
|
43
|
+
{
|
44
|
+
"Reservations": [
|
45
|
+
{
|
46
|
+
...
|
47
|
+
|
48
|
+
shell> tail -f /var/log/ec2-api-proxy.log
|
49
|
+
...
|
50
|
+
D, [2013-07-15T20:41:28.472712 #76304] DEBUG -- : proxy: connect from 127.0.0.1:58456
|
51
|
+
D, [2013-07-15T20:41:28.529715 #76304] DEBUG -- : proxy: cache miss
|
52
|
+
D, [2013-07-15T20:41:29.051745 #76304] DEBUG -- : proxy: unbind connection from 127.0.0.1:58456
|
53
|
+
D, [2013-07-15T20:41:29.051745 #76304] DEBUG -- : backend: unbind connection
|
54
|
+
D, [2013-07-15T20:41:29.052745 #76304] DEBUG -- : backend: set cache (length=22330)
|
55
|
+
D, [2013-07-15T20:41:31.202868 #76304] DEBUG -- : proxy: connect from 127.0.0.1:58459
|
56
|
+
D, [2013-07-15T20:41:31.207869 #76304] DEBUG -- : proxy: cache hit (length=22330)
|
57
|
+
D, [2013-07-15T20:41:31.209869 #76304] DEBUG -- : proxy: unbind connection from 127.0.0.1:58459
|
58
|
+
|
data/bin/eapctl
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'drb/drb'
|
5
|
+
require 'optparse'
|
6
|
+
require 'ec2-api-proxy/constants'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
COMMAND_NAME = File.basename(__FILE__)
|
10
|
+
|
11
|
+
COMMANDS = {
|
12
|
+
'status' => lambda {|server_options|
|
13
|
+
h = {}
|
14
|
+
server_options.to_hash.each {|k, v| h[k.to_s] = v }
|
15
|
+
puts YAML.dump(h)
|
16
|
+
},
|
17
|
+
'set-expires' => lambda {|server_options, v|
|
18
|
+
server_options[:expires] = v.to_i
|
19
|
+
},
|
20
|
+
'set-debug' => lambda {|server_options, v|
|
21
|
+
server_options[:debug] = !!(/#{Regexp.escape(v)}/i =~ 'true')
|
22
|
+
},
|
23
|
+
}
|
24
|
+
|
25
|
+
options = nil
|
26
|
+
|
27
|
+
ARGV.options do |parser|
|
28
|
+
cmds = COMMANDS.keys
|
29
|
+
|
30
|
+
options = {
|
31
|
+
:socket => "/var/tmp/#{APP_NAME}.sock",
|
32
|
+
}
|
33
|
+
|
34
|
+
parser.on('-S', '--socket=SOCK_FILE') {|v| options[:socket] = v }
|
35
|
+
|
36
|
+
help_and_exit = lambda do |v|
|
37
|
+
$stderr.puts parser.help.sub(COMMAND_NAME, "#{COMMAND_NAME} {#{cmds.join('|')}}")
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
parser.on('-h', '--help', &help_and_exit)
|
42
|
+
|
43
|
+
parser.parse!
|
44
|
+
|
45
|
+
unless ARGV.length >= 1 and cmds.include?(ARGV[0])
|
46
|
+
help_and_exit.call(true)
|
47
|
+
end
|
48
|
+
end # parse options
|
49
|
+
|
50
|
+
server_options = DRbObject.new_with_uri("drbunix:#{options[:socket]}")
|
51
|
+
|
52
|
+
cmd = COMMANDS.fetch(ARGV[0])
|
53
|
+
|
54
|
+
if cmd.arity > 1
|
55
|
+
cmd.call(server_options, ARGV[1])
|
56
|
+
else
|
57
|
+
cmd.call(server_options)
|
58
|
+
end
|
data/bin/ec2-api-proxy
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'ec2-api-proxy/constants'
|
6
|
+
require 'ec2-api-proxy/server'
|
7
|
+
|
8
|
+
options = nil
|
9
|
+
|
10
|
+
ARGV.options do |parser|
|
11
|
+
daemon_commands = %w(start stop restart status)
|
12
|
+
|
13
|
+
options = {
|
14
|
+
:addr => '0.0.0.0',
|
15
|
+
:port => 8080,
|
16
|
+
:memcached => 'localhost:11211',
|
17
|
+
:expires => 60,
|
18
|
+
:compress => false,
|
19
|
+
:working_dir => '/var',
|
20
|
+
:socket => "/var/tmp/#{APP_NAME}.sock",
|
21
|
+
:debug => false,
|
22
|
+
}
|
23
|
+
|
24
|
+
parser.on('-a', '--addr=ADDR' ) {|v| options[:addr] = v }
|
25
|
+
parser.on('-p', '--port=PORT' ) {|v| options[:port] = v.to_i }
|
26
|
+
parser.on('-m', '--memcached=HOST:PORT') {|v| options[:memcached] = v }
|
27
|
+
parser.on('-e', '--expires=SECOND' ) {|v| options[:expires] = v.to_i }
|
28
|
+
parser.on('-c', '--compress' ) { options[:compress] = true }
|
29
|
+
parser.on('-n', '--threads=SIZE' ) {|v| options[:threads] = v.to_i }
|
30
|
+
parser.on('-W', '--working-dir=DIR' ) {|v| options[:working_dir] = v }
|
31
|
+
parser.on('-S', '--socket=SOCK_FILE' ) {|v| options[:socket] = v }
|
32
|
+
parser.on('', '--debug' ) { options[:debug] = true }
|
33
|
+
|
34
|
+
help_and_exit = lambda do |v|
|
35
|
+
$stderr.puts parser.help.sub(APP_NAME, "#{APP_NAME} {#{daemon_commands.join('|')}}")
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.on('-h', '--help', &help_and_exit)
|
40
|
+
|
41
|
+
parser.parse!
|
42
|
+
|
43
|
+
unless ARGV.length == 1 and daemon_commands.include?(ARGV[0])
|
44
|
+
help_and_exit.call(true)
|
45
|
+
end
|
46
|
+
end # parse options
|
47
|
+
|
48
|
+
EC2APIProxy::Server.options = options
|
49
|
+
EC2APIProxy::Server.daemonize
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module EC2APIProxy
|
4
|
+
class Backend < EM::Connection
|
5
|
+
def initialize(proxy, memcached_key)
|
6
|
+
@proxy = proxy
|
7
|
+
@logger = proxy.logger
|
8
|
+
@memcached_key = memcached_key
|
9
|
+
@data_list = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def unbind
|
13
|
+
if @proxy and @proxy.options[:debug]
|
14
|
+
@logger.debug("backend: unbind connection")
|
15
|
+
end
|
16
|
+
|
17
|
+
unless @data_list.empty?
|
18
|
+
data = @data_list.join
|
19
|
+
@proxy.memcached.set(@memcached_key, data, @proxy.options[:expires])
|
20
|
+
|
21
|
+
if @proxy and @proxy.options[:debug]
|
22
|
+
@logger.debug("backend: set cache (length=#{data.length})")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def receive_data(data)
|
28
|
+
if not @proxy or @proxy.error?
|
29
|
+
@logger.info("backend: proxy connection error: #{data.inspect}")
|
30
|
+
close_proxy_connection
|
31
|
+
close_connection_after_writing
|
32
|
+
else
|
33
|
+
@data_list << data
|
34
|
+
@proxy.send_data(data)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def close_proxy_connection
|
41
|
+
@proxy.close_connection if @proxy
|
42
|
+
rescue Exception => e
|
43
|
+
@logger.warn("#{e.class.name}: #{e.message}")
|
44
|
+
end
|
45
|
+
end # Backend
|
46
|
+
end # EC2APIProxy
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'ec2-api-proxy/backend'
|
3
|
+
require 'logger'
|
4
|
+
require 'uri'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
module EC2APIProxy
|
8
|
+
class Proxy < EM::Connection
|
9
|
+
attr_reader :options
|
10
|
+
attr_reader :logger
|
11
|
+
attr_reader :memcached
|
12
|
+
|
13
|
+
def initialize(options)
|
14
|
+
@options = options
|
15
|
+
@memcached = Dalli::Client.new(options[:memcached], :compress => options[:compress])
|
16
|
+
@logger = Logger.new($stdout)
|
17
|
+
end
|
18
|
+
|
19
|
+
def post_init
|
20
|
+
if (peername = get_peername)
|
21
|
+
@connect_from = Socket.unpack_sockaddr_in(peername)
|
22
|
+
end
|
23
|
+
|
24
|
+
if @options[:debug]
|
25
|
+
port, ip = @connect_from
|
26
|
+
@logger.debug("proxy: connect from #{ip}:#{port}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def unbind
|
31
|
+
close_backend_connection
|
32
|
+
|
33
|
+
if @options[:debug]
|
34
|
+
port, ip = @connect_from
|
35
|
+
@logger.debug("proxy: unbind connection from #{ip}:#{port}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def receive_data(data)
|
40
|
+
EM.defer {
|
41
|
+
begin
|
42
|
+
endpoint, params = parse_proxy_request(data)
|
43
|
+
key = params_to_key(params)
|
44
|
+
|
45
|
+
if (cache = @memcached.get(key))
|
46
|
+
@logger.debug("proxy: cache hit (length=#{cache.length})") if @options[:debug]
|
47
|
+
send_data(cache)
|
48
|
+
close_connection_after_writing
|
49
|
+
else
|
50
|
+
@logger.debug("proxy: cache miss") if @options[:debug]
|
51
|
+
@backend = EM.connect(endpoint.host, endpoint.port, EC2APIProxy::Backend, self, key)
|
52
|
+
|
53
|
+
if @backend.error?
|
54
|
+
@logger.error("proxy: backend connection error: #{data.inspect}")
|
55
|
+
send_error('ec2-api-proxy-backend-connection-error', 'backend connection error')
|
56
|
+
close_backend_connection
|
57
|
+
close_connection_after_writing
|
58
|
+
else
|
59
|
+
@backend.send_data(data)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
@logger.warn("#{e.class.name}: #{e.message}")
|
64
|
+
send_error(e.class.name, e.message)
|
65
|
+
close_backend_connection
|
66
|
+
close_connection_after_writing
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def parse_proxy_request(data)
|
74
|
+
request_method = nil
|
75
|
+
endpoint = nil
|
76
|
+
|
77
|
+
data.sub!(%r{\A(GET|POST) (https?://[^/]+)}) do
|
78
|
+
request_method = $1
|
79
|
+
endpoint = $2
|
80
|
+
"#{request_method} "
|
81
|
+
end
|
82
|
+
|
83
|
+
endpoint = URI.parse(endpoint)
|
84
|
+
params = nil
|
85
|
+
|
86
|
+
if request_method =~ /GET/i
|
87
|
+
params = CGI.parse(endpoint.query)
|
88
|
+
else
|
89
|
+
params = CGI.parse(data.split(/\r\n\r\n/, 2).last)
|
90
|
+
end
|
91
|
+
|
92
|
+
[endpoint, params]
|
93
|
+
end
|
94
|
+
|
95
|
+
def params_to_key(params)
|
96
|
+
params = params.dup
|
97
|
+
|
98
|
+
%w(
|
99
|
+
SignatureVersion
|
100
|
+
AWSAccessKeyId
|
101
|
+
Timestamp
|
102
|
+
SignatureMethod
|
103
|
+
Version
|
104
|
+
Signature
|
105
|
+
).each {|i| params.delete(i) }
|
106
|
+
|
107
|
+
params.sort.map {|k, v| "#{k}=#{v}" }.join('&')
|
108
|
+
end
|
109
|
+
|
110
|
+
def send_error(code, message)
|
111
|
+
send_data(<<-EOS)
|
112
|
+
<Response>
|
113
|
+
<Errors>
|
114
|
+
<Error>
|
115
|
+
<Code>#{code}</Code>
|
116
|
+
<Message>#{message}</Message>
|
117
|
+
</Error>
|
118
|
+
</Errors>
|
119
|
+
</Response>
|
120
|
+
EOS
|
121
|
+
end
|
122
|
+
|
123
|
+
def close_backend_connection
|
124
|
+
@backend.close_connection if @backend
|
125
|
+
rescue Exception => e
|
126
|
+
@logger.warn("#{e.class.name}: #{e.message}")
|
127
|
+
end
|
128
|
+
end # Proxy
|
129
|
+
end # EC2APIProxy
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'rexec'
|
5
|
+
require 'rexec/daemon'
|
6
|
+
require 'dalli'
|
7
|
+
|
8
|
+
require 'ec2-api-proxy/proxy'
|
9
|
+
|
10
|
+
module RExec; module Daemon; class Base
|
11
|
+
def self.daemon_name; APP_NAME; end
|
12
|
+
end; end; end
|
13
|
+
|
14
|
+
module EC2APIProxy
|
15
|
+
class Server < RExec::Daemon::Base
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def options=(options)
|
19
|
+
@@options = options
|
20
|
+
@@base_directory = options[:working_dir]
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
@@control_options = {
|
25
|
+
:memcached => @@options[:memcached],
|
26
|
+
:expires => @@options[:expires],
|
27
|
+
:compress => @@options[:compress],
|
28
|
+
:debug => @@options[:debug],
|
29
|
+
}
|
30
|
+
|
31
|
+
# start DRb
|
32
|
+
FileUtils.rm_f(@@options[:socket])
|
33
|
+
DRb.start_service("drbunix:#{@@options[:socket]}", @@control_options)
|
34
|
+
File.chmod(0700, @@options[:socket])
|
35
|
+
at_exit { FileUtils.rm_f(@@options[:socket]) }
|
36
|
+
|
37
|
+
EM.epoll
|
38
|
+
EM.threadpool_size = @@options[:threads] if @@options[:threads]
|
39
|
+
|
40
|
+
EM.run {
|
41
|
+
EM.start_server(@@options[:addr], @@options[:port], EC2APIProxy::Proxy,
|
42
|
+
@@control_options)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end # self
|
46
|
+
|
47
|
+
end # Server
|
48
|
+
end # EC2APIProxy
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ec2-api-proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- winebarrel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rexec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.5.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.5.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: dalli
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.6.4
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.6.4
|
62
|
+
description:
|
63
|
+
email: sgwr_dts@yahoo.co.jp
|
64
|
+
executables:
|
65
|
+
- ec2-api-proxy
|
66
|
+
- eapctl
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- README
|
71
|
+
- bin/eapctl
|
72
|
+
- bin/ec2-api-proxy
|
73
|
+
- lib/ec2-api-proxy/backend.rb
|
74
|
+
- lib/ec2-api-proxy/constants.rb
|
75
|
+
- lib/ec2-api-proxy/proxy.rb
|
76
|
+
- lib/ec2-api-proxy/server.rb
|
77
|
+
homepage: https://bitbucket.org/winebarrel/ec2-api-proxy
|
78
|
+
licenses: []
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.8.23
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: ec2-api-proxy is a proxy server for EC2 API.
|
101
|
+
test_files: []
|