distributed_demo 1.0.4 → 1.0.5
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 +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: []
|