async-websocket 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +4 -0
  3. data/.travis.yml +5 -5
  4. data/README.md +1 -1
  5. data/async-websocket.gemspec +2 -2
  6. data/examples/chat/client.rb +6 -6
  7. data/examples/middleware/client.rb +45 -0
  8. data/examples/middleware/config.ru +77 -0
  9. data/examples/utopia/.bowerrc +4 -0
  10. data/examples/utopia/.gitignore +9 -0
  11. data/examples/utopia/.rspec +4 -0
  12. data/examples/utopia/Gemfile +35 -0
  13. data/examples/utopia/Guardfile +29 -0
  14. data/examples/utopia/README.md +16 -0
  15. data/examples/utopia/Rakefile +8 -0
  16. data/examples/utopia/config.ru +47 -0
  17. data/examples/utopia/config/README.md +7 -0
  18. data/examples/utopia/config/environment.rb +8 -0
  19. data/examples/utopia/lib/readme.txt +1 -0
  20. data/examples/utopia/pages/_heading.xnode +2 -0
  21. data/examples/utopia/pages/_page.xnode +30 -0
  22. data/examples/utopia/pages/client/client.js +28 -0
  23. data/examples/utopia/pages/client/controller.rb +0 -0
  24. data/examples/utopia/pages/client/index.xnode +8 -0
  25. data/examples/utopia/pages/errors/exception.xnode +5 -0
  26. data/examples/utopia/pages/errors/file-not-found.xnode +5 -0
  27. data/examples/utopia/pages/links.yaml +2 -0
  28. data/examples/utopia/pages/server/controller.rb +21 -0
  29. data/examples/utopia/public/_static/icon.png +0 -0
  30. data/examples/utopia/public/_static/site.css +205 -0
  31. data/examples/utopia/public/_static/utopia-background.svg +1 -0
  32. data/examples/utopia/public/_static/utopia.svg +1 -0
  33. data/examples/utopia/public/readme.txt +1 -0
  34. data/examples/utopia/spec/spec_helper.rb +31 -0
  35. data/examples/utopia/spec/website_context.rb +11 -0
  36. data/examples/utopia/spec/website_spec.rb +56 -0
  37. data/examples/utopia/tasks/bower.rake +45 -0
  38. data/examples/utopia/tasks/deploy.rake +13 -0
  39. data/examples/utopia/tasks/development.rake +34 -0
  40. data/examples/utopia/tasks/environment.rake +17 -0
  41. data/examples/utopia/tasks/log.rake +17 -0
  42. data/examples/utopia/tasks/static.rake +43 -0
  43. data/lib/async/websocket.rb +2 -0
  44. data/lib/async/websocket/connection.rb +11 -1
  45. data/lib/async/websocket/version.rb +1 -1
  46. metadata +51 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ff856912dcea7dfba40abc7ec2c5fd9366aa31c32752c142b1cc692fa8e8237
4
- data.tar.gz: 3d697fbb3fb107ba7cac79978a57fcae7864255f6b133b8fcb9b967d0a150b06
3
+ metadata.gz: c110468d58034b4bbdd08e4d39482bf24cf99207dba5f5fa478251c8751f3dbe
4
+ data.tar.gz: cd992b36fb505345834c51ae31523e35e9058d846bd1b4a85ddf43143eebc17a
5
5
  SHA512:
6
- metadata.gz: 7c01550d7db048683e2f6e16b45b38ae4ac183b730fec333e5a212141d85eb5dcf1cdabf26d596c39d3a321f89acdb9d777a02e450a48c3925b2ffda49603fc2
7
- data.tar.gz: 96b6a20b28600c256baf55ed3b9eeed37f318e8976150bdd29b38a5dc2dbf83371e7e445dd3bd233ce407c1600732e3a2d519cc0255d17f0dfd95dafec7e831d
6
+ metadata.gz: 36c2509873572672d504f0de96785840b52d3993b3cc7febca16d9f08ed85f7e3e73c4995380aea74de3a2d431f4db772ed4c1b4bc268c698fea7ecf7445cf01
7
+ data.tar.gz: d226a93fa0f972789b5e1f9d842fe0ec1dcffefff3b846b2e977555fc29cafa23ffe2c20782f28d39dbdea6d67bccce5fc00120e0829fe0876019f08963c39ba
data/.editorconfig ADDED
@@ -0,0 +1,4 @@
1
+
2
+ [*]
3
+ indent_style = tab
4
+ indent_size = 2
data/.travis.yml CHANGED
@@ -1,15 +1,15 @@
1
1
  language: ruby
2
- sudo: false
3
- dist: trusty
4
2
  cache: bundler
5
3
 
4
+ before_script:
5
+ - gem update --system
6
+ - gem install bundler
7
+
6
8
  matrix:
7
9
  include:
8
- - rvm: 2.0
9
- - rvm: 2.1
10
- - rvm: 2.2
11
10
  - rvm: 2.3
12
11
  - rvm: 2.4
12
+ - rvm: 2.5
13
13
  - rvm: jruby-head
14
14
  env: JRUBY_OPTS="--debug -X+O"
15
15
  - rvm: ruby-head
data/README.md CHANGED
@@ -46,7 +46,7 @@ run lambda {|env|
46
46
  }
47
47
  ```
48
48
 
49
- And [here is a client program](example/client.rb) which can read input from `stdin` and send messages to the server.
49
+ And [here is a client program](examples/chat/client.rb) which can read input from `stdin` and send messages to the server.
50
50
 
51
51
  ## Contributing
52
52
 
@@ -20,9 +20,9 @@ Gem::Specification.new do |spec|
20
20
  spec.add_dependency "async-io"
21
21
 
22
22
  spec.add_development_dependency "async-rspec"
23
-
23
+ spec.add_development_dependency "falcon", "~> 0.15.0"
24
+
24
25
  spec.add_development_dependency "bundler", "~> 1.6"
25
26
  spec.add_development_dependency "rspec", "~> 3.6"
26
27
  spec.add_development_dependency "rake"
27
- spec.add_development_dependency "falcon"
28
28
  end
@@ -5,10 +5,14 @@ require 'async/io/stream'
5
5
  require 'async/http/url_endpoint'
6
6
  require 'async/websocket/client'
7
7
 
8
- URL = ARGV.pop
9
- USER = ARGV.pop
8
+ USER = ARGV.pop || "anonymous"
9
+ URL = ARGV.pop || "ws://localhost:9292"
10
10
 
11
11
  Async::Reactor.run do |task|
12
+ stdin = Async::IO::Stream.new(
13
+ Async::IO::Generic.new($stdin)
14
+ )
15
+
12
16
  endpoint = Async::HTTP::URLEndpoint.parse(URL)
13
17
 
14
18
  endpoint.connect do |socket|
@@ -20,10 +24,6 @@ Async::Reactor.run do |task|
20
24
  })
21
25
 
22
26
  task.async do
23
- stdin = Async::IO::Stream.new(
24
- Async::IO::Generic.new($stdin)
25
- )
26
-
27
27
  puts "Waiting for input..."
28
28
  while line = stdin.read_until("\n")
29
29
  puts "Sending text: #{line}"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async/reactor'
4
+ require 'async/io/stream'
5
+ require 'async/http/url_endpoint'
6
+ require 'async/websocket/client'
7
+
8
+ USER = ARGV.pop || "anonymous"
9
+ URL = ARGV.pop || "ws://localhost:9292"
10
+
11
+ Async::Reactor.run do |task|
12
+ stdin = Async::IO::Stream.new(
13
+ Async::IO::Generic.new($stdin)
14
+ )
15
+
16
+ endpoint = Async::HTTP::URLEndpoint.parse(URL)
17
+
18
+ endpoint.connect do |socket|
19
+ connection = Async::WebSocket::Client.new(socket, URL)
20
+
21
+ connection.send_message({
22
+ user: USER,
23
+ status: "connected",
24
+ })
25
+
26
+ task.async do
27
+ puts "Waiting for input..."
28
+ begin
29
+ while line = stdin.read_until("\n")
30
+ puts "Sending text: #{line}"
31
+ connection.send_message({
32
+ user: USER,
33
+ text: line,
34
+ })
35
+ end
36
+ rescue
37
+ puts "Client error: #{$!}"
38
+ end
39
+ end
40
+
41
+ while message = connection.next_message
42
+ puts "From server: #{message.inspect}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env falcon --verbose serve --concurrency 1 -c
2
+
3
+ require 'async/websocket/server'
4
+
5
+ Async.logger.level = Logger::DEBUG
6
+
7
+ class RackUpgrade
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ if ::WebSocket::Driver.websocket?(env)
14
+ env['rack.upgrade?'] = :websocket
15
+
16
+ response = @app.call(env)
17
+
18
+ if handler = env['rack.upgrade']
19
+ Async::WebSocket::Server.open(env) do |connection|
20
+ begin
21
+ while event = connection.next_event
22
+ if event.is_a? ::WebSocket::Driver::OpenEvent
23
+ handler.on_open(connection) if handler.respond_to? :on_open
24
+ elsif event.is_a? ::WebSocket::Driver::MessageEvent
25
+ handler.on_message(connection, JSON.parse(event.data))
26
+ elsif event.is_a? ::WebSocket::Driver::CloseEvent
27
+ handler.on_close(connection) if handler.respond_to? :on_close
28
+ end
29
+ end
30
+ ensure
31
+ handler.on_shutdown(connection) if handler.respond_to? :on_shutdown
32
+ end
33
+ end
34
+ end
35
+ else
36
+ return @app.call(env)
37
+ end
38
+ end
39
+ end
40
+
41
+ class Chatty
42
+ def initialize
43
+ @connections = Set.new
44
+ end
45
+
46
+ def on_open(connection)
47
+ Async.logger.info(self) {"on_open: #{connection}"}
48
+ @connections << connection
49
+ end
50
+
51
+ def on_message(connection, message)
52
+ Async.logger.info(self) {"on_message: #{connection} -> #{message}"}
53
+
54
+ @connections.each do |connection|
55
+ connection.send_message(message)
56
+ end
57
+ end
58
+
59
+ def on_shutdown(connection)
60
+ Async.logger.info(self) {"on_shutdown: #{connection}"}
61
+ @connections.delete(connection)
62
+
63
+ @connections.each do |connection|
64
+ connection.send_message(message)
65
+ end
66
+ end
67
+ end
68
+
69
+ use RackUpgrade
70
+
71
+ CHATTY = Chatty.new
72
+
73
+ run lambda {|env|
74
+ env['rack.upgrade'] = CHATTY
75
+
76
+ return [200, {}, []]
77
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "directory": "lib/components",
3
+ "public": "public/_components"
4
+ }
@@ -0,0 +1,9 @@
1
+ # Development specific:
2
+ .rspec_status
3
+ .tags*
4
+
5
+ # Temporary data should not be added to the repository:
6
+ tmp/
7
+
8
+ # This file should only ever exist on production, and may contain sensitive information:
9
+ config/environment.yaml
@@ -0,0 +1,4 @@
1
+ --format documentation
2
+ --backtrace
3
+ --warnings
4
+ --require spec_helper
@@ -0,0 +1,35 @@
1
+
2
+ source "https://rubygems.org"
3
+
4
+ gem "utopia", "~> 2.3.0"
5
+ # gem "utopia-gallery"
6
+ # gem "utopia-analytics"
7
+
8
+ gem "rake"
9
+ gem "bundler"
10
+
11
+ gem 'async-websocket'
12
+
13
+ gem "rack-freeze", "~> 1.2"
14
+
15
+ group :development do
16
+ # For `rake server`:
17
+ gem "guard-falcon", require: false
18
+ gem 'guard-rspec', require: false
19
+
20
+ # For `rake console`:
21
+ gem "pry"
22
+ gem "rack-test"
23
+
24
+ # For `rspec` testing:
25
+ gem "rspec"
26
+ gem "simplecov"
27
+
28
+ # For testing:
29
+ gem 'async-rspec'
30
+ end
31
+
32
+ group :production do
33
+ # Used for passenger-config to restart server after deployment:
34
+ gem "passenger"
35
+ end
@@ -0,0 +1,29 @@
1
+
2
+ group :development do
3
+ guard :falcon, port: 9292 do
4
+ watch('Gemfile.lock')
5
+ watch('config.ru')
6
+ watch(%r{^config|lib|pages/.*})
7
+
8
+ notification :off
9
+ end
10
+ end
11
+
12
+ group :test do
13
+ guard :rspec, cmd: 'rspec' do
14
+ # Notifications can get a bit tedious:
15
+ # notification :off
16
+
17
+ # Re-run specs if they are changed:
18
+ watch(%r{^spec/.+_spec\.rb$})
19
+ watch('spec/spec_helper.rb') {'spec'}
20
+
21
+ # Run relevent specs if files in `lib/` or `pages/` are changed:
22
+ watch(%r{^lib/(.+)\.rb$}) {|match| "spec/lib/#{match[1]}_spec.rb" }
23
+ watch(%r{^pages/(.+)\.(rb|xnode)$}) {|match| "spec/pages/#{match[1]}_spec.rb"}
24
+ watch(%r{^pages/(.+)controller\.rb$}) {|match| Dir.glob("spec/pages/#{match[1]}*_spec.rb")}
25
+
26
+ # If any files in pages changes, ensure the website still works:
27
+ watch(%r{^pages/.*}) {'spec/website_spec.rb'}
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # Example WebSocket Chat Server
2
+
3
+ This is a simple chat client/server implementation with specs.
4
+
5
+ ## Starting Development Server
6
+
7
+ To start the development server, simply execute
8
+
9
+ > rake
10
+ Generating transient session key for development...
11
+ 20:57:36 - INFO - Starting Falcon HTTP server on localhost:9292
12
+ 20:57:36 - INFO - Guard::RSpec is running
13
+ 20:57:36 - INFO - Guard is now watching at '...'
14
+ [1] guard(main)>
15
+
16
+ Then browse http://localhost:9292 (or as specified) to see your new site.
@@ -0,0 +1,8 @@
1
+
2
+ require 'pathname'
3
+ SITE_ROOT = Pathname.new(__dir__).realpath
4
+
5
+ # Load all rake tasks:
6
+ import(*Dir.glob('tasks/**/*.rake'))
7
+
8
+ task :default => :development
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rackup
2
+
3
+ require_relative 'config/environment'
4
+
5
+ require 'rack/freeze'
6
+
7
+ if RACK_ENV == :production
8
+ # Handle exceptions in production with a error page and send an email notification:
9
+ use Utopia::Exceptions::Handler
10
+ use Utopia::Exceptions::Mailer
11
+ else
12
+ # We want to propate exceptions up when running tests:
13
+ use Rack::ShowExceptions unless RACK_ENV == :test
14
+
15
+ # Serve the public directory in a similar way to the web server:
16
+ use Utopia::Static, root: 'public'
17
+ end
18
+
19
+ use Rack::Sendfile
20
+
21
+ use Utopia::ContentLength
22
+
23
+ use Utopia::Redirection::Rewrite,
24
+ '/' => '/client/index'
25
+
26
+ use Utopia::Redirection::DirectoryIndex
27
+
28
+ use Utopia::Redirection::Errors,
29
+ 404 => '/errors/file-not-found'
30
+
31
+ use Utopia::Localization,
32
+ :default_locale => 'en',
33
+ :locales => ['en', 'de', 'ja', 'zh']
34
+
35
+ require 'utopia/session'
36
+ use Utopia::Session,
37
+ :expires_after => 3600 * 24,
38
+ :secret => ENV['UTOPIA_SESSION_SECRET']
39
+
40
+ use Utopia::Controller
41
+
42
+ use Utopia::Static
43
+
44
+ # Serve dynamic content
45
+ use Utopia::Content
46
+
47
+ run lambda { |env| [404, {}, []] }
@@ -0,0 +1,7 @@
1
+ # Utopia Config
2
+
3
+ This directory contains `environment.rb` which is used to initialize the running environment for tasks and servers.
4
+
5
+ ## Setting Environment Variables
6
+
7
+ If you wish to set environment variables on a per-deployment basis, you can do so by creating an `config/environment.yaml` and populating it with key-value pairs.
@@ -0,0 +1,8 @@
1
+
2
+ require 'bundler/setup'
3
+ Bundler.setup
4
+
5
+ require 'utopia/setup'
6
+ Utopia.setup
7
+
8
+ RACK_ENV = ENV.fetch('RACK_ENV', :development).to_sym unless defined? RACK_ENV
@@ -0,0 +1 @@
1
+ You can add additional code for your application in this directory, and require it directly from the config.ru.
@@ -0,0 +1,2 @@
1
+ <?r document.attributes[:title] ||= content ?>
2
+ <h1><utopia:content/></h1>
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <?r response.cache! ?>
5
+
6
+ <?r if title = self[:title] ?>
7
+ <title>#{title.gsub(/<.*?>/, "")} - Utopia</title>
8
+ <?r else ?>
9
+ <title>Utopia</title>
10
+ <?r end ?>
11
+
12
+ <base href="#{first.node.uri_path}"/>
13
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
14
+
15
+ <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous" />
16
+
17
+ <link rel="icon" type="image/png" href="/_static/icon.png" />
18
+ <link rel="stylesheet" href="/_static/site.css" type="text/css" media="screen" />
19
+ </head>
20
+
21
+ <body class="#{attributes[:class]}">
22
+ <header>
23
+ <img src="/_static/utopia.svg" />
24
+ </header>
25
+
26
+ <div id="page">
27
+ <utopia:content/>
28
+ </div>
29
+ </body>
30
+ </html>
@@ -0,0 +1,28 @@
1
+
2
+ var url = new URL('/server/connect', window.location.href);
3
+ url.protocol = url.protocol.replace('http', 'ws');
4
+
5
+ console.log("Connecting to server", url);
6
+ var server = new WebSocket(url.href);
7
+ console.log("Connected to", server);
8
+
9
+ server.onopen = function(event) {
10
+ chat.onkeypress = function(event) {
11
+ if (event.keyCode == 13) {
12
+ server.send(JSON.stringify({text: chat.value}));
13
+
14
+ chat.value = "";
15
+ }
16
+ }
17
+ };
18
+
19
+ server.onmessage = function(event) {
20
+ console.log("Got message", event);
21
+
22
+ var message = JSON.parse(event.data);
23
+
24
+ var pre = document.createElement('pre');
25
+ pre.innerText = message.text;
26
+
27
+ response.appendChild(pre);
28
+ };
File without changes
@@ -0,0 +1,8 @@
1
+ <content:page>
2
+ <content:heading>Client</content:heading>
3
+
4
+ <script src="client.js?#{rand}"></script>
5
+ <section id="response" style="border: 1px;"></section>
6
+
7
+ <input id="chat" />
8
+ </content:page>
@@ -0,0 +1,5 @@
1
+ <content:page>
2
+ <content:heading>Exception</content:heading>
3
+
4
+ <p>It seems like something didn't quite work out as expected!</p>
5
+ </content:page>
@@ -0,0 +1,5 @@
1
+ <content:page>
2
+ <content:heading>File Not Found</content:heading>
3
+
4
+ <p>The file you requested is unfortunately not available at this time!</p>
5
+ </content:page>
@@ -0,0 +1,2 @@
1
+ errors:
2
+ display: false
@@ -0,0 +1,21 @@
1
+
2
+ prepend Actions
3
+
4
+ require 'async/websocket/server'
5
+
6
+ $connections = []
7
+
8
+ on 'connect' do |request|
9
+ Async::WebSocket::Server.open(request.env) do |connection|
10
+ $connections << connection
11
+
12
+ while message = connection.next_message
13
+ $connections.each do |connection|
14
+ puts "Server sending message: #{message.inspect}"
15
+ connection.send_message(message)
16
+ end
17
+ end
18
+ end
19
+
20
+ succeed!
21
+ end
@@ -0,0 +1,205 @@
1
+
2
+ html {
3
+ font-family: "PT Sans", Verdana, Helvetica, Arial, sans-serif;
4
+ font-size: 16px;
5
+ }
6
+
7
+ pre {
8
+ tab-size: 2;
9
+ }
10
+
11
+ @media (min-width: 40em) {
12
+ html {
13
+ font-size: 18px;
14
+ }
15
+
16
+ pre {
17
+ tab-size: 4;
18
+ }
19
+ }
20
+
21
+ @media (min-width: 80em) {
22
+ html {
23
+ font-size: 20px;
24
+ }
25
+
26
+ pre {
27
+ tab-size: 4;
28
+ }
29
+ }
30
+
31
+ body {
32
+ padding: 0;
33
+ margin: 0;
34
+
35
+ background-color: #fafafa;
36
+ }
37
+
38
+ body > header {
39
+ margin: 1rem 0 1rem 0;
40
+
41
+ background-color: white;
42
+
43
+ background-image: url(utopia-background.svg);
44
+
45
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
46
+ }
47
+
48
+ body > header img {
49
+ display: block;
50
+ margin: auto;
51
+ height: 4rem;
52
+ }
53
+
54
+ p, ul, ol {
55
+ color: #555;
56
+ }
57
+
58
+ p strong {
59
+ color: #222;
60
+ }
61
+
62
+ h1, h2, h3, h4, h5, h6 {
63
+ margin: 2rem 1rem 1rem 1rem;
64
+ color: #4E8DD9;
65
+ }
66
+
67
+ h1 {
68
+ margin-bottom: 4rem;
69
+ }
70
+
71
+ h2 {
72
+ margin-top: 6rem;
73
+ }
74
+
75
+ img {
76
+ border: none;
77
+ }
78
+
79
+ a {
80
+ color: #33a;
81
+ }
82
+
83
+ a:hover {
84
+ color: #55c;
85
+ }
86
+
87
+ p, ul, ol, dl, h3 {
88
+ margin: 2rem;
89
+ }
90
+
91
+ li {
92
+ margin: 0.2rem;
93
+ }
94
+
95
+ li > ul, li > ol {
96
+ margin: 0;
97
+ }
98
+
99
+ pre {
100
+ overflow: auto;
101
+
102
+ padding: 1rem 2rem;
103
+ font-size: 0.8rem;
104
+
105
+ border-top: 1px solid #ccc;
106
+ border-bottom: 1px solid #ccc;
107
+
108
+ background-color: #eee;
109
+ }
110
+
111
+ h3 {
112
+ border-bottom: 1px solid #ccf;
113
+ }
114
+
115
+ ul {
116
+ margin-bottom: 1rem;
117
+ }
118
+
119
+ h2, h3, h4, h5, h6 {
120
+ font-weight: normal;
121
+ }
122
+
123
+ body.front h1 {
124
+ font-weight: normal;
125
+ font-size: 300%;
126
+ color: #F89432;
127
+
128
+ text-align: center;
129
+ }
130
+
131
+ footer {
132
+ text-align: right;
133
+ margin: 2rem;
134
+ font-size: 0.65rem;
135
+ color: #aaa;
136
+ }
137
+
138
+ nav {
139
+ position: absolute;
140
+ margin: 2.5rem;
141
+ font-size: 0.8rem;
142
+ color: #aaa;
143
+ }
144
+
145
+ section.features {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ justify-content: space-around;
149
+
150
+ margin: 1rem;
151
+ }
152
+
153
+ section.features > div {
154
+ box-sizing: border-box;
155
+
156
+ flex-basis: 20rem;
157
+ flex-grow: 1;
158
+
159
+ color: #171e42;
160
+ margin: 1rem;
161
+ padding: 1rem;
162
+
163
+ padding-left: 3rem;
164
+
165
+ position: relative;
166
+ }
167
+
168
+ section.features > div i {
169
+ position: absolute;
170
+ left: 0rem;
171
+
172
+ font-size: 1.5rem;
173
+ text-align: center;
174
+
175
+ width: 3rem;
176
+ color: #fafafa;
177
+ text-shadow: 0px 0px 1px #000;
178
+ }
179
+
180
+ section.features p {
181
+ margin: 0;
182
+ maring-bottom: 1rem;
183
+ font-size: 80%;
184
+ }
185
+
186
+ section.features h2 {
187
+ margin: 0;
188
+ font-size: 1.1rem;
189
+ padding: 0;
190
+ }
191
+
192
+ form fieldset {
193
+ border: 0;
194
+ }
195
+
196
+ form fieldset textarea {
197
+ box-sizing: border-box;
198
+
199
+ width: 100%;
200
+ height: 10rem;
201
+ }
202
+
203
+ form fieldset.footer {
204
+ text-align: right;
205
+ }
@@ -0,0 +1 @@
1
+ <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 10 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><rect x="0" y="0" width="10" height="56" style="fill:#f79433;"/><rect x="0" y="56" width="10" height="24" style="fill:#4e8dd8;"/></svg>
@@ -0,0 +1 @@
1
+ <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 420 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><rect x="0" y="0" width="420" height="56" style="fill:#f79433;"/><rect x="0" y="56" width="420" height="24" style="fill:#4e8dd8;"/><g><path d="M75.145,70.819c2.37,-3.097 4.173,-6.921 5.111,-11.365c0.91,-4.318 1.498,-9.261 1.498,-14.692l0,-44.762l-62.754,0l0,44.762c0,2.628 0.244,5.333 0.407,8.035c0.168,2.782 0.674,5.515 1.345,8.118c0.68,2.644 1.739,5.173 3.067,7.517c1.363,2.405 3.263,4.526 5.609,6.303c2.319,1.755 5.245,3.163 8.677,4.172c1.617,0.478 3.416,1.093 5.354,1.093l13.856,0c3.071,0 5.797,-1.058 8.131,-2.001c4.042,-1.631 7.305,-4.049 9.699,-7.18Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M151.481,18.701l0,-18.701l-62.754,-0.022l0,18.723l22.246,0l0.02,61.299l17.93,0l-0.02,-61.299l22.578,0Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M229.926,39.999c0,-22.051 -16.979,-39.992 -37.852,-39.992c-20.872,0 -37.851,17.942 -37.851,39.992c0,22.054 16.979,39.994 37.851,39.994c20.873,0 37.852,-17.94 37.852,-39.994Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M269.238,50.909c9.717,0 17.181,-2.066 22.183,-6.395c5.087,-4.399 7.667,-10.942 7.667,-19.575c0,-3.257 -0.393,-5.962 -1.167,-8.476c-0.778,-2.528 -1.883,-4.934 -3.281,-6.814c-1.401,-1.882 -3.098,-3.458 -5.045,-4.703c-1.895,-1.21 -4.003,-2.198 -6.264,-2.943c-2.239,-0.737 -4.64,-1.263 -7.139,-1.56c-2.464,-0.292 -5.016,-0.443 -7.587,-0.443l-29.468,0l0,80l17.93,0l0,-29.091l12.171,0Z" style="fill:#fff;fill-rule:nonzero;"/><rect x="304.879" y="0" width="17.93" height="80" style="fill:#fff;"/><path d="M362.589,0l-29.477,80l75.888,0l-31.247,-80l-15.164,0Z" style="fill:#fff;fill-rule:nonzero;"/></g></g></svg>
@@ -0,0 +1 @@
1
+ This directory is required by Apache/Phusion Passenger and contains static assets that are typically served using sendfile.
@@ -0,0 +1,31 @@
1
+
2
+ if ENV['COVERAGE']
3
+ begin
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ add_filter "/spec/"
8
+ end
9
+
10
+ if ENV['TRAVIS']
11
+ require 'coveralls'
12
+ Coveralls.wear!
13
+ end
14
+ rescue LoadError
15
+ warn "Could not load simplecov: #{$!}"
16
+ end
17
+ end
18
+
19
+ require 'bundler/setup'
20
+ require 'utopia'
21
+
22
+ require 'async/rspec'
23
+
24
+ RSpec.configure do |config|
25
+ # Enable flags like --only-failures and --next-failure
26
+ config.example_status_persistence_file_path = '.rspec_status'
27
+
28
+ config.expect_with :rspec do |c|
29
+ c.syntax = :expect
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+
2
+ require 'rack/test'
3
+
4
+ RSpec.shared_context "website" do
5
+ include Rack::Test::Methods
6
+
7
+ let(:rackup_path) {File.expand_path('../config.ru', __dir__)}
8
+ let(:rackup_directory) {File.dirname(rackup_path)}
9
+
10
+ let(:app) {Rack::Builder.parse_file(rackup_path).first}
11
+ end
@@ -0,0 +1,56 @@
1
+
2
+ require_relative 'website_context'
3
+
4
+ require 'falcon/server'
5
+ require 'falcon/adapters/rack'
6
+
7
+ require 'async/http/url_endpoint'
8
+ require 'async/websocket/client'
9
+
10
+ # Learn about best practice specs from http://betterspecs.org
11
+ RSpec.describe "my website" do
12
+ include_context "website"
13
+
14
+ it "should have an accessible front page" do
15
+ get "/"
16
+
17
+ follow_redirect!
18
+
19
+ expect(last_response.status).to be == 200
20
+ end
21
+
22
+ context "websockets" do
23
+ include_context Async::RSpec::Reactor
24
+
25
+ let(:endpoint) {Async::HTTP::URLEndpoint.parse("http://localhost:9282")}
26
+ let(:server) {Falcon::Server.new(Falcon::Adapters::Rack.new(app), endpoint)}
27
+
28
+ let(:hello_message) do
29
+ {
30
+ "user" => "test",
31
+ "text" => "Hello World",
32
+ }
33
+ end
34
+
35
+ let!(:server_task) do
36
+ server_task = reactor.async do
37
+ server.run
38
+ end
39
+ end
40
+
41
+ after(:each) do
42
+ server_task.stop
43
+ end
44
+
45
+ it "can connect to server" do
46
+ endpoint.connect do |socket|
47
+ connection = Async::WebSocket::Client.new(socket, "ws://localhost/server/connect")
48
+
49
+ connection.send_message(hello_message)
50
+
51
+ message = connection.next_message
52
+ expect(message).to be == hello_message
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,45 @@
1
+
2
+ namespace :bower do
3
+ desc 'Load the .bowerrc file and setup the environment for other tasks.'
4
+ task :bowerrc do
5
+ require 'json'
6
+
7
+ bowerrc_path = SITE_ROOT + ".bowerrc"
8
+ bowerrc = JSON.load(File.read(bowerrc_path))
9
+
10
+ @bower_package_root = SITE_ROOT + bowerrc['directory']
11
+ @bower_install_root = SITE_ROOT + bowerrc['public']
12
+ @bower_install_method = (bowerrc['install'] || :copy).to_sym
13
+ end
14
+
15
+ desc 'Update the bower packages and link into the public directory.'
16
+ task :update => :bowerrc do
17
+ require 'fileutils'
18
+ require 'utopia/path'
19
+
20
+ #sh %W{bower update}
21
+
22
+ @bower_package_root.children.select(&:directory?).collect(&:basename).each do |package_directory|
23
+ install_path = @bower_install_root + package_directory
24
+ package_path = @bower_package_root + package_directory
25
+ dist_path = package_path + 'dist'
26
+
27
+ FileUtils::Verbose.rm_rf install_path
28
+ FileUtils::Verbose.mkpath(install_path.dirname)
29
+
30
+ # If a package has a dist directory, we only symlink that... otherwise we have to do the entire package, and hope that bower's ignore was setup correctly:
31
+ if File.exist? dist_path
32
+ link_path = Utopia::Path.shortest_path(dist_path, install_path)
33
+ else
34
+ link_path = Utopia::Path.shortest_path(package_path, install_path)
35
+ end
36
+
37
+ if @bower_install_method == :symlink
38
+ # This is useful for some
39
+ FileUtils::Verbose.ln_s link_path, install_path
40
+ else
41
+ FileUtils::Verbose.cp_r File.expand_path(link_path, install_path), install_path
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+
2
+ desc 'Run by git post-update hook when deployed to a web server'
3
+ task :deploy do
4
+ # This task is typiclly run after the site is updated but before the server is restarted.
5
+ end
6
+
7
+ desc 'Restart the application server'
8
+ task :restart do
9
+ # This task is run after the deployment task above.
10
+ if passenger_config = `which passenger-config`.chomp!
11
+ sh(passenger_config, 'restart-app', '--ignore-passenger-not-running', SITE_ROOT.to_s)
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test) do |task|
5
+ task.rspec_opts = %w{--require simplecov} if ENV['COVERAGE']
6
+ end
7
+
8
+ task :coverage do
9
+ ENV['COVERAGE'] = 'y'
10
+ end
11
+
12
+ desc 'Start the development server.'
13
+ task :server => :environment do
14
+ exec('guard', '-g', 'development')
15
+ end
16
+
17
+ desc 'Start the development environment which includes web server and tests.'
18
+ task :development => :environment do
19
+ exec('guard', '-g', 'development,test')
20
+ end
21
+
22
+ desc 'Start an interactive console for your web application'
23
+ task :console => :environment do
24
+ require 'pry'
25
+ require 'rack/test'
26
+
27
+ include Rack::Test::Methods
28
+
29
+ def app
30
+ @app ||= Rack::Builder.parse_file(SITE_ROOT + 'config.ru').first
31
+ end
32
+
33
+ Pry.start
34
+ end
@@ -0,0 +1,17 @@
1
+
2
+ desc 'Set up the environment for running your web application'
3
+ task :environment do |task|
4
+ require SITE_ROOT + 'config/environment'
5
+
6
+ # We ensure this is part of the shell environment so if other commands are invoked they will work correctly.
7
+ ENV['RACK_ENV'] = RACK_ENV.to_s if defined?(RACK_ENV)
8
+ ENV['DATABASE_ENV'] = DATABASE_ENV.to_s if defined?(DATABASE_ENV)
9
+
10
+ # This generates a consistent session secret if one was not already provided:
11
+ if ENV['UTOPIA_SESSION_SECRET'].nil?
12
+ require 'securerandom'
13
+
14
+ warn 'Generating transient session key for development...'
15
+ ENV['UTOPIA_SESSION_SECRET'] = SecureRandom.hex(32)
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+
2
+ task :log do
3
+ require 'utopia/logger'
4
+ LOGGER = Utopia::Logger.new
5
+ end
6
+
7
+ namespace :log do
8
+ desc "Increase verbosity of logger to info."
9
+ task :info => :log do
10
+ LOGGER.level = Logger::INFO
11
+ end
12
+
13
+ desc "Increase verbosity of global debug."
14
+ task :debug => :log do
15
+ LOGGER.level = Logger::DEBUG
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+
2
+ namespace :static do
3
+ task :static_environment do
4
+ RACK_ENV ||= :static
5
+ DATABASE_ENV ||= :production
6
+ SERVER_PORT ||= 9291
7
+ end
8
+
9
+ desc "Generate a static copy of the site."
10
+ task :generate => [:static_environment, :environment] do
11
+ require 'falcon/server'
12
+ require 'async/io'
13
+ require 'async/container'
14
+
15
+ config_path = SITE_ROOT + 'config.ru'
16
+ container_class = Async::Container::Threaded
17
+
18
+ app, options = Rack::Builder.parse_file(config_path.to_s)
19
+
20
+ container = container_class.new(concurrency: 2) do
21
+ server = Falcon::Server.new(app, [
22
+ Async::IO::Endpoint.parse("tcp://localhost:#{SERVER_PORT}", reuse_port: true)
23
+ ])
24
+
25
+ server.run
26
+ end
27
+
28
+ output_path = ENV.fetch('OUTPUT_PATH') {SITE_ROOT + 'static'}
29
+
30
+ # Delete any existing stuff:
31
+ FileUtils.rm_rf(output_path)
32
+
33
+ # Copy all public assets:
34
+ Dir.glob(SITE_ROOT + 'public/*').each do |path|
35
+ FileUtils.cp_r(path, output_path)
36
+ end
37
+
38
+ # Generate HTML pages:
39
+ system("wget", "--mirror", "--recursive", "--continue", "--convert-links", "--adjust-extension", "--no-host-directories", "--directory-prefix", output_path.to_s, "http://localhost:#{SERVER_PORT}")
40
+
41
+ container.stop
42
+ end
43
+ end
@@ -21,3 +21,5 @@
21
21
  require_relative 'websocket/version'
22
22
  require_relative 'websocket/server'
23
23
  require_relative 'websocket/client'
24
+
25
+ require 'async/io'
@@ -21,10 +21,14 @@
21
21
  require 'websocket/driver'
22
22
  require 'json'
23
23
 
24
+ require 'async/io/stream'
25
+
24
26
  module Async
25
27
  module WebSocket
26
28
  # This is a basic synchronous websocket client:
27
29
  class Connection
30
+ BLOCK_SIZE = Async::IO::Stream::BLOCK_SIZE
31
+
28
32
  EVENTS = [:open, :message, :close]
29
33
 
30
34
  def initialize(socket, driver)
@@ -50,8 +54,10 @@ module Async
50
54
  attr :url
51
55
 
52
56
  def next_event
57
+ @socket.flush
58
+
53
59
  while @queue.empty?
54
- data = @socket.read(1024)
60
+ data = @socket.readpartial(BLOCK_SIZE)
55
61
 
56
62
  if data and !data.empty?
57
63
  @driver.parse(data)
@@ -75,6 +81,10 @@ module Async
75
81
  end
76
82
  end
77
83
 
84
+ def send_text(text)
85
+ @driver.text(text)
86
+ end
87
+
78
88
  def send_message(message)
79
89
  @driver.text(JSON.dump(message))
80
90
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module WebSocket
23
- VERSION = "0.4.1"
23
+ VERSION = "0.5.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-20 00:00:00.000000000 Z
11
+ date: 2018-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -53,49 +53,49 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: bundler
56
+ name: falcon
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.6'
61
+ version: 0.15.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.6'
68
+ version: 0.15.0
69
69
  - !ruby/object:Gem::Dependency
70
- name: rspec
70
+ name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '3.6'
75
+ version: '1.6'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '3.6'
82
+ version: '1.6'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '3.6'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '3.6'
97
97
  - !ruby/object:Gem::Dependency
98
- name: falcon
98
+ name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -115,6 +115,7 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - ".editorconfig"
118
119
  - ".gitignore"
119
120
  - ".rspec"
120
121
  - ".travis.yml"
@@ -124,6 +125,42 @@ files:
124
125
  - async-websocket.gemspec
125
126
  - examples/chat/client.rb
126
127
  - examples/chat/config.ru
128
+ - examples/middleware/client.rb
129
+ - examples/middleware/config.ru
130
+ - examples/utopia/.bowerrc
131
+ - examples/utopia/.gitignore
132
+ - examples/utopia/.rspec
133
+ - examples/utopia/Gemfile
134
+ - examples/utopia/Guardfile
135
+ - examples/utopia/README.md
136
+ - examples/utopia/Rakefile
137
+ - examples/utopia/config.ru
138
+ - examples/utopia/config/README.md
139
+ - examples/utopia/config/environment.rb
140
+ - examples/utopia/lib/readme.txt
141
+ - examples/utopia/pages/_heading.xnode
142
+ - examples/utopia/pages/_page.xnode
143
+ - examples/utopia/pages/client/client.js
144
+ - examples/utopia/pages/client/controller.rb
145
+ - examples/utopia/pages/client/index.xnode
146
+ - examples/utopia/pages/errors/exception.xnode
147
+ - examples/utopia/pages/errors/file-not-found.xnode
148
+ - examples/utopia/pages/links.yaml
149
+ - examples/utopia/pages/server/controller.rb
150
+ - examples/utopia/public/_static/icon.png
151
+ - examples/utopia/public/_static/site.css
152
+ - examples/utopia/public/_static/utopia-background.svg
153
+ - examples/utopia/public/_static/utopia.svg
154
+ - examples/utopia/public/readme.txt
155
+ - examples/utopia/spec/spec_helper.rb
156
+ - examples/utopia/spec/website_context.rb
157
+ - examples/utopia/spec/website_spec.rb
158
+ - examples/utopia/tasks/bower.rake
159
+ - examples/utopia/tasks/deploy.rake
160
+ - examples/utopia/tasks/development.rake
161
+ - examples/utopia/tasks/environment.rake
162
+ - examples/utopia/tasks/log.rake
163
+ - examples/utopia/tasks/static.rake
127
164
  - lib/async/websocket.rb
128
165
  - lib/async/websocket/client.rb
129
166
  - lib/async/websocket/connection.rb