distributed_demo 1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'client'
4
+
5
+ Thread.abort_on_exception = true
6
+ Dist::Client.new(ARGV[0], ARGV[1] || '9501')
7
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'server'
4
+
5
+ Thread.abort_on_exception = true
6
+ server = Dist::Server.new(ARGV[0].to_i)
7
+ Thread.new { server.listen }
8
+ Rack::Handler::Thin.run MasterApp.new(server), :Port => 9505
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def rand
3
+ self[Kernel.rand * self.size]
4
+ end
5
+ end
@@ -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
+
@@ -0,0 +1,5 @@
1
+ require 'socket'
2
+
3
+ module Dist
4
+ class UnexpectedMessage < StandardError ; end
5
+ end
@@ -0,0 +1,8 @@
1
+ module Dist
2
+ module Encoding
3
+ def send_string(out, string)
4
+ out << [string.size].pack('n')
5
+ out << string
6
+ end
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ module Dist
2
+ module Messages
3
+ REQUEST_ROLE = 1.chr
4
+ SEND_ROLE_CONFIRM = 2.chr
5
+ CONFIRM_ROLE = 3.chr
6
+ REQUEST_HEARTBEAT = 4.chr
7
+ HEARTBEAT = 5.chr
8
+ end
9
+ end
@@ -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
+
@@ -0,0 +1,12 @@
1
+ class TCPSocket
2
+ attr_accessor :rack_port
3
+
4
+ def remote_ip
5
+ peeraddr.last.split(':').last
6
+ end
7
+
8
+ def ready_to_read?(timeout=5)
9
+ r,_,__ = IO.select([self], nil, nil, timeout)
10
+ return r.first == self
11
+ end
12
+ end
@@ -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>
@@ -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
+ }
@@ -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,11 @@
1
+ require 'erb'
2
+
3
+ class App
4
+ def initialize
5
+ @view = ERB.new(File.read(File.join(File.dirname(__FILE__), 'view.erb')))
6
+ end
7
+
8
+ def call(env)
9
+ return [200, { 'Content-Type' => 'text/html' }, [@view.result(binding)]]
10
+ end
11
+ end
@@ -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,11 @@
1
+ require 'erb'
2
+
3
+ class App
4
+ def initialize
5
+ @view = ERB.new(File.read(File.join(File.dirname(__FILE__), 'view.erb')))
6
+ end
7
+
8
+ def call(env)
9
+ return [200, { 'Content-Type' => 'text/html' }, [@view.result(binding)]]
10
+ end
11
+ end
@@ -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,11 @@
1
+ require 'erb'
2
+
3
+ class App
4
+ def initialize
5
+ @view = ERB.new(File.read(File.join(File.dirname(__FILE__), 'view.erb')))
6
+ end
7
+
8
+ def call(env)
9
+ return [200, { 'Content-Type' => 'text/html' }, [@view.result(binding)]]
10
+ end
11
+ end
@@ -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,11 @@
1
+ require 'erb'
2
+
3
+ class App
4
+ def initialize
5
+ @view = ERB.new(File.read(File.join(File.dirname(__FILE__), 'view.erb')))
6
+ end
7
+
8
+ def call(env)
9
+ return [200, { 'Content-Type' => 'text/html' }, [@view.result(binding)]]
10
+ end
11
+ end
@@ -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,11 @@
1
+ require 'erb'
2
+
3
+ class App
4
+ def initialize
5
+ @view = ERB.new(File.read(File.join(File.dirname(__FILE__), 'view.erb')))
6
+ end
7
+
8
+ def call(env)
9
+ return [200, { 'Content-Type' => 'text/html' }, [@view.result(binding)]]
10
+ end
11
+ end
@@ -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
+