async-websocket 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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