distributed_demo 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README +34 -0
- data/lib/array.rb +5 -0
- data/lib/client.rb +64 -0
- data/lib/dist.rb +5 -0
- data/lib/encoding.rb +8 -0
- data/lib/master_app.rb +40 -0
- data/lib/messages.rb +9 -0
- data/lib/server.rb +113 -0
- data/lib/tcp_socket.rb +10 -0
- data/nginx/mime.types +73 -0
- data/nginx/nginx.conf +36 -0
- data/views/master.erb +63 -0
- data/views/status.erb +23 -0
- metadata +15 -2
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Demo for the talk "Distributed Systems with Rack"
|
2
|
+
-------------------------------------------------
|
3
|
+
|
4
|
+
This is the complete code for my Scottish Ruby Conference talk. It demonstrates the idea
|
5
|
+
of having a website generated by multiple distinct and distributed services. For the
|
6
|
+
purposes of the demo, we have a server application which assigns roles to computers that
|
7
|
+
connect. In reality, you'd probably have a legitimate proxy server sit in front of the
|
8
|
+
clusters of nodes...
|
9
|
+
|
10
|
+
In our case though, we keep a list of clients that are connected, and which roles
|
11
|
+
they've been assigned to. When we want to generate a page, we determine which roles are
|
12
|
+
necessary, pick one of the clients from each role at random, and use Nginx to combine
|
13
|
+
the results into a single page.
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
Client Startup
|
18
|
+
--------------
|
19
|
+
|
20
|
+
ruby client.rb 1.2.3.4:9876
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
Server Startup
|
25
|
+
--------------
|
26
|
+
|
27
|
+
nginx -c /path/to/distributed_demo/nginx/nginx.conf
|
28
|
+
ruby server.rb 9876
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
Cheers,
|
33
|
+
|
34
|
+
Tyler
|
data/lib/array.rb
ADDED
data/lib/client.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
$: << './lib'
|
2
|
+
|
3
|
+
require 'dist'
|
4
|
+
require 'encoding'
|
5
|
+
require 'messages'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rack'
|
8
|
+
|
9
|
+
module Dist
|
10
|
+
class Client
|
11
|
+
include Encoding
|
12
|
+
|
13
|
+
def initialize(address)
|
14
|
+
leave_a_will
|
15
|
+
|
16
|
+
host, port = address.split(':')
|
17
|
+
@socket = TCPSocket.new(host, port.to_i)
|
18
|
+
|
19
|
+
@socket << Messages::REQUEST_ROLE
|
20
|
+
|
21
|
+
receive_message Messages::SEND_ROLE_CONFIRM
|
22
|
+
role = receive_string
|
23
|
+
|
24
|
+
puts "Your role: #{role.dump}"
|
25
|
+
|
26
|
+
@socket << Messages::CONFIRM_ROLE
|
27
|
+
send_string @socket, role
|
28
|
+
|
29
|
+
# spin up rack app
|
30
|
+
require "roles/#{role}/app"
|
31
|
+
Thread.new { Rack::Handler::Mongrel.run App.new, :Port => 9501 }
|
32
|
+
|
33
|
+
while true
|
34
|
+
receive_message Messages::REQUEST_HEARTBEAT
|
35
|
+
@socket << Messages::HEARTBEAT
|
36
|
+
end
|
37
|
+
rescue UnexpectedMessage
|
38
|
+
puts 'Disconnected.'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def leave_a_will
|
44
|
+
graceful_death = lambda { @socket.close }
|
45
|
+
%w[INT TERM].each { |code| Signal.trap(code, &graceful_death) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def receive_message(expected_message=nil)
|
49
|
+
message = @socket.recv(1)
|
50
|
+
raise UnexpectedMessage if expected_message && expected_message != message
|
51
|
+
end
|
52
|
+
|
53
|
+
def receive_string
|
54
|
+
length = @socket.recv(2).unpack('n')[0]
|
55
|
+
return '' if !length || length < 1
|
56
|
+
return @socket.recv(length)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if __FILE__ == $0
|
62
|
+
Thread.abort_on_exception = true
|
63
|
+
Dist::Client.new(ARGV[0])
|
64
|
+
end
|
data/lib/dist.rb
ADDED
data/lib/encoding.rb
ADDED
data/lib/master_app.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'array'
|
3
|
+
require 'sinatra/base'
|
4
|
+
|
5
|
+
class MasterApp < Sinatra::Base
|
6
|
+
def initialize(demo_server)
|
7
|
+
@demo_server = demo_server
|
8
|
+
super(*[])
|
9
|
+
end
|
10
|
+
|
11
|
+
get '/disconnect' do
|
12
|
+
@demo_server.disconnect_ip params[:ip]
|
13
|
+
redirect '/status'
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/status' do
|
17
|
+
erb :status
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/debug' do
|
21
|
+
debugger
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/' do
|
25
|
+
@roles = @demo_server.roles.inject({}) { |o,(role,nodes)|
|
26
|
+
node = nil
|
27
|
+
while !node && nodes.size > 0
|
28
|
+
node = nodes.to_a.rand
|
29
|
+
if node.closed?
|
30
|
+
nodes.delete(node)
|
31
|
+
node = nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
o[role] = node && node.remote_ip
|
35
|
+
o
|
36
|
+
}
|
37
|
+
|
38
|
+
erb :master
|
39
|
+
end
|
40
|
+
end
|
data/lib/messages.rb
ADDED
data/lib/server.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
$: << './lib'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'dist'
|
5
|
+
require 'encoding'
|
6
|
+
require 'messages'
|
7
|
+
require 'set'
|
8
|
+
require 'rack'
|
9
|
+
require 'master_app'
|
10
|
+
require 'tcp_socket'
|
11
|
+
|
12
|
+
module Dist
|
13
|
+
class Server
|
14
|
+
include Encoding
|
15
|
+
|
16
|
+
def initialize(port)
|
17
|
+
@server = TCPServer.new(port)
|
18
|
+
@roles = Dir[File.join(File.dirname(__FILE__), 'roles', '*')].map { |p| p.split('/').last }.inject({}) { |o,f| o[f] = Set.new; o }
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :roles
|
22
|
+
|
23
|
+
def disconnect_ip(ip)
|
24
|
+
@roles.each do |role,sockets|
|
25
|
+
sockets.select { |sock| sock.remote_ip == ip }.each do |sock|
|
26
|
+
sockets.delete(sock)
|
27
|
+
sock.close
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def listen
|
33
|
+
puts 'Listening...'
|
34
|
+
|
35
|
+
while (tcp_session = @server.accept)
|
36
|
+
|
37
|
+
remote_ip = tcp_session.remote_ip
|
38
|
+
disconnect_ip remote_ip
|
39
|
+
|
40
|
+
Thread.new do
|
41
|
+
puts "Client connected: #{tcp_session.peeraddr.inspect}"
|
42
|
+
|
43
|
+
role = next_role
|
44
|
+
|
45
|
+
begin
|
46
|
+
receive_message tcp_session, Messages::REQUEST_ROLE
|
47
|
+
|
48
|
+
tcp_session << Messages::SEND_ROLE_CONFIRM
|
49
|
+
send_string tcp_session, role
|
50
|
+
|
51
|
+
receive_message tcp_session, Messages::CONFIRM_ROLE
|
52
|
+
confirmed_role = receive_string(tcp_session)
|
53
|
+
|
54
|
+
if confirmed_role == role
|
55
|
+
puts 'Role confirmation complete. Putting client into production.'
|
56
|
+
@roles[role] << tcp_session
|
57
|
+
else
|
58
|
+
puts 'Role client sent back did not match role server sent... disconnecting client.'
|
59
|
+
tcp_session.close rescue nil
|
60
|
+
end
|
61
|
+
rescue UnexpectedMessage
|
62
|
+
puts 'Unexpected message... disconnecting client.'
|
63
|
+
tcp_session.close rescue nil
|
64
|
+
end
|
65
|
+
|
66
|
+
p @roles
|
67
|
+
|
68
|
+
begin
|
69
|
+
while true
|
70
|
+
sleep 3
|
71
|
+
tcp_session << Messages::REQUEST_HEARTBEAT
|
72
|
+
if tcp_session.ready_to_read?(5)
|
73
|
+
receive_message tcp_session, Messages::HEARTBEAT
|
74
|
+
#puts "#{remote_ip} - #{role}: Heartbeat"
|
75
|
+
else
|
76
|
+
puts "#{remote_ip} - #{role}: Timeout"
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
rescue => e
|
81
|
+
puts "#{remote_ip} - #{role}: #{e.message}"
|
82
|
+
puts "#{remote_ip} - #{role}: Dead"
|
83
|
+
ensure
|
84
|
+
tcp_session.close rescue nil
|
85
|
+
@roles[role].delete(tcp_session)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def next_role
|
92
|
+
@roles.keys.rand.min { |a,b| @roles[a].size <=> @roles[b].size }
|
93
|
+
end
|
94
|
+
|
95
|
+
def receive_message(session, expected_message=nil)
|
96
|
+
message = session.recvfrom(1).first
|
97
|
+
raise UnexpectedMessage if expected_message && expected_message != message
|
98
|
+
end
|
99
|
+
|
100
|
+
def receive_string(session)
|
101
|
+
length = session.recvfrom(2).first.unpack('n')[0]
|
102
|
+
return '' if length < 1
|
103
|
+
return session.recvfrom(length).first
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if __FILE__ == $0
|
109
|
+
Thread.abort_on_exception = true
|
110
|
+
server = Dist::Server.new(ARGV[0].to_i)
|
111
|
+
Thread.new { server.listen }
|
112
|
+
Rack::Handler::Thin.run MasterApp.new(server), :Port => 9505
|
113
|
+
end
|
data/lib/tcp_socket.rb
ADDED
data/nginx/mime.types
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
types {
|
3
|
+
text/html html htm shtml;
|
4
|
+
text/css css;
|
5
|
+
text/xml xml;
|
6
|
+
image/gif gif;
|
7
|
+
image/jpeg jpeg jpg;
|
8
|
+
application/x-javascript js;
|
9
|
+
application/atom+xml atom;
|
10
|
+
application/rss+xml rss;
|
11
|
+
|
12
|
+
text/mathml mml;
|
13
|
+
text/plain txt;
|
14
|
+
text/vnd.sun.j2me.app-descriptor jad;
|
15
|
+
text/vnd.wap.wml wml;
|
16
|
+
text/x-component htc;
|
17
|
+
|
18
|
+
image/png png;
|
19
|
+
image/tiff tif tiff;
|
20
|
+
image/vnd.wap.wbmp wbmp;
|
21
|
+
image/x-icon ico;
|
22
|
+
image/x-jng jng;
|
23
|
+
image/x-ms-bmp bmp;
|
24
|
+
image/svg+xml svg;
|
25
|
+
|
26
|
+
application/java-archive jar war ear;
|
27
|
+
application/mac-binhex40 hqx;
|
28
|
+
application/msword doc;
|
29
|
+
application/pdf pdf;
|
30
|
+
application/postscript ps eps ai;
|
31
|
+
application/rtf rtf;
|
32
|
+
application/vnd.ms-excel xls;
|
33
|
+
application/vnd.ms-powerpoint ppt;
|
34
|
+
application/vnd.wap.wmlc wmlc;
|
35
|
+
application/vnd.wap.xhtml+xml xhtml;
|
36
|
+
application/vnd.google-earth.kml+xml kml;
|
37
|
+
application/vnd.google-earth.kmz kmz;
|
38
|
+
application/x-cocoa cco;
|
39
|
+
application/x-java-archive-diff jardiff;
|
40
|
+
application/x-java-jnlp-file jnlp;
|
41
|
+
application/x-makeself run;
|
42
|
+
application/x-perl pl pm;
|
43
|
+
application/x-pilot prc pdb;
|
44
|
+
application/x-rar-compressed rar;
|
45
|
+
application/x-redhat-package-manager rpm;
|
46
|
+
application/x-sea sea;
|
47
|
+
application/x-shockwave-flash swf;
|
48
|
+
application/x-stuffit sit;
|
49
|
+
application/x-tcl tcl tk;
|
50
|
+
application/x-x509-ca-cert der pem crt;
|
51
|
+
application/x-xpinstall xpi;
|
52
|
+
application/zip zip;
|
53
|
+
|
54
|
+
application/octet-stream bin exe dll;
|
55
|
+
application/octet-stream deb;
|
56
|
+
application/octet-stream dmg;
|
57
|
+
application/octet-stream eot;
|
58
|
+
application/octet-stream iso img;
|
59
|
+
application/octet-stream msi msp msm;
|
60
|
+
|
61
|
+
audio/midi mid midi kar;
|
62
|
+
audio/mpeg mp3;
|
63
|
+
audio/x-realaudio ra;
|
64
|
+
|
65
|
+
video/3gpp 3gpp 3gp;
|
66
|
+
video/mpeg mpeg mpg;
|
67
|
+
video/quicktime mov;
|
68
|
+
video/x-flv flv;
|
69
|
+
video/x-mng mng;
|
70
|
+
video/x-ms-asf asx asf;
|
71
|
+
video/x-ms-wmv wmv;
|
72
|
+
video/x-msvideo avi;
|
73
|
+
}
|
data/nginx/nginx.conf
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
worker_processes 1;
|
2
|
+
|
3
|
+
events {
|
4
|
+
worker_connections 1024;
|
5
|
+
}
|
6
|
+
|
7
|
+
http {
|
8
|
+
include mime.types;
|
9
|
+
default_type text/html;
|
10
|
+
|
11
|
+
sendfile on;
|
12
|
+
keepalive_timeout 65;
|
13
|
+
|
14
|
+
resolver 4.2.2.2;
|
15
|
+
|
16
|
+
error_log "/Users/tyler/Code/dist-demo/logs/error.log" debug;
|
17
|
+
|
18
|
+
server {
|
19
|
+
listen 9510;
|
20
|
+
server_name 127.0.0.1;
|
21
|
+
|
22
|
+
root /Users/tyler/Code/dist-demo;
|
23
|
+
|
24
|
+
location / {
|
25
|
+
ssi on;
|
26
|
+
proxy_pass http://127.0.0.1:9505/;
|
27
|
+
}
|
28
|
+
|
29
|
+
location ~* /remote/(.*)$ {
|
30
|
+
proxy_connect_timeout 2;
|
31
|
+
proxy_read_timeout 2;
|
32
|
+
proxy_send_timeout 1;
|
33
|
+
proxy_pass http://$1/;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
data/views/master.erb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<style type="text/css">
|
4
|
+
body {
|
5
|
+
width: 900px;
|
6
|
+
}
|
7
|
+
|
8
|
+
#content {
|
9
|
+
width: 600px;
|
10
|
+
min-height: 500px;
|
11
|
+
float: left;
|
12
|
+
border: 5px solid blue;
|
13
|
+
}
|
14
|
+
|
15
|
+
#search, #login_box, #new_users {
|
16
|
+
width: 250px;
|
17
|
+
float: right;
|
18
|
+
margin: 10px 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
#search {
|
22
|
+
border: 5px solid orange;
|
23
|
+
}
|
24
|
+
#login_box {
|
25
|
+
border: 5px solid green;
|
26
|
+
}
|
27
|
+
#new_users {
|
28
|
+
border: 5px solid purple;
|
29
|
+
}
|
30
|
+
</style>
|
31
|
+
</head>
|
32
|
+
|
33
|
+
<body>
|
34
|
+
<div id="content">
|
35
|
+
<% if @roles['content'] %>
|
36
|
+
<!--# block name="content" --><!--# endblock -->
|
37
|
+
<!--# include virtual="/remote/<%= @roles['content'] %>:9501" stub="content" -->
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div id="search">
|
42
|
+
<% if @roles['search'] %>
|
43
|
+
<!--# block name="search" --><!--# endblock -->
|
44
|
+
<!--# include virtual="/remote/<%= @roles['search'] %>:9501" stub="search" -->
|
45
|
+
<% end %>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<div id="login_box">
|
49
|
+
<% if @roles['login_box'] %>
|
50
|
+
<!--# block name="login_box" --><!--# endblock -->
|
51
|
+
<!--# include virtual="/remote/<%= @roles['login_box'] %>:9501" stub="login_box" -->
|
52
|
+
<% end %>
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<div id="new_users">
|
56
|
+
<% if @roles['new_users'] %>
|
57
|
+
<!--# block name="new_users" --><!--# endblock -->
|
58
|
+
<!--# include virtual="/remote/<%= @roles['new_users'] %>:9501" stub="new_users" -->
|
59
|
+
<% end %>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
</body>
|
63
|
+
</html>
|
data/views/status.erb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<style type="text/css">
|
4
|
+
</style>
|
5
|
+
</head>
|
6
|
+
|
7
|
+
<body>
|
8
|
+
<h1>Connection Status</h1>
|
9
|
+
|
10
|
+
<% @demo_server.roles.each do |role,clients| %>
|
11
|
+
|
12
|
+
<h2><%= role %></h2>
|
13
|
+
<ul>
|
14
|
+
<% clients.each do |client| %>
|
15
|
+
<li>
|
16
|
+
<%= client.remote_ip %>
|
17
|
+
(<a href="/disconnect?ip=<%= client.remote_ip %>">disconnect</a>)
|
18
|
+
</li>
|
19
|
+
<% end %>
|
20
|
+
</ul>
|
21
|
+
<% end %>
|
22
|
+
</body>
|
23
|
+
</html>
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 1.0.
|
8
|
+
- 5
|
9
|
+
version: 1.0.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Tyler McMullen
|
@@ -65,6 +65,19 @@ extra_rdoc_files: []
|
|
65
65
|
files:
|
66
66
|
- bin/distributed_demo
|
67
67
|
- bin/distributed_demo_server
|
68
|
+
- lib/array.rb
|
69
|
+
- lib/client.rb
|
70
|
+
- lib/dist.rb
|
71
|
+
- lib/encoding.rb
|
72
|
+
- lib/master_app.rb
|
73
|
+
- lib/messages.rb
|
74
|
+
- lib/server.rb
|
75
|
+
- lib/tcp_socket.rb
|
76
|
+
- views/master.erb
|
77
|
+
- views/status.erb
|
78
|
+
- nginx/mime.types
|
79
|
+
- nginx/nginx.conf
|
80
|
+
- README
|
68
81
|
has_rdoc: true
|
69
82
|
homepage:
|
70
83
|
licenses: []
|