kinetic_sdk 0.1.1
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.
- checksums.yaml +7 -0
- data/GettingStarted.md +64 -0
- data/README.md +383 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/gems/kontena-websocket-client-0.1.1/Gemfile +4 -0
- data/gems/kontena-websocket-client-0.1.1/LICENSE +190 -0
- data/gems/kontena-websocket-client-0.1.1/README.md +138 -0
- data/gems/kontena-websocket-client-0.1.1/Rakefile +6 -0
- data/gems/kontena-websocket-client-0.1.1/benchmark/benchmark-client.rb +45 -0
- data/gems/kontena-websocket-client-0.1.1/benchmark/benchmark-em.rb +66 -0
- data/gems/kontena-websocket-client-0.1.1/benchmark/benchmark.rb +161 -0
- data/gems/kontena-websocket-client-0.1.1/benchmark/benchmark.sh +17 -0
- data/gems/kontena-websocket-client-0.1.1/benchmark/websocket-echo-server.go +207 -0
- data/gems/kontena-websocket-client-0.1.1/examples/websocket-echo-client.rb +80 -0
- data/gems/kontena-websocket-client-0.1.1/kontena-websocket-client.gemspec +24 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena/websocket/client/connection.rb +119 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena/websocket/client/version.rb +13 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena/websocket/client.rb +848 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena/websocket/error.rb +81 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena/websocket/logging.rb +55 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena/websocket/openssl_patch.rb +10 -0
- data/gems/kontena-websocket-client-0.1.1/lib/kontena-websocket-client.rb +15 -0
- data/gems/mime-types-3.1/Code-of-Conduct.rdoc +74 -0
- data/gems/mime-types-3.1/Contributing.rdoc +130 -0
- data/gems/mime-types-3.1/History.rdoc +658 -0
- data/gems/mime-types-3.1/Licence.rdoc +25 -0
- data/gems/mime-types-3.1/Manifest.txt +31 -0
- data/gems/mime-types-3.1/README.rdoc +202 -0
- data/gems/mime-types-3.1/Rakefile +254 -0
- data/gems/mime-types-3.1/lib/mime/type/columnar.rb +55 -0
- data/gems/mime-types-3.1/lib/mime/type.rb +573 -0
- data/gems/mime-types-3.1/lib/mime/types/_columnar.rb +135 -0
- data/gems/mime-types-3.1/lib/mime/types/cache.rb +56 -0
- data/gems/mime-types-3.1/lib/mime/types/columnar.rb +1 -0
- data/gems/mime-types-3.1/lib/mime/types/container.rb +30 -0
- data/gems/mime-types-3.1/lib/mime/types/deprecations.rb +32 -0
- data/gems/mime-types-3.1/lib/mime/types/full.rb +17 -0
- data/gems/mime-types-3.1/lib/mime/types/loader.rb +148 -0
- data/gems/mime-types-3.1/lib/mime/types/logger.rb +37 -0
- data/gems/mime-types-3.1/lib/mime/types/registry.rb +81 -0
- data/gems/mime-types-3.1/lib/mime/types.rb +228 -0
- data/gems/mime-types-3.1/lib/mime-types.rb +1 -0
- data/gems/mime-types-3.1/test/bad-fixtures/malformed +9 -0
- data/gems/mime-types-3.1/test/fixture/json.json +1 -0
- data/gems/mime-types-3.1/test/fixture/old-data +9 -0
- data/gems/mime-types-3.1/test/fixture/yaml.yaml +55 -0
- data/gems/mime-types-3.1/test/minitest_helper.rb +13 -0
- data/gems/mime-types-3.1/test/test_mime_type.rb +603 -0
- data/gems/mime-types-3.1/test/test_mime_types.rb +161 -0
- data/gems/mime-types-3.1/test/test_mime_types_cache.rb +109 -0
- data/gems/mime-types-3.1/test/test_mime_types_class.rb +155 -0
- data/gems/mime-types-3.1/test/test_mime_types_lazy.rb +43 -0
- data/gems/mime-types-3.1/test/test_mime_types_loader.rb +32 -0
- data/gems/mime-types-data-3.2016.0521/Code-of-Conduct.md +75 -0
- data/gems/mime-types-data-3.2016.0521/Contributing.md +157 -0
- data/gems/mime-types-data-3.2016.0521/History.md +351 -0
- data/gems/mime-types-data-3.2016.0521/Licence.md +25 -0
- data/gems/mime-types-data-3.2016.0521/Manifest.txt +18 -0
- data/gems/mime-types-data-3.2016.0521/README.md +61 -0
- data/gems/mime-types-data-3.2016.0521/Rakefile +80 -0
- data/gems/mime-types-data-3.2016.0521/data/mime-types.json +1 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.content_type.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.docs.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.encoding.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.flags.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.friendly.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.pext.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.use_instead.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/data/mime.xrefs.column +1964 -0
- data/gems/mime-types-data-3.2016.0521/lib/mime/types/data.rb +21 -0
- data/gems/mime-types-data-3.2016.0521/lib/mime-types-data.rb +3 -0
- data/gems/multipart-post-2.0.0/Gemfile +14 -0
- data/gems/multipart-post-2.0.0/History.txt +60 -0
- data/gems/multipart-post-2.0.0/Manifest.txt +9 -0
- data/gems/multipart-post-2.0.0/README.md +77 -0
- data/gems/multipart-post-2.0.0/Rakefile +9 -0
- data/gems/multipart-post-2.0.0/lib/composite_io.rb +108 -0
- data/gems/multipart-post-2.0.0/lib/multipart_post.rb +9 -0
- data/gems/multipart-post-2.0.0/lib/multipartable.rb +29 -0
- data/gems/multipart-post-2.0.0/lib/net/http/post/multipart.rb +27 -0
- data/gems/multipart-post-2.0.0/lib/parts.rb +96 -0
- data/gems/multipart-post-2.0.0/multipart-post.gemspec +22 -0
- data/gems/multipart-post-2.0.0/test/multibyte.txt +1 -0
- data/gems/multipart-post-2.0.0/test/net/http/post/test_multipart.rb +110 -0
- data/gems/multipart-post-2.0.0/test/test_composite_io.rb +115 -0
- data/gems/multipart-post-2.0.0/test/test_parts.rb +86 -0
- data/gems/parallel-1.12.1/MIT-LICENSE.txt +20 -0
- data/gems/parallel-1.12.1/lib/parallel/processor_count.rb +93 -0
- data/gems/parallel-1.12.1/lib/parallel/version.rb +3 -0
- data/gems/parallel-1.12.1/lib/parallel.rb +500 -0
- data/gems/ruby-progressbar-1.9.0/LICENSE.txt +19 -0
- data/gems/ruby-progressbar-1.9.0/README.md +38 -0
- data/gems/ruby-progressbar-1.9.0/Rakefile +2 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/base.rb +183 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/calculators/length.rb +99 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/calculators/running_average.rb +9 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/components/bar.rb +96 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/components/percentage.rb +29 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/components/rate.rb +43 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/components/time.rb +107 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/components/title.rb +13 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/components.rb +5 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/errors/invalid_progress_error.rb +4 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/format/formatter.rb +27 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/format/molecule.rb +59 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/format/string.rb +36 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/format.rb +3 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/output.rb +68 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/outputs/non_tty.rb +47 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/outputs/null.rb +33 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/outputs/tty.rb +32 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/progress.rb +118 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/refinements/enumerator.rb +25 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/refinements.rb +1 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/throttle.rb +25 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/time.rb +30 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/timer.rb +72 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar/version.rb +3 -0
- data/gems/ruby-progressbar-1.9.0/lib/ruby-progressbar.rb +18 -0
- data/gems/slugify-1.0.7/README +18 -0
- data/gems/slugify-1.0.7/lib/slugify.rb +1012 -0
- data/gems/slugify-1.0.7/lib/slugify_string.rb +11 -0
- data/gems/slugify-1.0.7/tests/slugify_test.rb +116 -0
- data/gems/slugify-1.0.7/tests/string_slugify_test.rb +23 -0
- data/gems/websocket-driver-0.6.5/CHANGELOG.md +123 -0
- data/gems/websocket-driver-0.6.5/LICENSE.md +22 -0
- data/gems/websocket-driver-0.6.5/README.md +369 -0
- data/gems/websocket-driver-0.6.5/examples/tcp_server.rb +28 -0
- data/gems/websocket-driver-0.6.5/ext/websocket-driver/Makefile +264 -0
- data/gems/websocket-driver-0.6.5/ext/websocket-driver/WebsocketMaskService.java +55 -0
- data/gems/websocket-driver-0.6.5/ext/websocket-driver/extconf.rb +4 -0
- data/gems/websocket-driver-0.6.5/ext/websocket-driver/websocket_mask.bundle +0 -0
- data/gems/websocket-driver-0.6.5/ext/websocket-driver/websocket_mask.c +41 -0
- data/gems/websocket-driver-0.6.5/ext/websocket-driver/websocket_mask.o +0 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/client.rb +140 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/draft75.rb +102 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/draft76.rb +96 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/event_emitter.rb +54 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/headers.rb +45 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi/frame.rb +20 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi/message.rb +31 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb +406 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/proxy.rb +68 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/server.rb +80 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver/stream_reader.rb +55 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/driver.rb +199 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/http/headers.rb +112 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/http/request.rb +45 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/http/response.rb +29 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/http.rb +15 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/mask.rb +14 -0
- data/gems/websocket-driver-0.6.5/lib/websocket/websocket_mask.rb +2 -0
- data/gems/websocket-driver-0.6.5/lib/websocket_mask.bundle +0 -0
- data/gems/websocket-driver-0.6.5-java/CHANGELOG.md +123 -0
- data/gems/websocket-driver-0.6.5-java/LICENSE.md +22 -0
- data/gems/websocket-driver-0.6.5-java/README.md +369 -0
- data/gems/websocket-driver-0.6.5-java/examples/tcp_server.rb +28 -0
- data/gems/websocket-driver-0.6.5-java/ext/websocket-driver/WebsocketMaskService.java +55 -0
- data/gems/websocket-driver-0.6.5-java/ext/websocket-driver/extconf.rb +4 -0
- data/gems/websocket-driver-0.6.5-java/ext/websocket-driver/websocket_mask.c +41 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/client.rb +140 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/draft75.rb +102 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/draft76.rb +96 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/event_emitter.rb +54 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/headers.rb +45 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/hybi/frame.rb +20 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/hybi/message.rb +31 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/hybi.rb +406 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/proxy.rb +68 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/server.rb +80 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver/stream_reader.rb +55 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/driver.rb +199 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/http/headers.rb +112 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/http/request.rb +45 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/http/response.rb +29 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/http.rb +15 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/mask.rb +14 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket/websocket_mask.rb +2 -0
- data/gems/websocket-driver-0.6.5-java/lib/websocket_mask.jar +0 -0
- data/gems/websocket-extensions-0.1.3/CHANGELOG.md +15 -0
- data/gems/websocket-extensions-0.1.3/LICENSE.md +20 -0
- data/gems/websocket-extensions-0.1.3/README.md +313 -0
- data/gems/websocket-extensions-0.1.3/lib/websocket/extensions/parser.rb +111 -0
- data/gems/websocket-extensions-0.1.3/lib/websocket/extensions.rb +181 -0
- data/lib/kinetic-sdk.rb +1 -0
- data/lib/kinetic_sdk/bridgehub/bridgehub-sdk.rb +80 -0
- data/lib/kinetic_sdk/bridgehub/lib/access_keys.rb +67 -0
- data/lib/kinetic_sdk/bridgehub/lib/bridge.rb +69 -0
- data/lib/kinetic_sdk/discussions/discussions-sdk.rb +165 -0
- data/lib/kinetic_sdk/discussions/lib/discussions.rb +107 -0
- data/lib/kinetic_sdk/discussions/lib/invitations.rb +120 -0
- data/lib/kinetic_sdk/discussions/lib/messages.rb +190 -0
- data/lib/kinetic_sdk/discussions/lib/meta.rb +14 -0
- data/lib/kinetic_sdk/discussions/lib/participants.rb +64 -0
- data/lib/kinetic_sdk/discussions/lib/related_items.rb +54 -0
- data/lib/kinetic_sdk/discussions/lib/websockets.rb +96 -0
- data/lib/kinetic_sdk/filehub/filehub-sdk.rb +80 -0
- data/lib/kinetic_sdk/filehub/lib/access_keys.rb +67 -0
- data/lib/kinetic_sdk/filehub/lib/filestores.rb +67 -0
- data/lib/kinetic_sdk/request_ce/lib/attribute_definitions.rb +153 -0
- data/lib/kinetic_sdk/request_ce/lib/bridges.rb +66 -0
- data/lib/kinetic_sdk/request_ce/lib/categories.rb +34 -0
- data/lib/kinetic_sdk/request_ce/lib/datastore_form.rb +110 -0
- data/lib/kinetic_sdk/request_ce/lib/datastore_submissions.rb +157 -0
- data/lib/kinetic_sdk/request_ce/lib/form.rb +99 -0
- data/lib/kinetic_sdk/request_ce/lib/form_types.rb +51 -0
- data/lib/kinetic_sdk/request_ce/lib/jwt.rb +55 -0
- data/lib/kinetic_sdk/request_ce/lib/kapp.rb +93 -0
- data/lib/kinetic_sdk/request_ce/lib/meta.rb +14 -0
- data/lib/kinetic_sdk/request_ce/lib/oauth.rb +37 -0
- data/lib/kinetic_sdk/request_ce/lib/security_policy_definitions.rb +157 -0
- data/lib/kinetic_sdk/request_ce/lib/space.rb +89 -0
- data/lib/kinetic_sdk/request_ce/lib/submissions.rb +215 -0
- data/lib/kinetic_sdk/request_ce/lib/system_api.rb +69 -0
- data/lib/kinetic_sdk/request_ce/lib/teams.rb +113 -0
- data/lib/kinetic_sdk/request_ce/lib/users.rb +245 -0
- data/lib/kinetic_sdk/request_ce/lib/webhook_jobs.rb +142 -0
- data/lib/kinetic_sdk/request_ce/lib/webhooks.rb +192 -0
- data/lib/kinetic_sdk/request_ce/request-ce-sdk.rb +153 -0
- data/lib/kinetic_sdk/task/lib/access_keys.rb +94 -0
- data/lib/kinetic_sdk/task/lib/categories.rb +190 -0
- data/lib/kinetic_sdk/task/lib/config.rb +202 -0
- data/lib/kinetic_sdk/task/lib/engine.rb +42 -0
- data/lib/kinetic_sdk/task/lib/environment.rb +14 -0
- data/lib/kinetic_sdk/task/lib/errors.rb +165 -0
- data/lib/kinetic_sdk/task/lib/groups.rb +112 -0
- data/lib/kinetic_sdk/task/lib/handlers.rb +105 -0
- data/lib/kinetic_sdk/task/lib/health.rb +28 -0
- data/lib/kinetic_sdk/task/lib/license.rb +52 -0
- data/lib/kinetic_sdk/task/lib/policy_rules.rb +166 -0
- data/lib/kinetic_sdk/task/lib/setup.rb +56 -0
- data/lib/kinetic_sdk/task/lib/sources.rb +134 -0
- data/lib/kinetic_sdk/task/lib/tasks.rb +17 -0
- data/lib/kinetic_sdk/task/lib/trees.rb +274 -0
- data/lib/kinetic_sdk/task/lib/users.rb +75 -0
- data/lib/kinetic_sdk/task/task-sdk.rb +92 -0
- data/lib/kinetic_sdk/utils/kinetic-http-headers.rb +150 -0
- data/lib/kinetic_sdk/utils/kinetic-http-response.rb +75 -0
- data/lib/kinetic_sdk/utils/kinetic-http.rb +552 -0
- data/lib/kinetic_sdk/utils/logger.rb +70 -0
- data/lib/kinetic_sdk/utils/random.rb +30 -0
- data/lib/kinetic_sdk/version.rb +7 -0
- data/lib/kinetic_sdk.rb +57 -0
- metadata +414 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
[](https://travis-ci.org/kontena/kontena-websocket-client)
|
|
2
|
+
[](https://badge.fury.io/rb/kontena-websocket-client)
|
|
3
|
+
[](http://www.rubydoc.info/github/kontena/kontena-websocket-client/master)
|
|
4
|
+
|
|
5
|
+
# Kontena::Websocket::Client
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'kontena-websocket-client'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install kontena-websocket-client
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
The high-level `Kontena::Websocket::Client#connect` API uses a synchronous programming model instead of the event-based `on(:event) do ...` model used by the browser WebSockets API:
|
|
26
|
+
|
|
27
|
+
* The `connect` class method yields the connected `Kontena::Websocket::Client` object
|
|
28
|
+
* The `read` method yields received websocket messages and returns once the websocket is closed
|
|
29
|
+
* The `send` and `close` methods can be called from the `connect` block, the `read` block, or a different thread
|
|
30
|
+
* Any of these functions can also raise `Kontena::Websocket::Error`
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require 'kontena-websocket-client'
|
|
34
|
+
|
|
35
|
+
def websocket_connect
|
|
36
|
+
Kontena::Websocket::Client.connect(url, options...) do |client|
|
|
37
|
+
on_open
|
|
38
|
+
|
|
39
|
+
client.send(...)
|
|
40
|
+
client.close(1000)
|
|
41
|
+
|
|
42
|
+
client.read do |message|
|
|
43
|
+
on_message(message)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
on_close(client.close_code, client.close_reason) # client closed connection
|
|
47
|
+
end
|
|
48
|
+
rescue Kontena::Websocket::CloseError => exc
|
|
49
|
+
on_close(exc.code, exc.reason) # server closed connection
|
|
50
|
+
rescue Kontena::Websocket::Error => exc
|
|
51
|
+
on_error(exc)
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
### Environment variables
|
|
55
|
+
|
|
56
|
+
The library uses the following environment variables:
|
|
57
|
+
|
|
58
|
+
* `SSL_CERT_FILE`: Default value for `ssl_params: { ca_file: ... }`
|
|
59
|
+
* `SSL_CERT_PATH`: Default value for `ssl_params: { ca_path: ... }`
|
|
60
|
+
|
|
61
|
+
### Threadsafe
|
|
62
|
+
|
|
63
|
+
The `Kontena::Websocket::Client` is threadsafe: while a single read thread is blocking on `read`, other threads may safely call `send`, `ping` and `close`.
|
|
64
|
+
|
|
65
|
+
The `read` block may also call `send`, `ping` and `close`.
|
|
66
|
+
Do not call `read` from multiple threads, or the websocket messages may get corrupted.
|
|
67
|
+
|
|
68
|
+
The library uses an internal `Mutex` to protect the internal `Websocket::Driver` state, and prevent socket read/write reordering/corruption.
|
|
69
|
+
|
|
70
|
+
XXX: The `on_pong` callback is called with the mutex held; do not call any client methods from the `on_pong` block.
|
|
71
|
+
|
|
72
|
+
### Timeouts
|
|
73
|
+
|
|
74
|
+
The `Kontena::Websocket::Client` uses timeouts (given in `options`) to deal with network errors and not leave the client hanging. Timeouts will raise a descriptive `Kontena::Websocket::TimeoutError` from either the `connect`, `read` or `send/close` methods.
|
|
75
|
+
|
|
76
|
+
* `connect_timeout` is used for both TCP, SSL `connect` operations
|
|
77
|
+
* `open_timeout` is used for the websocket `open` handshake
|
|
78
|
+
* `write_timeout` is used for each socket `write` operation
|
|
79
|
+
* `ping_timeout` is used for each websocket `ping` request
|
|
80
|
+
|
|
81
|
+
The `Kontena::Websocket::Client` supports keepalive pings, where the `read` method will send a websocket ping request every `ping_interval` seconds, and raise a `Kontena::Websocket::TimeoutError` if it does not receive a websocket pong response within `ping_timeout` seconds.
|
|
82
|
+
|
|
83
|
+
### SSL Validation
|
|
84
|
+
|
|
85
|
+
The `Kontena::Websocket::Client` validates `wss://` server SSL certificates by default, using the OpenSSL APIs to provide useful `Kontena::Websocket::SSLVerifyError` messages. It also provides methods to inspect and validate the server SSL certificates, even when not using strict SSL validation.
|
|
86
|
+
|
|
87
|
+
Example code and resulting messages:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
begin
|
|
91
|
+
ssl_cert = ws.ssl_cert!
|
|
92
|
+
rescue Kontena::Websocket::SSLVerifyError => ssl_error
|
|
93
|
+
ssl_cert = ssl_error.cert
|
|
94
|
+
else
|
|
95
|
+
ssl_error = nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if ssl_cert && ssl_error
|
|
99
|
+
$logger.warn "Connected to #{url} with ssl errors: #{ssl_error} (subject #{ssl_cert.subject}, issuer #{ssl_cert.issuer})"
|
|
100
|
+
elsif ssl_error
|
|
101
|
+
$logger.warn "Connected to #{url} with ssl errors: #{ssl_error}"
|
|
102
|
+
elsif ssl_cert && !ws.ssl_verify?
|
|
103
|
+
$logger.warn "Connected to #{url} without ssl verify: #{ssl_cert.subject} (issuer #{ssl_cert.issuer})"
|
|
104
|
+
elsif ssl_cert
|
|
105
|
+
$logger.info "Connected to #{url} with ssl verify: #{ssl_cert.subject} (issuer #{ssl_cert.issuer})"
|
|
106
|
+
else
|
|
107
|
+
$logger.info "Connected to #{url} without ssl"
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
* `ERROR -- websocket-echo-client: certificate verify failed: self signed certificate`
|
|
112
|
+
* `WARN -- websocket-echo-client: Connected to wss://localhost:9293 with ssl errors: certificate verify failed: self signed certificate (subject /CN=kontena.test, issuer /CN=kontena.test)`
|
|
113
|
+
* `WARN -- websocket-echo-client: Connected to wss://echo.websocket.org without ssl verify: /OU=Domain Control Validated/CN=*.websocket.org (issuer /C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2)`
|
|
114
|
+
* `INFO -- websocket-echo-client: Connected to wss://echo.websocket.org with ssl verify: /OU=Domain Control Validated/CN=*.websocket.org (issuer /C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2)`
|
|
115
|
+
* `INFO -- websocket-echo-client: Connected to ws://echo.websocket.org without ssl`
|
|
116
|
+
|
|
117
|
+
### Examples
|
|
118
|
+
|
|
119
|
+
Use `bundle exec ./examples/...` to run the examples.
|
|
120
|
+
|
|
121
|
+
#### [Websocket Echo Client](./examples/websocket-echo-client.rb)
|
|
122
|
+
|
|
123
|
+
Connect to a websocket server, displaying the server SSL certificate. Send lines from `stdin`, and write messages to `stdout`. Close on `EOF`.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
I, [2017-07-21T17:06:48.353944 #17507] INFO -- : Connecting to wss://echo.websocket.org...
|
|
127
|
+
I, [2017-07-21T17:06:49.329616 #17507] INFO -- : Connected to wss://echo.websocket.org with ssl verify: /OU=Domain Control Validated/CN=*.websocket.org (issuer /C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2)
|
|
128
|
+
hello
|
|
129
|
+
D, [2017-07-21T17:06:50.447423 #17507] DEBUG -- : websocket write: "hello\n"
|
|
130
|
+
D, [2017-07-21T17:06:50.578683 #17507] DEBUG -- : websocket read: "hello\n"
|
|
131
|
+
hello
|
|
132
|
+
D, [2017-07-21T17:06:51.135395 #17507] DEBUG -- : websocket close: EOF
|
|
133
|
+
I, [2017-07-21T17:06:51.375279 #17507] INFO -- : Client closed connection with code 1000: EOF
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kontena/kontena-websocket-client.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
Thread.abort_on_exception = true
|
|
4
|
+
|
|
5
|
+
require 'kontena-websocket-client'
|
|
6
|
+
require_relative './benchmark'
|
|
7
|
+
|
|
8
|
+
WEBSOCKET_OPTIONS = {
|
|
9
|
+
connect_timeout: 1.0,
|
|
10
|
+
open_timeout: 1.0,
|
|
11
|
+
ping_timeout: 1.0,
|
|
12
|
+
ping_interval: nil,
|
|
13
|
+
write_timeout: 5.0,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Kontena::Websocket::Logging.initialize_logger(STDERR, LOG_LEVEL)
|
|
17
|
+
|
|
18
|
+
run_benchmark do |url, **options|
|
|
19
|
+
send_thread = nil
|
|
20
|
+
reader = BenchmarkReader.new
|
|
21
|
+
|
|
22
|
+
Kontena::Websocket::Client.connect(url, **WEBSOCKET_OPTIONS) do |client|
|
|
23
|
+
$logger.info "connect: #{client}"
|
|
24
|
+
|
|
25
|
+
send_thread = Thread.new {
|
|
26
|
+
send_stats = benchmark_sender(**options) do |msg, seq|
|
|
27
|
+
client.send(msg)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
client.close()
|
|
31
|
+
|
|
32
|
+
send_stats
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
reader.start()
|
|
36
|
+
client.read do |message|
|
|
37
|
+
reader.on_message(Time.now, message)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
read_stats = reader.stop()
|
|
42
|
+
send_stats = send_thread.value
|
|
43
|
+
|
|
44
|
+
next send_stats, read_stats
|
|
45
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
Thread.abort_on_exception = true
|
|
4
|
+
|
|
5
|
+
require 'faye/websocket'
|
|
6
|
+
require 'eventmachine'
|
|
7
|
+
require_relative './benchmark'
|
|
8
|
+
|
|
9
|
+
run_benchmark do |url, **options|
|
|
10
|
+
send_thread = nil
|
|
11
|
+
reader = BenchmarkReader.new
|
|
12
|
+
|
|
13
|
+
EM.run {
|
|
14
|
+
ws = Faye::WebSocket::Client.new(url)
|
|
15
|
+
|
|
16
|
+
ws.on :open do |event|
|
|
17
|
+
$logger.info "open"
|
|
18
|
+
|
|
19
|
+
send_thread = Thread.new {
|
|
20
|
+
send_stats = benchmark_sender(**options) do |msg, seq|
|
|
21
|
+
EM.next_tick {
|
|
22
|
+
$logger.debug "send seq=%d" % [seq]
|
|
23
|
+
ws.send(msg)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
EM.next_tick {
|
|
28
|
+
$logger.info "close..."
|
|
29
|
+
ws.close()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
send_stats
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
reader.start()
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
read = 0
|
|
39
|
+
|
|
40
|
+
ws.on :message do |event|
|
|
41
|
+
seq, rtt = reader.on_message(Time.now, event.data)
|
|
42
|
+
|
|
43
|
+
$logger.debug "read %d: seq=%d rtt=%.2f" % [read, seq, rtt]
|
|
44
|
+
|
|
45
|
+
read += 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ws.on :close do |event|
|
|
49
|
+
$logger.info "closed"
|
|
50
|
+
|
|
51
|
+
reader.stop
|
|
52
|
+
EM.stop
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
ws.on :error do |event|
|
|
56
|
+
$logger.warn "error: #{event}"
|
|
57
|
+
|
|
58
|
+
exit 1
|
|
59
|
+
end
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
read_stats = reader.stop()
|
|
63
|
+
send_stats = send_thread.value
|
|
64
|
+
|
|
65
|
+
next send_stats, read_stats
|
|
66
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
LOG_LEVEL = ENV['LOG_LEVEL'] || Logger::WARN
|
|
4
|
+
|
|
5
|
+
$logger = Logger.new(STDERR)
|
|
6
|
+
$logger.level = LOG_LEVEL
|
|
7
|
+
$logger.progname = 'websocket-benchmark'
|
|
8
|
+
|
|
9
|
+
# @yield at given target interval
|
|
10
|
+
# @return after duration
|
|
11
|
+
def with_rate(rate, duration, &block)
|
|
12
|
+
t0 = Time.now
|
|
13
|
+
interval = 1.0 / rate
|
|
14
|
+
count = 0
|
|
15
|
+
count_miss = 0
|
|
16
|
+
total_yield = 0.0
|
|
17
|
+
|
|
18
|
+
while (t = Time.now) < t0 + duration
|
|
19
|
+
yield t
|
|
20
|
+
|
|
21
|
+
t_yield = Time.now
|
|
22
|
+
|
|
23
|
+
count += 1
|
|
24
|
+
total_yield += (t_yield - t)
|
|
25
|
+
|
|
26
|
+
t_next = t0 + count * interval
|
|
27
|
+
|
|
28
|
+
if t_next > t_yield
|
|
29
|
+
sleep t_next - t_yield
|
|
30
|
+
else
|
|
31
|
+
count_miss += 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
t_total = t - t0
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
time: t_total,
|
|
39
|
+
count: count,
|
|
40
|
+
rate: count / t_total,
|
|
41
|
+
util: total_yield / t_total,
|
|
42
|
+
miss: (count_miss / count),
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @yield [seq, message]
|
|
47
|
+
# @return [Hash] after duration
|
|
48
|
+
def benchmark_sender(rate: 1000, duration: 5.0, message_size: 1000)
|
|
49
|
+
total_size = 0
|
|
50
|
+
seq = 0
|
|
51
|
+
|
|
52
|
+
padding = 'X'*(message_size - 16 - 16)
|
|
53
|
+
|
|
54
|
+
stats = with_rate(rate, duration) do |t|
|
|
55
|
+
message = '%15.6f %15d %s' % [t.to_f, seq, padding]
|
|
56
|
+
|
|
57
|
+
yield message, seq
|
|
58
|
+
|
|
59
|
+
total_size += message.length
|
|
60
|
+
seq += 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
return stats.merge(
|
|
64
|
+
bytes: total_size,
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class BenchmarkReader
|
|
69
|
+
def initialize
|
|
70
|
+
@count = 0
|
|
71
|
+
@bytes = 0
|
|
72
|
+
@latency_total = 0.0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def start
|
|
76
|
+
@t_start = Time.now
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param time [Time] Time.now
|
|
80
|
+
# @param message [String]
|
|
81
|
+
# @return [Integer, Float] seq, rtt
|
|
82
|
+
def on_message(time, message)
|
|
83
|
+
msg_time_s, msg_seq_s, padding = message.split(' ', 3)
|
|
84
|
+
msg_t = msg_time_s.to_f
|
|
85
|
+
msg_seq = msg_seq_s.to_i
|
|
86
|
+
t = time.to_f
|
|
87
|
+
|
|
88
|
+
@count += 1
|
|
89
|
+
@bytes += message.length
|
|
90
|
+
@latency_total += (t - msg_t)
|
|
91
|
+
|
|
92
|
+
return msg_seq, t - msg_t
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [Hash]
|
|
96
|
+
def stop
|
|
97
|
+
@t_stop = Time.now
|
|
98
|
+
seconds = @t_stop - @t_start
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
time: seconds,
|
|
102
|
+
count: @count,
|
|
103
|
+
rate: @count / seconds,
|
|
104
|
+
bytes: @bytes,
|
|
105
|
+
bytes_rate: @bytes / seconds,
|
|
106
|
+
latency_avg: @latency_total / @count,
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
URL = 'ws://localhost:8080/echo'
|
|
112
|
+
RATES = [1, 10, 100, 1000, 3000, 5000, 10000]
|
|
113
|
+
DURATION = 5.0
|
|
114
|
+
MESSAGE_SIZE = 1000
|
|
115
|
+
|
|
116
|
+
HEADER = '%5s %6s/s %9s: send @ %9s/s (%6s%% %6s%%) read @ %9s/s (%6s%%) = %12s/s ~%9s'
|
|
117
|
+
FORMAT = '%5.2fs %6d/s %9d: send @ %9.2f/s (%6.2f%% %6.2f%%) read @ %9.2f/s (%6.2f%%) = %12s/s ~%9.6fs'
|
|
118
|
+
|
|
119
|
+
def to_si(val)
|
|
120
|
+
if val > 10**9
|
|
121
|
+
'%.3fG' % [val / 10**9]
|
|
122
|
+
elsif val > 10**6
|
|
123
|
+
'%.3fM' % [val / 10**6]
|
|
124
|
+
elsif val > 10**3
|
|
125
|
+
'%.3fK' % [val / 10**3]
|
|
126
|
+
else
|
|
127
|
+
'%.3f ' % [val]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @yield [url, **options]
|
|
132
|
+
# @yieldreturn [send_stats, read_stats]
|
|
133
|
+
def run_benchmark()
|
|
134
|
+
url = ENV['URL'] || URL
|
|
135
|
+
|
|
136
|
+
rates = (ENV['RATES']&.split&.map{|r| Integer(r)} || RATES)
|
|
137
|
+
duration = (ENV['DURATION'] || DURATION).to_f
|
|
138
|
+
message_size = (ENV['MESSAGE_SIZE'] || MESSAGE_SIZE).to_i
|
|
139
|
+
|
|
140
|
+
puts HEADER % ['TIME ', 'RATE', 'COUNT', 'MESSAGES', 'UTIL', 'MISS', 'MESSAGES', 'DROP', 'BYTES', 'LATENCY']
|
|
141
|
+
|
|
142
|
+
for rate in rates
|
|
143
|
+
options = {
|
|
144
|
+
rate: rate,
|
|
145
|
+
duration: duration,
|
|
146
|
+
message_size: message_size,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
$logger.info "benchmark: #{url} #{options}"
|
|
150
|
+
|
|
151
|
+
send_stats, read_stats = yield(url, **options)
|
|
152
|
+
|
|
153
|
+
drop_ratio = 1.0 - read_stats[:count].to_f / send_stats[:count].to_f
|
|
154
|
+
|
|
155
|
+
puts FORMAT % [
|
|
156
|
+
duration, rate, send_stats[:count],
|
|
157
|
+
send_stats[:rate], send_stats[:util] * 100.0, send_stats[:miss] * 100.0,
|
|
158
|
+
read_stats[:rate], drop_ratio * 100.0, to_si(read_stats[:bytes_rate]), read_stats[:latency_avg],
|
|
159
|
+
]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -uex
|
|
4
|
+
|
|
5
|
+
DIR=$(dirname $0)
|
|
6
|
+
SERVER_ARGS=${SERVER_ARGS:- -drop}
|
|
7
|
+
BENCHMARK=${BENCHMARK:-benchmark-client.rb}
|
|
8
|
+
|
|
9
|
+
go get -d github.com/gorilla/websocket
|
|
10
|
+
go build -o $DIR/websocket-echo-server $DIR/websocket-echo-server.go
|
|
11
|
+
|
|
12
|
+
killall websocket-echo-server || true
|
|
13
|
+
$DIR/websocket-echo-server -quiet $SERVER_ARGS &
|
|
14
|
+
|
|
15
|
+
$DIR/${BENCHMARK}
|
|
16
|
+
|
|
17
|
+
trap "kill 0" INT EXIT
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"flag"
|
|
5
|
+
"fmt"
|
|
6
|
+
"github.com/gorilla/websocket"
|
|
7
|
+
"log"
|
|
8
|
+
"net/http"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
var websocketUpgrader = websocket.Upgrader{}
|
|
13
|
+
|
|
14
|
+
// single-goroutine read+write loop
|
|
15
|
+
// blocks reads if writes block
|
|
16
|
+
func websocketEchoSync(conn *websocket.Conn) error {
|
|
17
|
+
for {
|
|
18
|
+
if messageType, data, err := conn.ReadMessage(); err != nil {
|
|
19
|
+
if websocket.IsCloseError(err, 1000) {
|
|
20
|
+
break
|
|
21
|
+
} else {
|
|
22
|
+
return fmt.Errorf("websocket read: %v", err)
|
|
23
|
+
}
|
|
24
|
+
} else if err := conn.WriteMessage(messageType, data); err != nil {
|
|
25
|
+
return fmt.Errorf("websocket write: %v", err)
|
|
26
|
+
} else {
|
|
27
|
+
if options.Verbose {
|
|
28
|
+
log.Printf("websocket echo: %v", data)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return nil
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type websocketMessage struct {
|
|
37
|
+
Type int
|
|
38
|
+
Data []byte
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func websocketAsyncReader(conn *websocket.Conn, c chan websocketMessage) error {
|
|
42
|
+
defer close(c)
|
|
43
|
+
|
|
44
|
+
var messages, dropped int
|
|
45
|
+
var start = time.Now()
|
|
46
|
+
|
|
47
|
+
for {
|
|
48
|
+
if messageType, data, err := conn.ReadMessage(); err != nil {
|
|
49
|
+
if websocket.IsCloseError(err, 1000) {
|
|
50
|
+
if options.Verbose {
|
|
51
|
+
log.Printf("websocket read close: %v", err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
break
|
|
55
|
+
} else {
|
|
56
|
+
return fmt.Errorf("websocket read: %v", err)
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
var m = websocketMessage{messageType, data}
|
|
60
|
+
messages += 1
|
|
61
|
+
|
|
62
|
+
select {
|
|
63
|
+
case c <- m:
|
|
64
|
+
if options.Verbose {
|
|
65
|
+
log.Printf("websocket read: %v", m.Data)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
default:
|
|
69
|
+
dropped += 1
|
|
70
|
+
|
|
71
|
+
if options.Verbose {
|
|
72
|
+
log.Printf("websocket drop: %v", m.Data)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var end = time.Now()
|
|
79
|
+
|
|
80
|
+
if !options.Quiet {
|
|
81
|
+
var seconds = end.Sub(start).Seconds()
|
|
82
|
+
|
|
83
|
+
log.Printf("websocket read: %d messages in %.1fs (%.2f/s, dropped %.2f%%)",
|
|
84
|
+
messages, seconds,
|
|
85
|
+
float64(messages)/seconds,
|
|
86
|
+
float64(dropped)/float64(messages)*100.0,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return nil
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func websocketAsyncWriter(conn *websocket.Conn, c <-chan websocketMessage) error {
|
|
94
|
+
for m := range c {
|
|
95
|
+
if err := conn.WriteMessage(m.Type, m.Data); err != nil {
|
|
96
|
+
return fmt.Errorf("websocket write: %v", err)
|
|
97
|
+
} else {
|
|
98
|
+
if options.Verbose {
|
|
99
|
+
log.Printf("websocket write: %v", m.Data)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func websocketEchoAsync(conn *websocket.Conn) error {
|
|
108
|
+
var messageChan = make(chan websocketMessage, options.DropBuffer)
|
|
109
|
+
var readClose struct {
|
|
110
|
+
code int
|
|
111
|
+
text string
|
|
112
|
+
}
|
|
113
|
+
var readError error
|
|
114
|
+
|
|
115
|
+
// custom close handler to send close frame after reader drains the message queue
|
|
116
|
+
conn.SetCloseHandler(func(code int, text string) error {
|
|
117
|
+
readClose.code = code
|
|
118
|
+
readClose.text = text
|
|
119
|
+
|
|
120
|
+
return nil
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
go func() {
|
|
124
|
+
if err := websocketAsyncReader(conn, messageChan); err != nil {
|
|
125
|
+
readError = err
|
|
126
|
+
log.Printf("websocket read error: %v", err)
|
|
127
|
+
}
|
|
128
|
+
}()
|
|
129
|
+
|
|
130
|
+
if err := websocketAsyncWriter(conn, messageChan); err != nil {
|
|
131
|
+
return err
|
|
132
|
+
} else if readError != nil {
|
|
133
|
+
return readError
|
|
134
|
+
} else {
|
|
135
|
+
var closeMessage = websocket.FormatCloseMessage(readClose.code, readClose.text)
|
|
136
|
+
|
|
137
|
+
if err := conn.WriteControl(websocket.CloseMessage, closeMessage, time.Time{}); err != nil {
|
|
138
|
+
log.Printf("websocket write close: %v", err)
|
|
139
|
+
} else {
|
|
140
|
+
if options.Verbose {
|
|
141
|
+
log.Printf("websocket write close")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return nil
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func EchoHandler(w http.ResponseWriter, r *http.Request) {
|
|
150
|
+
if websocketConn, err := websocketUpgrader.Upgrade(w, r, nil); err != nil {
|
|
151
|
+
log.Printf("Websocket Upgrade error: %v", err)
|
|
152
|
+
w.WriteHeader(500)
|
|
153
|
+
fmt.Fprintf(w, "%v", err)
|
|
154
|
+
} else {
|
|
155
|
+
if !options.Quiet {
|
|
156
|
+
log.Printf("Websocket echo connect: %v", r.RemoteAddr)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
defer websocketConn.Close()
|
|
160
|
+
|
|
161
|
+
if options.Drop {
|
|
162
|
+
if err := websocketEchoAsync(websocketConn); err != nil {
|
|
163
|
+
log.Printf("Websocket echo error: %v", err)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
if err := websocketEchoSync(websocketConn); err != nil {
|
|
168
|
+
log.Printf("Websocket echo error: %v", err)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if !options.Quiet {
|
|
174
|
+
log.Printf("Websocket echo close: %v", r.RemoteAddr)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
var options struct {
|
|
180
|
+
Listen string
|
|
181
|
+
Verbose bool
|
|
182
|
+
Quiet bool
|
|
183
|
+
Drop bool
|
|
184
|
+
DropBuffer uint
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func init() {
|
|
188
|
+
flag.StringVar(&options.Listen, "listen", "localhost:8080", "HOST:PORT")
|
|
189
|
+
flag.BoolVar(&options.Verbose, "verbose", false, "log echo messages")
|
|
190
|
+
flag.BoolVar(&options.Quiet, "quiet", false, "do not log connects")
|
|
191
|
+
flag.BoolVar(&options.Drop, "drop", false, "drop messages if client is sending faster than reading")
|
|
192
|
+
flag.UintVar(&options.DropBuffer, "drop-buffer", 1000, "message buffer length")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func main() {
|
|
196
|
+
flag.Parse()
|
|
197
|
+
|
|
198
|
+
if !options.Quiet {
|
|
199
|
+
log.Printf("Websocket listen: %v", options.Listen)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
http.HandleFunc("/echo", EchoHandler)
|
|
203
|
+
|
|
204
|
+
if err := http.ListenAndServe(options.Listen, nil); err != nil {
|
|
205
|
+
log.Fatalf("http listen: %v", err)
|
|
206
|
+
}
|
|
207
|
+
}
|