distributed_demo 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +34 -0
- data/bin/distributed_demo_client +7 -0
- data/bin/distributed_demo_server +8 -0
- data/lib/array.rb +5 -0
- data/lib/client.rb +61 -0
- data/lib/dist.rb +5 -0
- data/lib/encoding.rb +8 -0
- data/lib/master_app.rb +33 -0
- data/lib/messages.rb +9 -0
- data/lib/server.rb +134 -0
- data/lib/tcp_socket.rb +12 -0
- data/lib/views/master.erb +125 -0
- data/lib/views/status.erb +23 -0
- data/nginx/mime.types +73 -0
- data/nginx/nginx.conf +36 -0
- data/roles/content/app.rb +11 -0
- data/roles/content/view.erb +10 -0
- data/roles/old_content/app.rb +11 -0
- data/roles/old_content/view.erb +21 -0
- data/roles/popular/app.rb +11 -0
- data/roles/popular/view.erb +19 -0
- data/roles/recent_comments/app.rb +11 -0
- data/roles/recent_comments/view.erb +16 -0
- data/roles/recent_posts/app.rb +11 -0
- data/roles/recent_posts/view.erb +19 -0
- metadata +80 -0
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,61 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..')
|
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, rack_port)
|
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
|
+
send_string @socket, rack_port
|
29
|
+
|
30
|
+
# spin up rack app
|
31
|
+
require "roles/#{role}/app"
|
32
|
+
Thread.new { Rack::Handler::Mongrel.run App.new, :Port => rack_port.to_i }
|
33
|
+
|
34
|
+
while true
|
35
|
+
receive_message Messages::REQUEST_HEARTBEAT
|
36
|
+
@socket << Messages::HEARTBEAT
|
37
|
+
end
|
38
|
+
rescue UnexpectedMessage
|
39
|
+
puts 'Disconnected.'
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def leave_a_will
|
45
|
+
graceful_death = lambda { @socket.close }
|
46
|
+
%w[INT TERM].each { |code| Signal.trap(code, &graceful_death) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def receive_message(expected_message=nil)
|
50
|
+
message = @socket.recv(1)
|
51
|
+
raise UnexpectedMessage if expected_message && expected_message != message
|
52
|
+
end
|
53
|
+
|
54
|
+
def receive_string
|
55
|
+
length = @socket.recv(2).unpack('n')[0]
|
56
|
+
return '' if !length || length < 1
|
57
|
+
return @socket.recv(length)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/lib/dist.rb
ADDED
data/lib/encoding.rb
ADDED
data/lib/master_app.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'array'
|
3
|
+
require 'ruby-debug'
|
4
|
+
require 'sinatra/base'
|
5
|
+
|
6
|
+
class MasterApp < Sinatra::Base
|
7
|
+
def initialize(demo_server)
|
8
|
+
@demo_server = demo_server
|
9
|
+
end
|
10
|
+
|
11
|
+
set :views, File.join(File.dirname(__FILE__), 'views')
|
12
|
+
|
13
|
+
get '/disconnect' do
|
14
|
+
@demo_server.disconnect_ip params[:ip], params[:port]
|
15
|
+
redirect '/status'
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/status' do
|
19
|
+
erb :status
|
20
|
+
end
|
21
|
+
|
22
|
+
get '/debug' do
|
23
|
+
debugger
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/' do
|
27
|
+
@roles = @demo_server.pick_servers
|
28
|
+
|
29
|
+
halt(503) unless @roles['content']
|
30
|
+
|
31
|
+
erb :master
|
32
|
+
end
|
33
|
+
end
|
data/lib/messages.rb
ADDED
data/lib/server.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..')
|
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 'ruby-debug'
|
11
|
+
require 'tcp_socket'
|
12
|
+
|
13
|
+
module Dist
|
14
|
+
class Server
|
15
|
+
include Encoding
|
16
|
+
|
17
|
+
def initialize(port)
|
18
|
+
@server = TCPServer.new(port)
|
19
|
+
@roles = Dir[File.join(File.dirname(__FILE__), '..', 'roles', '*')].map { |p| p.split('/').last }.inject({}) { |o,f| o[f] = Set.new; o }
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :roles
|
23
|
+
|
24
|
+
def pick_servers
|
25
|
+
roles.inject({}) do |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.remote_ip}:#{node.rack_port}" if node
|
35
|
+
o
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def disconnect_ip(ip,port)
|
40
|
+
@roles.each do |role,sockets|
|
41
|
+
sockets.select { |sock|
|
42
|
+
begin
|
43
|
+
sock.remote_ip == ip && sock.rack_port.to_i == port.to_i
|
44
|
+
rescue IOError
|
45
|
+
true
|
46
|
+
end
|
47
|
+
}.each do |sock|
|
48
|
+
sockets.delete(sock)
|
49
|
+
sock.close rescue nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def listen
|
55
|
+
puts 'Listening...'
|
56
|
+
|
57
|
+
while (tcp_session = @server.accept)
|
58
|
+
|
59
|
+
remote_ip = tcp_session.remote_ip
|
60
|
+
|
61
|
+
Thread.new do
|
62
|
+
puts "Client connected: #{tcp_session.peeraddr.inspect}"
|
63
|
+
|
64
|
+
role = next_role
|
65
|
+
|
66
|
+
begin
|
67
|
+
receive_message tcp_session, Messages::REQUEST_ROLE
|
68
|
+
|
69
|
+
tcp_session << Messages::SEND_ROLE_CONFIRM
|
70
|
+
send_string tcp_session, role
|
71
|
+
|
72
|
+
receive_message tcp_session, Messages::CONFIRM_ROLE
|
73
|
+
confirmed_role = receive_string(tcp_session)
|
74
|
+
remote_port = receive_string(tcp_session)
|
75
|
+
|
76
|
+
disconnect_ip remote_ip, remote_port
|
77
|
+
|
78
|
+
tcp_session.rack_port = remote_port
|
79
|
+
|
80
|
+
if confirmed_role == role
|
81
|
+
puts 'Role confirmation complete. Putting client into production.'
|
82
|
+
@roles[role] << tcp_session
|
83
|
+
else
|
84
|
+
puts 'Role client sent back did not match role server sent... disconnecting client.'
|
85
|
+
tcp_session.close rescue nil
|
86
|
+
end
|
87
|
+
rescue UnexpectedMessage
|
88
|
+
puts 'Unexpected message... disconnecting client.'
|
89
|
+
tcp_session.close rescue nil
|
90
|
+
end
|
91
|
+
|
92
|
+
p @roles
|
93
|
+
|
94
|
+
begin
|
95
|
+
while true
|
96
|
+
tcp_session << Messages::REQUEST_HEARTBEAT
|
97
|
+
if tcp_session.ready_to_read?(10)
|
98
|
+
receive_message tcp_session, Messages::HEARTBEAT
|
99
|
+
else
|
100
|
+
puts "#{remote_ip} - #{role}: Timeout"
|
101
|
+
break
|
102
|
+
end
|
103
|
+
sleep 15
|
104
|
+
end
|
105
|
+
rescue => e
|
106
|
+
puts "#{remote_ip} - #{role}: #{e.message}"
|
107
|
+
puts "#{remote_ip} - #{role}: Dead"
|
108
|
+
ensure
|
109
|
+
tcp_session.close rescue nil
|
110
|
+
@roles[role].delete(tcp_session)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def next_role
|
117
|
+
sorted_roles = @roles.sort { |(k1,v1),(k2,v2)| v1.size <=> v2.size }
|
118
|
+
min_size = sorted_roles[0][1].size
|
119
|
+
sorted_roles.select { |_,socks| socks.size == min_size }.map { |role,_| role }.rand
|
120
|
+
end
|
121
|
+
|
122
|
+
def receive_message(session, expected_message=nil)
|
123
|
+
message = session.recvfrom(1).first
|
124
|
+
raise UnexpectedMessage if expected_message && expected_message != message
|
125
|
+
end
|
126
|
+
|
127
|
+
def receive_string(session)
|
128
|
+
length = session.recvfrom(2).first.unpack('n')[0]
|
129
|
+
return '' if length < 1
|
130
|
+
return session.recvfrom(length).first
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
data/lib/tcp_socket.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<style type="text/css">
|
4
|
+
body {
|
5
|
+
width: 900px;
|
6
|
+
margin: 0 auto;
|
7
|
+
}
|
8
|
+
|
9
|
+
h1 {
|
10
|
+
margin: 20px 0 25px;
|
11
|
+
}
|
12
|
+
|
13
|
+
ul#menu {
|
14
|
+
padding: 0;
|
15
|
+
margin: 0;
|
16
|
+
overflow: hidden;
|
17
|
+
}
|
18
|
+
|
19
|
+
ul#menu li {
|
20
|
+
float: left;
|
21
|
+
width: 100px;
|
22
|
+
font: 15px/15px Helvetica;
|
23
|
+
color: #35D;
|
24
|
+
text-decoration: underline;
|
25
|
+
text-align: left;
|
26
|
+
}
|
27
|
+
|
28
|
+
h2 {
|
29
|
+
margin-bottom: 5px;
|
30
|
+
}
|
31
|
+
|
32
|
+
#content span.date {
|
33
|
+
font: 12px/15px Helvetica;
|
34
|
+
}
|
35
|
+
|
36
|
+
#content span.author {
|
37
|
+
font: 13px/15px Helvetica;
|
38
|
+
color: #333;
|
39
|
+
}
|
40
|
+
|
41
|
+
#content {
|
42
|
+
margin-top: 20px;
|
43
|
+
width: 600px;
|
44
|
+
float: left;
|
45
|
+
}
|
46
|
+
|
47
|
+
#content p {
|
48
|
+
line-height: 20px;
|
49
|
+
}
|
50
|
+
|
51
|
+
#content div.post {
|
52
|
+
margin-bottom: 50px;
|
53
|
+
}
|
54
|
+
|
55
|
+
#sidebar {
|
56
|
+
float: right;
|
57
|
+
margin-top: 45px;
|
58
|
+
width: 250px;
|
59
|
+
}
|
60
|
+
|
61
|
+
#sidebar > div {
|
62
|
+
margin-bottom: 20px;
|
63
|
+
}
|
64
|
+
|
65
|
+
#sidebar h3 {
|
66
|
+
margin: 0 0 10px;;
|
67
|
+
}
|
68
|
+
|
69
|
+
#sidebar div.post {
|
70
|
+
margin-bottom: 5px;
|
71
|
+
}
|
72
|
+
#sidebar div.post span.date {
|
73
|
+
display: block;
|
74
|
+
font: 12px/16px Helvetica;
|
75
|
+
}
|
76
|
+
#sidebar div.post span.views {
|
77
|
+
display: block;
|
78
|
+
font: bold 12px/16px Helvetica;
|
79
|
+
}
|
80
|
+
|
81
|
+
#recent_comments p {
|
82
|
+
margin: 0 0 10px;
|
83
|
+
font: 12px/16px Helvetica;
|
84
|
+
}
|
85
|
+
</style>
|
86
|
+
</head>
|
87
|
+
|
88
|
+
<body>
|
89
|
+
<h1>Some Technical Blog</h1>
|
90
|
+
|
91
|
+
<ul id="menu">
|
92
|
+
<li>Home</li>
|
93
|
+
<li>Explore</li>
|
94
|
+
<li>Something</li>
|
95
|
+
</ul>
|
96
|
+
|
97
|
+
<div id="content">
|
98
|
+
<% if @roles['content'] %>
|
99
|
+
<!--# include virtual="/remote/<%= @roles['content'] %>" -->
|
100
|
+
<% end %>
|
101
|
+
|
102
|
+
<% if @roles['old_content'] %>
|
103
|
+
<!--# block name="old_content" --><!--# endblock -->
|
104
|
+
<!--# include virtual="/remote/<%= @roles['old_content'] %>" stub="old_content" -->
|
105
|
+
<% end %>
|
106
|
+
</div>
|
107
|
+
|
108
|
+
<div id="sidebar">
|
109
|
+
<% if @roles['popular'] %>
|
110
|
+
<!--# block name="popular" --><!--# endblock -->
|
111
|
+
<!--# include virtual="/remote/<%= @roles['popular'] %>" stub="popular" -->
|
112
|
+
<% end %>
|
113
|
+
|
114
|
+
<% if @roles['recent_posts'] %>
|
115
|
+
<!--# block name="recent_posts" --><!--# endblock -->
|
116
|
+
<!--# include virtual="/remote/<%= @roles['recent_posts'] %>" stub="recent_posts" -->
|
117
|
+
<% end %>
|
118
|
+
|
119
|
+
<% if @roles['recent_comments'] %>
|
120
|
+
<!--# block name="recent_comments" --><!--# endblock -->
|
121
|
+
<!--# include virtual="/remote/<%= @roles['recent_comments'] %>" stub="recent_comments" -->
|
122
|
+
<% end %>
|
123
|
+
</div>
|
124
|
+
</body>
|
125
|
+
</html>
|
@@ -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 %>:<%= client.rack_port %>
|
17
|
+
(<a href="/disconnect?ip=<%= client.remote_ip %>&port=<%= client.rack_port %>">disconnect</a>)
|
18
|
+
</li>
|
19
|
+
<% end %>
|
20
|
+
</ul>
|
21
|
+
<% end %>
|
22
|
+
</body>
|
23
|
+
</html>
|
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
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<div class="post">
|
2
|
+
<h2>A Recent Post</h2>
|
3
|
+
<span class="date">March 24, 2010</span>
|
4
|
+
|
5
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lobortis libero et mi mollis bibendum. Pellentesque id enim vestibulum quam fermentum lacinia imperdiet eu sapien. Aenean eget nisl risus. Vivamus consequat ultricies sollicitudin. Nullam a nibh viverra odio suscipit mattis. Mauris tempus bibendum condimentum. Pellentesque non dui ac lacus euismod sodales et et nibh. Suspendisse purus urna, interdum eu placerat at, tristique id nulla. Aliquam metus orci, facilisis et accumsan vitae, imperdiet quis diam. Phasellus urna erat, dignissim sed varius vitae, tincidunt ac nisl. Morbi a sem eu sapien rhoncus rhoncus. Morbi aliquet est a nisl interdum ut molestie tellus pellentesque. Nunc eu nulla metus, at eleifend libero. Aenean ut odio eget felis sollicitudin aliquet at dapibus dolor. Suspendisse consectetur ipsum a enim rhoncus vehicula. In eleifend congue tortor id eleifend.</p>
|
6
|
+
|
7
|
+
<p>Quisque nec magna tortor, at eleifend lacus. Sed vel tortor leo. Sed et urna quis nunc tristique condimentum ac sed leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras varius scelerisque ligula, sit amet adipiscing mi gravida non. Pellentesque a pharetra risus. Nunc ut nunc nec tortor pharetra tristique vel nec arcu. Pellentesque sit amet eros sapien, eget ullamcorper nibh. Etiam sit amet dui at velit eleifend pellentesque vel at ligula. Curabitur dictum accumsan vulputate. Duis nibh lorem, tincidunt nec volutpat vitae, tincidunt sit amet libero. Cras tempor nisi non mi eleifend tristique. Vestibulum eu dolor sed dui iaculis tincidunt ut id mauris. Fusce accumsan nunc vel lectus tincidunt at auctor arcu volutpat. Mauris et aliquet purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam ac lectus eget lacus gravida rhoncus id id justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In in urna erat, id rutrum mauris.</p>
|
8
|
+
|
9
|
+
<span class="author">Tyler McMullen</span>
|
10
|
+
</div>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<div class="post">
|
2
|
+
<h2>An Older Writing</h2>
|
3
|
+
<span class="date">March 15, 2010</span>
|
4
|
+
|
5
|
+
<p>Quisque nec magna tortor, at eleifend lacus. Sed vel tortor leo. Sed et urna quis nunc tristique condimentum ac sed leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras varius scelerisque ligula, sit amet adipiscing mi gravida non. Pellentesque a pharetra risus. Nunc ut nunc nec tortor pharetra tristique vel nec arcu. Pellentesque sit amet eros sapien, eget ullamcorper nibh. Etiam sit amet dui at velit eleifend pellentesque vel at ligula. Curabitur dictum accumsan vulputate. Duis nibh lorem, tincidunt nec volutpat vitae, tincidunt sit amet libero. Cras tempor nisi non mi eleifend tristique. Vestibulum eu dolor sed dui iaculis tincidunt ut id mauris. Fusce accumsan nunc vel lectus tincidunt at auctor arcu volutpat. Mauris et aliquet purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam ac lectus eget lacus gravida rhoncus id id justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In in urna erat, id rutrum mauris.</p>
|
6
|
+
|
7
|
+
<p>Suspendisse mi libero, accumsan at porta in, mollis id eros. Etiam quis tristique lectus. Ut a diam sit amet eros rhoncus mollis id eu purus. Phasellus elit justo, feugiat a scelerisque aliquam, ultrices dapibus quam. Nam sagittis convallis nunc, ut scelerisque mauris bibendum quis. Phasellus auctor sem eleifend urna accumsan nec aliquam magna posuere. Duis turpis magna, accumsan sit amet mollis quis, vehicula sed est. Donec luctus egestas dui. Proin vulputate mauris et mi hendrerit porttitor. Etiam nec magna metus. Pellentesque faucibus tellus nec justo fringilla quis volutpat lectus malesuada. Suspendisse potenti. Nulla sed est quis mauris imperdiet dignissim vel non augue.</p>
|
8
|
+
|
9
|
+
<span class="author">Tyler McMullen</span>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class="post">
|
13
|
+
<h2>An Even Older Post</h2>
|
14
|
+
<span class="date">March 9, 2010</span>
|
15
|
+
|
16
|
+
<p>Suspendisse mi libero, accumsan at porta in, mollis id eros. Etiam quis tristique lectus. Ut a diam sit amet eros rhoncus mollis id eu purus. Phasellus elit justo, feugiat a scelerisque aliquam, ultrices dapibus quam. Nam sagittis convallis nunc, ut scelerisque mauris bibendum quis. Phasellus auctor sem eleifend urna accumsan nec aliquam magna posuere. Duis turpis magna, accumsan sit amet mollis quis, vehicula sed est. Donec luctus egestas dui. Proin vulputate mauris et mi hendrerit porttitor. Etiam nec magna metus. Pellentesque faucibus tellus nec justo fringilla quis volutpat lectus malesuada. Suspendisse potenti. Nulla sed est quis mauris imperdiet dignissim vel non augue.</p>
|
17
|
+
|
18
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lobortis libero et mi mollis bibendum. Pellentesque id enim vestibulum quam fermentum lacinia imperdiet eu sapien. Aenean eget nisl risus. Vivamus consequat ultricies sollicitudin. Nullam a nibh viverra odio suscipit mattis. Mauris tempus bibendum condimentum. Pellentesque non dui ac lacus euismod sodales et et nibh. Suspendisse purus urna, interdum eu placerat at, tristique id nulla. Aliquam metus orci, facilisis et accumsan vitae, imperdiet quis diam. Phasellus urna erat, dignissim sed varius vitae, tincidunt ac nisl. Morbi a sem eu sapien rhoncus rhoncus. Morbi aliquet est a nisl interdum ut molestie tellus pellentesque. Nunc eu nulla metus, at eleifend libero. Aenean ut odio eget felis sollicitudin aliquet at dapibus dolor. Suspendisse consectetur ipsum a enim rhoncus vehicula. In eleifend congue tortor id eleifend.</p>
|
19
|
+
|
20
|
+
<span class="author">Tyler McMullen</span>
|
21
|
+
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<div id="popular">
|
2
|
+
<h3>Popular Posts</h3>
|
3
|
+
|
4
|
+
<div class="post">
|
5
|
+
<a href="#" class="name">Something Extremely Controversial</a>
|
6
|
+
<span class="date">10 months ago</span>
|
7
|
+
<span class="views">12,029 views</span>
|
8
|
+
</div>
|
9
|
+
<div class="post">
|
10
|
+
<a href="#" class="name">Obscure Language vs. Popular Language</a>
|
11
|
+
<span class="date">10 months ago</span>
|
12
|
+
<span class="views">10,399 views</span>
|
13
|
+
</div>
|
14
|
+
<div class="post">
|
15
|
+
<a href="#" class="name">Why is Ruby cooler than your mom?</a>
|
16
|
+
<span class="date">10 months ago</span>
|
17
|
+
<span class="views">9,523 views</span>
|
18
|
+
</div>
|
19
|
+
</div>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div id="recent_comments">
|
2
|
+
<h3>Recent Comments</h3>
|
3
|
+
|
4
|
+
<div class="comment">
|
5
|
+
<a href="#" class="name">Bob the Jerk</a>
|
6
|
+
<p>This article makes no sense and you smell kinda like ...</p>
|
7
|
+
</div>
|
8
|
+
<div class="comment">
|
9
|
+
<a href="#" class="name">Jill the Apologist</a>
|
10
|
+
<p>Well, maybe Java isn't such a bad language if you're extremely ...</p>
|
11
|
+
</div>
|
12
|
+
<div class="comment">
|
13
|
+
<a href="#" class="name">Tim the Fanboy</a>
|
14
|
+
<p>OMG how coold any1 evar have used any other langiage, like, omg ...</p>
|
15
|
+
</div>
|
16
|
+
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<div id="recent_posts">
|
2
|
+
<h3>Recent Posts</h3>
|
3
|
+
|
4
|
+
<div class="post">
|
5
|
+
<a href="#" class="name">Are Java programmers masochists?</a>
|
6
|
+
<span class="date">1 week ago</span>
|
7
|
+
<span class="views">2029 views</span>
|
8
|
+
</div>
|
9
|
+
<div class="post">
|
10
|
+
<a href="#" class="name">Whatever happened to Ada?</a>
|
11
|
+
<span class="date">2 weeks ago</span>
|
12
|
+
<span class="views">103 views</span>
|
13
|
+
</div>
|
14
|
+
<div class="post">
|
15
|
+
<a href="#" class="name">Should Matz get the Nobel prize?</a>
|
16
|
+
<span class="date">1 month ago</span>
|
17
|
+
<span class="views">523 views</span>
|
18
|
+
</div>
|
19
|
+
</div>
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: distributed_demo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.0"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler McMullen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-03-26 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: "Demo for my talk at the Scottish Ruby Conference: Distributed Systems with Rack."
|
17
|
+
email:
|
18
|
+
executables:
|
19
|
+
- distributed_demo_client
|
20
|
+
- distributed_demo_server
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- lib/array.rb
|
27
|
+
- lib/client.rb
|
28
|
+
- lib/dist.rb
|
29
|
+
- lib/encoding.rb
|
30
|
+
- lib/master_app.rb
|
31
|
+
- lib/messages.rb
|
32
|
+
- lib/server.rb
|
33
|
+
- lib/tcp_socket.rb
|
34
|
+
- roles/content/view.erb
|
35
|
+
- roles/old_content/view.erb
|
36
|
+
- roles/popular/view.erb
|
37
|
+
- roles/recent_comments/view.erb
|
38
|
+
- roles/recent_posts/view.erb
|
39
|
+
- roles/content/app.rb
|
40
|
+
- roles/old_content/app.rb
|
41
|
+
- roles/popular/app.rb
|
42
|
+
- roles/recent_comments/app.rb
|
43
|
+
- roles/recent_posts/app.rb
|
44
|
+
- nginx/mime.types
|
45
|
+
- nginx/nginx.conf
|
46
|
+
- bin/distributed_demo_client
|
47
|
+
- bin/distributed_demo_server
|
48
|
+
- lib/views/master.erb
|
49
|
+
- lib/views/status.erb
|
50
|
+
- README
|
51
|
+
has_rdoc: false
|
52
|
+
homepage:
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Distributed systems demo
|
79
|
+
test_files: []
|
80
|
+
|