velociraptor 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.md +14 -0
- data/bin/velociraptor +205 -0
- metadata +80 -0
data/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
= Velociraptor
|
2
|
+
|
3
|
+
$ velociraptor start &
|
4
|
+
$ curl localhost:3001
|
5
|
+
{"backends": "/backends"}
|
6
|
+
$ curl localhost:3001/backends
|
7
|
+
[]
|
8
|
+
$ echo 'run(lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello, world!\n"]] })' > config.ru
|
9
|
+
$ thin -p 5000 start &
|
10
|
+
$ curl -X PUT -d '{"host": "localhost", "port": "5000"}' localhost:3001/backends/web.0
|
11
|
+
{"host": "localhost", "port": "5000", "url": "/backends/web.0"}
|
12
|
+
$ curl localhost:3000
|
13
|
+
Hello, world!
|
14
|
+
|
data/bin/velociraptor
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'enumerator'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'json'
|
6
|
+
require 'sinatra/base'
|
7
|
+
require 'thor'
|
8
|
+
|
9
|
+
module ProxyConnection
|
10
|
+
def initialize client, header
|
11
|
+
@client, @header = client, header
|
12
|
+
end
|
13
|
+
def post_init
|
14
|
+
EM::enable_proxy self, @client
|
15
|
+
end
|
16
|
+
def connection_completed
|
17
|
+
send_data @header
|
18
|
+
end
|
19
|
+
def proxy_target_unbound
|
20
|
+
close_connection
|
21
|
+
end
|
22
|
+
def on_unbind &b
|
23
|
+
@on_unbind = b
|
24
|
+
end
|
25
|
+
def unbind
|
26
|
+
@client.close_connection_after_writing
|
27
|
+
@on_unbind.call if @on_unbind
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Backend
|
32
|
+
attr_accessor :id, :host, :port
|
33
|
+
attr_writer :busy
|
34
|
+
def busy?
|
35
|
+
@busy
|
36
|
+
end
|
37
|
+
def initialize(id, host, port)
|
38
|
+
self.id = id
|
39
|
+
self.host = host
|
40
|
+
self.port = port
|
41
|
+
self.busy = false
|
42
|
+
end
|
43
|
+
def proxy client, header
|
44
|
+
self.busy = true
|
45
|
+
EM.connect(self.host, self.port, ProxyConnection, client, header) do |c|
|
46
|
+
c.on_unbind &method(:ready!)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
def ready!
|
50
|
+
self.busy = false
|
51
|
+
@on_ready.call(self) if @on_ready
|
52
|
+
end
|
53
|
+
def on_ready &b
|
54
|
+
@on_ready = b
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RoundRobinStrategy
|
59
|
+
def initialize(list)
|
60
|
+
@list = list
|
61
|
+
@i = 0
|
62
|
+
end
|
63
|
+
def next
|
64
|
+
enum = Enumerator.new do |y|
|
65
|
+
start = @i
|
66
|
+
while @i < @list.length
|
67
|
+
y.yield @list[@i]
|
68
|
+
@i += 1
|
69
|
+
end
|
70
|
+
@i = 0
|
71
|
+
while @i < start and @i < @list.length
|
72
|
+
y.yield @list[@i]
|
73
|
+
@i += 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
yield(enum).tap { @i += 1}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class BackendManager
|
81
|
+
class <<self
|
82
|
+
attr_accessor :backends, :strategy, :waiting
|
83
|
+
def init
|
84
|
+
self.backends = []
|
85
|
+
self.strategy = RoundRobinStrategy.new(self.backends)
|
86
|
+
self.waiting = []
|
87
|
+
end
|
88
|
+
def add backend
|
89
|
+
self.backends.push backend
|
90
|
+
backend.on_ready &method(:ready)
|
91
|
+
ready backend
|
92
|
+
end
|
93
|
+
def ready backend
|
94
|
+
if (b = self.waiting.shift)
|
95
|
+
b.call backend
|
96
|
+
end
|
97
|
+
end
|
98
|
+
def find id
|
99
|
+
self.backends.find { |b| b.id == id }
|
100
|
+
end
|
101
|
+
def remove id
|
102
|
+
self.backends.delete_if { |b| b.id == id }
|
103
|
+
end
|
104
|
+
def next(&b)
|
105
|
+
backend = self.strategy.next do |enum|
|
106
|
+
enum.find { |backend| not backend.busy? }
|
107
|
+
end
|
108
|
+
backend ? b.call(backend) : self.waiting.push(b)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module ProxyServer
|
114
|
+
def receive_data(data)
|
115
|
+
(@header ||= "") << data
|
116
|
+
if @header =~ /\r\n\r\n/ # all http headers received
|
117
|
+
BackendManager.next do |backend|
|
118
|
+
backend.proxy self, @header
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Admin < Sinatra::Base
|
125
|
+
set :run, false
|
126
|
+
set :logging, true
|
127
|
+
before { content_type :json }
|
128
|
+
|
129
|
+
module BackendToHash
|
130
|
+
def to_hash
|
131
|
+
{
|
132
|
+
"host" => self.host,
|
133
|
+
"url" => "/backends/" + self.id,
|
134
|
+
"port" => self.port
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
get '/' do
|
140
|
+
{ "backends" => "/backends" }.to_json
|
141
|
+
end
|
142
|
+
get '/backends' do
|
143
|
+
BackendManager.backends.map do |b|
|
144
|
+
b.extend(BackendToHash).to_hash
|
145
|
+
end.to_json
|
146
|
+
end
|
147
|
+
put '/backends/:id' do
|
148
|
+
request.body.rewind
|
149
|
+
data = JSON.parse request.body.read
|
150
|
+
id = params[:id]
|
151
|
+
host = data['host']
|
152
|
+
port = data['port'] || 80
|
153
|
+
|
154
|
+
if (b = BackendManager.find id)
|
155
|
+
b.host = host
|
156
|
+
b.port = port
|
157
|
+
else
|
158
|
+
b = Backend.new id, host, port
|
159
|
+
BackendManager.add b
|
160
|
+
end
|
161
|
+
b.extend(BackendToHash).to_hash.to_json
|
162
|
+
end
|
163
|
+
delete '/backends/:id' do
|
164
|
+
BackendManager.remove params[:id]
|
165
|
+
202
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Printer
|
170
|
+
def self.out id, msg
|
171
|
+
puts "#{Time.now.strftime '%H:%M:%S'} #{id}#{' ' * (9 - id.size)} | #{msg}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
VERSION = "0.1.0"
|
176
|
+
|
177
|
+
class CLI < Thor
|
178
|
+
|
179
|
+
desc "start [PORT] [ADMIN_PORT]", "Start the proxy server on PORT(default 3000), with the admin server on ADMIN_PORT(default PORT+1)"
|
180
|
+
|
181
|
+
def start(*args)
|
182
|
+
port = args.shift || 3000
|
183
|
+
admin_port = args.shift || port + 1
|
184
|
+
|
185
|
+
BackendManager.init
|
186
|
+
EM.run {
|
187
|
+
trap("INT") { EM.stop }
|
188
|
+
|
189
|
+
Printer.out "system", "proxy running on 127.0.0.1:#{port}"
|
190
|
+
EM.start_server("127.0.0.1", port, ProxyServer)
|
191
|
+
|
192
|
+
Printer.out "system", "admin running on 0.0.0.0:#{admin_port}"
|
193
|
+
Admin.run!({:host => "0.0.0.0", :port => admin_port})
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
def help(*args)
|
198
|
+
puts "Velociraptor #{VERSION}"
|
199
|
+
puts
|
200
|
+
super
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
CLI.start
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: velociraptor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Maltese
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &70300733791240 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70300733791240
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sinatra
|
27
|
+
requirement: &70300733790580 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70300733790580
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: thor
|
38
|
+
requirement: &70300733789680 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70300733789680
|
47
|
+
description: ''
|
48
|
+
email: michael.maltese@pomona.edu
|
49
|
+
executables:
|
50
|
+
- velociraptor
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- bin/velociraptor
|
55
|
+
- README.md
|
56
|
+
homepage: http://github.com/mikemaltese/velociraptor
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.11
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Managed proxy server
|
80
|
+
test_files: []
|