├── Gemfile ├── Gemfile.lock ├── Passengerfile.json ├── README.md ├── config.ru ├── public ├── .gitkeep ├── images │ ├── bg_hr.png │ ├── blacktocat.png │ ├── icon_download.png │ └── sprite_download.png ├── index.html └── stylesheets │ ├── normalize.css │ ├── pygment_trac.css │ └── stylesheet.css └── tmp └── .gitkeep /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'websocket' 3 | gem 'rack' 4 | gem 'passenger', '>= 5.0.25', :require => false 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | passenger (5.0.25) 5 | rack 6 | rake (>= 0.8.1) 7 | rack (1.6.4) 8 | rake (10.4.2) 9 | websocket (1.1.1) 10 | 11 | PLATFORMS 12 | ruby 13 | 14 | DEPENDENCIES 15 | passenger (>= 5.0.25) 16 | rack 17 | websocket 18 | 19 | BUNDLED WITH 20 | 1.10.6 21 | -------------------------------------------------------------------------------- /Passengerfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "unlimited_concurrency_paths": ["/websocket"], 3 | "sticky_sessions": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSockets on Phusion Passenger 2 | 3 | This application demonstrates WebSocket support in [Phusion Passenger](https://www.phusionpassenger.com/). Passenger supports all major modern web technologies, such as WebSockets, entirely out of the box. You don't have to do anything: WebSocket support just works. 4 | 5 | If you like this demo, please [tweet about it](https://twitter.com/share) or [follow us on Twitter](https://twitter.com/phusion_nl). 6 | 7 | More information about Passenger: 8 | 9 | * [Website](https://www.phusionpassenger.com/) 10 | * [Documentation](https://www.phusionpassenger.com/library/) 11 | * [Support](https://www.phusionpassenger.com/support) 12 | * [Source code](https://github.com/phusion/passenger) 13 | * [Community discussion forum](https://groups.google.com/d/forum/phusion-passenger) 14 | * [Issue tracker](https://github.com/phusion/passenger/issues) 15 | 16 | ## Getting started 17 | 18 | Clone this repository, install the gem bundle and start Passenger Standalone. 19 | 20 | git clone https://github.com/phusion/passenger-ruby-websocket-demo.git 21 | cd passenger-ruby-websocket-demo 22 | bundle install 23 | bundle exec passenger start 24 | 25 | Access the demo application at http://0.0.0.0:3000/ and see it in action. 26 | 27 | If you deploy this demo to production, be sure to enable [sticky sessions](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_sticky_sessions) in Passenger. 28 | 29 | ## Compatibility 30 | 31 | * This app uses plain Rack, and thus is framework agnostic. 32 | * WebSockets work on Passenger for Nginx and Passenger Standalone. [Apache is currently not supported](https://github.com/phusion/passenger/issues/1202). 33 | * At least version 5.0.25 of Passenger is required. 34 | * Only the RFC 6455 version of the WebSocket protocol is supported. 35 | 36 | ## Tuning Passenger for WebSockets 37 | 38 | WebSockets work great on both the open source variant of Phusion Passenger, as well as on [Phusion Passenger Enterprise](https://www.phusionpassenger.com/enterprise). But you need to tune a few settings. Please refer to the following places in the Passenger Library for more information: 39 | 40 | * [Tuning for Server Sent Events and WebSockets: Passenger + Nginx](https://www.phusionpassenger.com/library/config/nginx/tuning_sse_and_websockets/) 41 | * [Tuning for Server Sent Events and WebSockets: Passenger + Apache](https://www.phusionpassenger.com/library/config/apache/tuning_sse_and_websockets/) 42 | * [Tuning for Server Sent Events and WebSockets: Passenger Standalone](https://www.phusionpassenger.com/library/config/standalone/tuning_sse_and_websockets/) 43 | 44 | This demo already contains tuning parameters for Passenger Standalone inside Passengerfile.json. 45 | 46 | ## Next steps 47 | 48 | * Using WebSockets on Phusion Passenger? [Tweet about us](https://twitter.com/share), [follow us on Twitter](https://twitter.com/phusion_nl) or [fork us on Github](https://github.com/phusion/passenger). 49 | * Having problems? Please post a message at [the community discussion forum](https://groups.google.com/d/forum/phusion-passenger). 50 | 51 | [](http://www.phusion.nl/) 52 | 53 | Please enjoy Phusion Passenger, a product by [Phusion](http://www.phusion.nl/). :-) 54 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'websocket' 2 | require 'thread' 3 | 4 | def server_handshake(env) 5 | data = "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']} #{env['SERVER_PROTOCOL']}\r\n" 6 | env.each_pair do |key, val| 7 | if key =~ /^HTTP_(.*)/ 8 | name = rack_env_key_to_http_header_name($1) 9 | data << "#{name}: #{val}\r\n" 10 | end 11 | end 12 | data << "\r\n" 13 | 14 | server = WebSocket::Handshake::Server.new 15 | server << data 16 | puts "WebSocket request:" 17 | puts data 18 | puts 19 | puts "WebSocket response:" 20 | puts server 21 | server 22 | end 23 | 24 | def rack_env_key_to_http_header_name(key) 25 | name = key.downcase.gsub('_', '-') 26 | name[0] = name[0].upcase 27 | name.gsub!(/-(.)/) do |chr| 28 | chr.upcase 29 | end 30 | name 31 | end 32 | 33 | def process_input(io, server, input_parser) 34 | done = false 35 | 36 | # Slurp as much input as possible. 37 | begin 38 | while select([io], nil, nil, 0) 39 | input_parser << io.readpartial(1024) 40 | end 41 | rescue EOFError 42 | done = true 43 | end 44 | 45 | # Parse all input into WebSocket frames and process them. 46 | while (frame = input_parser.next) 47 | case frame.type 48 | when :data 49 | send_output(io, server, "Echo: #{frame}\n") 50 | when :close 51 | done = true 52 | end 53 | end 54 | 55 | done 56 | end 57 | 58 | def send_output(io, server, data) 59 | frame = WebSocket::Frame::Outgoing::Server.new( 60 | :version => server.version, 61 | :data => data, 62 | :type => :text) 63 | io.write(frame.to_s) 64 | end 65 | 66 | def serve_websocket(env) 67 | # On every request, hijack the socket using the Rack socket hijacking API 68 | # (http://blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/), 69 | # then operate on the socket directly to send WebSocket messages. 70 | env['rack.hijack'].call 71 | io = env['rack.hijack_io'] 72 | Thread.new do 73 | begin 74 | puts "WebSocket begun" 75 | 76 | # Parse client handshake message and send server handshake response. 77 | server = server_handshake(env) 78 | io.write(server.to_s) 79 | 80 | # Handle input and stream responses. 81 | input_parser = WebSocket::Frame::Incoming::Server.new(:version => server.version) 82 | done = false 83 | while !done 84 | done = process_input(io, server, input_parser) 85 | send_output(io, server, "#{Time.now}\n") 86 | sleep 1 87 | end 88 | rescue Exception => e 89 | STDERR.puts "*** ERROR: #{e} (#{e.class})\n#{e.backtrace.join("\n")}" 90 | ensure 91 | puts "WebSocket finished" 92 | io.close 93 | end 94 | end 95 | end 96 | 97 | def serve_static_file(env) 98 | if env["PATH_INFO"] == "/" 99 | env["PATH_INFO"] = "/index.html" 100 | end 101 | public_dir = File.expand_path(File.dirname(__FILE__)) + "/public" 102 | Rack::File.new(public_dir).call(env) 103 | end 104 | 105 | app = proc do |env| 106 | if env["PATH_INFO"] == "/websocket" 107 | serve_websocket(env) 108 | else 109 | serve_static_file(env) 110 | end 111 | end 112 | 113 | run app 114 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phusion/passenger-ruby-websocket-demo/3f321cf26ea5079aafbf99bb482ddc241591cbb4/public/.gitkeep -------------------------------------------------------------------------------- /public/images/bg_hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phusion/passenger-ruby-websocket-demo/3f321cf26ea5079aafbf99bb482ddc241591cbb4/public/images/bg_hr.png -------------------------------------------------------------------------------- /public/images/blacktocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phusion/passenger-ruby-websocket-demo/3f321cf26ea5079aafbf99bb482ddc241591cbb4/public/images/blacktocat.png -------------------------------------------------------------------------------- /public/images/icon_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phusion/passenger-ruby-websocket-demo/3f321cf26ea5079aafbf99bb482ddc241591cbb4/public/images/icon_download.png -------------------------------------------------------------------------------- /public/images/sprite_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phusion/passenger-ruby-websocket-demo/3f321cf26ea5079aafbf99bb482ddc241591cbb4/public/images/sprite_download.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Phusion Passenger: Ruby WebSocket demo 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | View on GitHub 19 | 20 |

Phusion Passenger

21 |

Ruby WebSocket demo

22 | 23 |
24 | Download this project as a .zip file 25 | Download this project as a tar.gz file 26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 |

Just a simple timestamp server

34 |

In this demo, the server sends the current timestamp over a WebSocket once per second. The server generates raw WebSocket frames and sends them to the client using the Rack socket hijacking API. You can see the received timestamps below.

35 |
36 |

You can also say something to the server, and it'll reply back after a 1 second delay:

37 |
38 | 39 |
40 |
41 | 44 | 45 |
46 |
47 | 48 | 49 | 55 | 56 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address `[hidden]` styling not present in IE 8/9. 48 | * Hide the `template` element in IE, Safari, and Firefox < 22. 49 | */ 50 | 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | 56 | /* ========================================================================== 57 | Base 58 | ========================================================================== */ 59 | 60 | /** 61 | * 1. Set default font family to sans-serif. 62 | * 2. Prevent iOS text size adjust after orientation change, without disabling 63 | * user zoom. 64 | */ 65 | 66 | html { 67 | font-family: sans-serif; /* 1 */ 68 | -ms-text-size-adjust: 100%; /* 2 */ 69 | -webkit-text-size-adjust: 100%; /* 2 */ 70 | } 71 | 72 | /** 73 | * Remove default margin. 74 | */ 75 | 76 | body { 77 | margin: 0; 78 | } 79 | 80 | /* ========================================================================== 81 | Links 82 | ========================================================================== */ 83 | 84 | /** 85 | * Remove the gray background color from active links in IE 10. 86 | */ 87 | 88 | a { 89 | background: transparent; 90 | } 91 | 92 | /** 93 | * Address `outline` inconsistency between Chrome and other browsers. 94 | */ 95 | 96 | a:focus { 97 | outline: thin dotted; 98 | } 99 | 100 | /** 101 | * Improve readability when focused and also mouse hovered in all browsers. 102 | */ 103 | 104 | a:active, 105 | a:hover { 106 | outline: 0; 107 | } 108 | 109 | /* ========================================================================== 110 | Typography 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address variable `h1` font-size and margin within `section` and `article` 115 | * contexts in Firefox 4+, Safari 5, and Chrome. 116 | */ 117 | 118 | h1 { 119 | font-size: 2em; 120 | margin: 0.67em 0; 121 | } 122 | 123 | /** 124 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 125 | */ 126 | 127 | abbr[title] { 128 | border-bottom: 1px dotted; 129 | } 130 | 131 | /** 132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: bold; 138 | } 139 | 140 | /** 141 | * Address styling not present in Safari 5 and Chrome. 142 | */ 143 | 144 | dfn { 145 | font-style: italic; 146 | } 147 | 148 | /** 149 | * Address differences between Firefox and other browsers. 150 | */ 151 | 152 | hr { 153 | -moz-box-sizing: content-box; 154 | box-sizing: content-box; 155 | height: 0; 156 | } 157 | 158 | /** 159 | * Address styling not present in IE 8/9. 160 | */ 161 | 162 | mark { 163 | background: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Correct font family set oddly in Safari 5 and Chrome. 169 | */ 170 | 171 | code, 172 | kbd, 173 | pre, 174 | samp { 175 | font-family: monospace, serif; 176 | font-size: 1em; 177 | } 178 | 179 | /** 180 | * Improve readability of pre-formatted text in all browsers. 181 | */ 182 | 183 | pre { 184 | white-space: pre-wrap; 185 | } 186 | 187 | /** 188 | * Set consistent quote types. 189 | */ 190 | 191 | q { 192 | quotes: "\201C" "\201D" "\2018" "\2019"; 193 | } 194 | 195 | /** 196 | * Address inconsistent and variable font size in all browsers. 197 | */ 198 | 199 | small { 200 | font-size: 80%; 201 | } 202 | 203 | /** 204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 205 | */ 206 | 207 | sub, 208 | sup { 209 | font-size: 75%; 210 | line-height: 0; 211 | position: relative; 212 | vertical-align: baseline; 213 | } 214 | 215 | sup { 216 | top: -0.5em; 217 | } 218 | 219 | sub { 220 | bottom: -0.25em; 221 | } 222 | 223 | /* ========================================================================== 224 | Embedded content 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove border when inside `a` element in IE 8/9. 229 | */ 230 | 231 | img { 232 | border: 0; 233 | } 234 | 235 | /** 236 | * Correct overflow displayed oddly in IE 9. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | Figures 245 | ========================================================================== */ 246 | 247 | /** 248 | * Address margin not present in IE 8/9 and Safari 5. 249 | */ 250 | 251 | figure { 252 | margin: 0; 253 | } 254 | 255 | /* ========================================================================== 256 | Forms 257 | ========================================================================== */ 258 | 259 | /** 260 | * Define consistent border, margin, and padding. 261 | */ 262 | 263 | fieldset { 264 | border: 1px solid #c0c0c0; 265 | margin: 0 2px; 266 | padding: 0.35em 0.625em 0.75em; 267 | } 268 | 269 | /** 270 | * 1. Correct `color` not being inherited in IE 8/9. 271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 272 | */ 273 | 274 | legend { 275 | border: 0; /* 1 */ 276 | padding: 0; /* 2 */ 277 | } 278 | 279 | /** 280 | * 1. Correct font family not being inherited in all browsers. 281 | * 2. Correct font size not being inherited in all browsers. 282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 283 | */ 284 | 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-family: inherit; /* 1 */ 290 | font-size: 100%; /* 2 */ 291 | margin: 0; /* 3 */ 292 | } 293 | 294 | /** 295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 296 | * the UA stylesheet. 297 | */ 298 | 299 | button, 300 | input { 301 | line-height: normal; 302 | } 303 | 304 | /** 305 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 306 | * All other form control elements do not inherit `text-transform` values. 307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 308 | * Correct `select` style inheritance in Firefox 4+ and Opera. 309 | */ 310 | 311 | button, 312 | select { 313 | text-transform: none; 314 | } 315 | 316 | /** 317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 318 | * and `video` controls. 319 | * 2. Correct inability to style clickable `input` types in iOS. 320 | * 3. Improve usability and consistency of cursor style between image-type 321 | * `input` and others. 322 | */ 323 | 324 | button, 325 | html input[type="button"], /* 1 */ 326 | input[type="reset"], 327 | input[type="submit"] { 328 | -webkit-appearance: button; /* 2 */ 329 | cursor: pointer; /* 3 */ 330 | } 331 | 332 | /** 333 | * Re-set default cursor for disabled elements. 334 | */ 335 | 336 | button[disabled], 337 | html input[disabled] { 338 | cursor: default; 339 | } 340 | 341 | /** 342 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 343 | * 2. Remove excess padding in IE 8/9/10. 344 | */ 345 | 346 | input[type="checkbox"], 347 | input[type="radio"] { 348 | box-sizing: border-box; /* 1 */ 349 | padding: 0; /* 2 */ 350 | } 351 | 352 | /** 353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 355 | * (include `-moz` to future-proof). 356 | */ 357 | 358 | input[type="search"] { 359 | -webkit-appearance: textfield; /* 1 */ 360 | -moz-box-sizing: content-box; 361 | -webkit-box-sizing: content-box; /* 2 */ 362 | box-sizing: content-box; 363 | } 364 | 365 | /** 366 | * Remove inner padding and search cancel button in Safari 5 and Chrome 367 | * on OS X. 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Remove inner padding and border in Firefox 4+. 377 | */ 378 | 379 | button::-moz-focus-inner, 380 | input::-moz-focus-inner { 381 | border: 0; 382 | padding: 0; 383 | } 384 | 385 | /** 386 | * 1. Remove default vertical scrollbar in IE 8/9. 387 | * 2. Improve readability and alignment in all browsers. 388 | */ 389 | 390 | textarea { 391 | overflow: auto; /* 1 */ 392 | vertical-align: top; /* 2 */ 393 | } 394 | 395 | /* ========================================================================== 396 | Tables 397 | ========================================================================== */ 398 | 399 | /** 400 | * Remove most spacing between table cells. 401 | */ 402 | 403 | table { 404 | border-collapse: collapse; 405 | border-spacing: 0; 406 | } 407 | -------------------------------------------------------------------------------- /public/stylesheets/pygment_trac.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #f0f3f3; } 3 | .highlight .c { color: #0099FF; font-style: italic } /* Comment */ 4 | .highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */ 5 | .highlight .k { color: #006699; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #555555 } /* Operator */ 7 | .highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #009999 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */ 11 | .highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ 16 | .highlight .go { color: #AAAAAA } /* Generic.Output */ 17 | .highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #99CC66 } /* Generic.Traceback */ 21 | .highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #006699 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */ 27 | .highlight .m { color: #FF6600 } /* Literal.Number */ 28 | .highlight .s { color: #CC3300 } /* Literal.String */ 29 | .highlight .na { color: #330099 } /* Name.Attribute */ 30 | .highlight .nb { color: #336666 } /* Name.Builtin */ 31 | .highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #336600 } /* Name.Constant */ 33 | .highlight .nd { color: #9999FF } /* Name.Decorator */ 34 | .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */ 36 | .highlight .nf { color: #CC00FF } /* Name.Function */ 37 | .highlight .nl { color: #9999FF } /* Name.Label */ 38 | .highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #003333 } /* Name.Variable */ 41 | .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #FF6600 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #FF6600 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #FF6600 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #FF6600 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #CC3300 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #CC3300 } /* Literal.String.Char */ 49 | .highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #CC3300 } /* Literal.String.Double */ 51 | .highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #AA0000 } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #CC3300 } /* Literal.String.Other */ 55 | .highlight .sr { color: #33AAAA } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #CC3300 } /* Literal.String.Single */ 57 | .highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #003333 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #003333 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #003333 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */ 63 | 64 | .type-csharp .highlight .k { color: #0000FF } 65 | .type-csharp .highlight .kt { color: #0000FF } 66 | .type-csharp .highlight .nf { color: #000000; font-weight: normal } 67 | .type-csharp .highlight .nc { color: #2B91AF } 68 | .type-csharp .highlight .nn { color: #000000 } 69 | .type-csharp .highlight .s { color: #A31515 } 70 | .type-csharp .highlight .sc { color: #A31515 } 71 | -------------------------------------------------------------------------------- /public/stylesheets/stylesheet.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | Slate Theme for GitHub Pages 3 | by Jason Costello, @jsncostello 4 | *******************************************************************************/ 5 | 6 | @import url(normalize.css); 7 | @import url(pygment_trac.css); 8 | 9 | /******************************************************************************* 10 | Theme Styles 11 | *******************************************************************************/ 12 | 13 | *, *:before, *:after { 14 | -moz-box-sizing: border-box; 15 | -webkit-box-sizing: border-box; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | color:#373737; 21 | background: #212121; 22 | font-size: 18px; 23 | font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; 24 | line-height: 1.5; 25 | -webkit-font-smoothing: antialiased; 26 | } 27 | 28 | h1, h2, h3, h4, h5, h6 { 29 | margin: 10px 0; 30 | font-weight: 700; 31 | color:#222222; 32 | font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; 33 | letter-spacing: -1px; 34 | } 35 | 36 | h1 { 37 | font-size: 36px; 38 | font-weight: 700; 39 | } 40 | 41 | h2 { 42 | padding-bottom: 10px; 43 | font-size: 32px; 44 | background: url('../images/bg_hr.png') repeat-x bottom; 45 | } 46 | 47 | h3 { 48 | font-size: 24px; 49 | } 50 | 51 | h4 { 52 | font-size: 21px; 53 | } 54 | 55 | h5 { 56 | font-size: 18px; 57 | } 58 | 59 | h6 { 60 | font-size: 16px; 61 | } 62 | 63 | p { 64 | font-size: 18px; 65 | margin: 10px 0 15px 0; 66 | } 67 | 68 | footer p { 69 | color: #f2f2f2; 70 | } 71 | 72 | a { 73 | text-decoration: none; 74 | color: #007edf; 75 | text-shadow: none; 76 | } 77 | 78 | a:hover, a:focus {text-decoration: underline;} 79 | 80 | footer a { 81 | color: #F2F2F2; 82 | text-decoration: underline; 83 | } 84 | 85 | em { 86 | font-style: italic; 87 | } 88 | 89 | strong { 90 | font-weight: bold; 91 | } 92 | 93 | img { 94 | position: relative; 95 | margin: 0 auto; 96 | max-width: 739px; 97 | padding: 5px; 98 | margin: 10px 0 10px 0; 99 | /* 100 | border: 1px solid #ebebeb; 101 | 102 | box-shadow: 0 0 5px #ebebeb; 103 | -webkit-box-shadow: 0 0 5px #ebebeb; 104 | -moz-box-shadow: 0 0 5px #ebebeb; 105 | -o-box-shadow: 0 0 5px #ebebeb; 106 | -ms-box-shadow: 0 0 5px #ebebeb; 107 | */ 108 | } 109 | 110 | p img { 111 | display: inline; 112 | margin: 0; 113 | padding: 0; 114 | vertical-align: middle; 115 | text-align: center; 116 | border: none; 117 | } 118 | 119 | pre, code { 120 | width: 100%; 121 | color: #222; 122 | background-color: #fff; 123 | 124 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; 125 | font-size: 14px; 126 | 127 | border-radius: 2px; 128 | -moz-border-radius: 2px; 129 | -webkit-border-radius: 2px; 130 | 131 | box-shadow: 0 0 10px rgba(0,0,0,.1); 132 | } 133 | 134 | pre { 135 | padding: 10px; 136 | overflow: auto; 137 | } 138 | 139 | code { 140 | padding: 3px; 141 | margin: 0 3px; 142 | } 143 | 144 | pre code { 145 | display: block; 146 | box-shadow: none; 147 | } 148 | 149 | blockquote { 150 | color: #666; 151 | margin: 0 0 20px 2px; 152 | padding-left: 20px; 153 | border-left: 3px solid #bbb; 154 | font-style: italic; 155 | } 156 | 157 | ul, ol, dl { 158 | margin: 0 0 15px 0; 159 | padding-left: 20px; 160 | } 161 | 162 | dl dt { 163 | font-weight: bold; 164 | } 165 | 166 | dl dd { 167 | margin-left: 0; 168 | padding-left: 0; 169 | font-style: italic; 170 | } 171 | 172 | dl p { 173 | padding-left: 20px; 174 | font-style: italic; 175 | } 176 | 177 | hr { 178 | height: 1px; 179 | margin-bottom: 5px; 180 | border: none; 181 | background: url('../images/bg_hr.png') repeat-x center; 182 | } 183 | 184 | table { 185 | border: 1px solid #373737; 186 | margin-bottom: 20px; 187 | text-align: left; 188 | } 189 | 190 | th { 191 | font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; 192 | padding: 10px; 193 | background: #373737; 194 | color: #fff; 195 | } 196 | 197 | td { 198 | padding: 10px; 199 | border: 1px solid #373737; 200 | } 201 | 202 | form { 203 | background: #f2f2f2; 204 | padding: 20px; 205 | } 206 | 207 | /******************************************************************************* 208 | Full-Width Styles 209 | *******************************************************************************/ 210 | 211 | .outer { 212 | width: 100%; 213 | } 214 | 215 | .inner { 216 | position: relative; 217 | max-width: 640px; 218 | padding: 20px 10px; 219 | margin: 0 auto; 220 | } 221 | 222 | #forkme_banner { 223 | display: block; 224 | position: absolute; 225 | top:0; 226 | right: 10px; 227 | z-index: 10; 228 | padding: 10px 50px 10px 10px; 229 | color: #fff; 230 | background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%; 231 | font-weight: 700; 232 | box-shadow: 0 0 10px rgba(0,0,0,.5); 233 | border-bottom-left-radius: 2px; 234 | border-bottom-right-radius: 2px; 235 | } 236 | 237 | #header_wrap { 238 | background: #212121; 239 | } 240 | 241 | #header_wrap .inner { 242 | padding: 50px 10px 30px 10px; 243 | } 244 | 245 | #project_title { 246 | margin: 0; 247 | color: #fff; 248 | font-size: 42px; 249 | font-weight: 700; 250 | text-shadow: #111 0px 0px 10px; 251 | } 252 | 253 | #project_tagline { 254 | color: #fff; 255 | font-size: 24px; 256 | font-weight: 300; 257 | background: none; 258 | text-shadow: #111 0px 0px 10px; 259 | } 260 | 261 | #downloads { 262 | position: absolute; 263 | width: 210px; 264 | z-index: 10; 265 | bottom: -40px; 266 | right: 0; 267 | height: 70px; 268 | background: url('../images/icon_download.png') no-repeat 0% 90%; 269 | } 270 | 271 | .zip_download_link { 272 | display: block; 273 | float: right; 274 | width: 90px; 275 | height:70px; 276 | text-indent: -5000px; 277 | overflow: hidden; 278 | background: url(../images/sprite_download.png) no-repeat bottom left; 279 | } 280 | 281 | .tar_download_link { 282 | display: block; 283 | float: right; 284 | width: 90px; 285 | height:70px; 286 | text-indent: -5000px; 287 | overflow: hidden; 288 | background: url(../images/sprite_download.png) no-repeat bottom right; 289 | margin-left: 10px; 290 | } 291 | 292 | .zip_download_link:hover { 293 | background: url(../images/sprite_download.png) no-repeat top left; 294 | } 295 | 296 | .tar_download_link:hover { 297 | background: url(../images/sprite_download.png) no-repeat top right; 298 | } 299 | 300 | #main_content_wrap { 301 | background: #f2f2f2; 302 | border-top: 1px solid #111; 303 | border-bottom: 1px solid #111; 304 | } 305 | 306 | #main_content { 307 | padding-top: 40px; 308 | } 309 | 310 | #footer_wrap { 311 | background: #212121; 312 | } 313 | 314 | /******************************************************************************* 315 | Small Device Styles 316 | *******************************************************************************/ 317 | 318 | @media screen and (max-width: 480px) { 319 | body { 320 | font-size:14px; 321 | } 322 | 323 | #downloads { 324 | display: none; 325 | } 326 | 327 | .inner { 328 | min-width: 320px; 329 | max-width: 480px; 330 | } 331 | 332 | #project_title { 333 | font-size: 32px; 334 | } 335 | 336 | h1 { 337 | font-size: 28px; 338 | } 339 | 340 | h2 { 341 | font-size: 24px; 342 | } 343 | 344 | h3 { 345 | font-size: 21px; 346 | } 347 | 348 | h4 { 349 | font-size: 18px; 350 | } 351 | 352 | h5 { 353 | font-size: 14px; 354 | } 355 | 356 | h6 { 357 | font-size: 12px; 358 | } 359 | 360 | code, pre { 361 | min-width: 320px; 362 | max-width: 480px; 363 | font-size: 11px; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phusion/passenger-ruby-websocket-demo/3f321cf26ea5079aafbf99bb482ddc241591cbb4/tmp/.gitkeep --------------------------------------------------------------------------------