velociraptor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +14 -0
  2. data/bin/velociraptor +205 -0
  3. metadata +80 -0
@@ -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
+
@@ -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: []