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.
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: []