alo7-net 0.1.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.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/.gitignore +9 -0
- data/.gitlab-ci.yml +57 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +12 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +49 -0
- data/Gemfile +19 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +13 -0
- data/alo7-net.gemspec +24 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/echo/Gemfile +3 -0
- data/examples/echo/client.rb +14 -0
- data/examples/echo/server.rb +13 -0
- data/lib/alo7-net.rb +4 -0
- data/lib/alo7/net.rb +185 -0
- data/lib/alo7/net/client.rb +47 -0
- data/lib/alo7/net/connection.rb +182 -0
- data/lib/alo7/net/defer.rb +56 -0
- data/lib/alo7/net/error.rb +10 -0
- data/lib/alo7/net/server.rb +26 -0
- data/lib/alo7/net/version.rb +5 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9203184940ea7875e9119f7dfeb7f195b41a6c7c
|
4
|
+
data.tar.gz: 08ad750866199fcf9d0b2b0ebbac1b165af472ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 16a36c6b9afffdeb260d65384d61c14d46631f79363be391e00fc1e70abee619981e12cbefcfc25963f41182a1ce24ee0ceabe2aa21a76bc15fb5a7ddd129a08
|
7
|
+
data.tar.gz: 259cfb5db9ea77d663b774ad807bfa3ba8d4697a00efe6e2bb8dd052ad68250700d06c647c9854645c44be1753974e35d5220a696932e7f4ee3962efd2bdae93
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
before_script:
|
2
|
+
- ruby -v
|
3
|
+
- gem -v
|
4
|
+
- bundle -v
|
5
|
+
- bundle config mirror.https://rubygems.org https://ruby.taobao.org
|
6
|
+
- bundle install --without development --path vendor/bundle
|
7
|
+
|
8
|
+
cache:
|
9
|
+
paths:
|
10
|
+
- vendor/bundle
|
11
|
+
|
12
|
+
stages:
|
13
|
+
- test
|
14
|
+
- doc
|
15
|
+
|
16
|
+
.test: &test
|
17
|
+
stage: test
|
18
|
+
script:
|
19
|
+
- bundle exec rake test
|
20
|
+
variables:
|
21
|
+
COVERAGE: 'true'
|
22
|
+
tags:
|
23
|
+
- docker
|
24
|
+
|
25
|
+
test:2.3:
|
26
|
+
<<: *test
|
27
|
+
image: ruby:2.3
|
28
|
+
|
29
|
+
test:2.2:
|
30
|
+
<<: *test
|
31
|
+
image: ruby:2.2
|
32
|
+
|
33
|
+
test:2.1:
|
34
|
+
<<: *test
|
35
|
+
image: ruby:2.1
|
36
|
+
|
37
|
+
test:2.0:
|
38
|
+
<<: *test
|
39
|
+
image: ruby:2.0
|
40
|
+
|
41
|
+
test:1.9:
|
42
|
+
<<: *test
|
43
|
+
image: ruby:1.9
|
44
|
+
|
45
|
+
pages:
|
46
|
+
image: ruby
|
47
|
+
stage: doc
|
48
|
+
script:
|
49
|
+
- bundle exec rake yard
|
50
|
+
- mv doc public
|
51
|
+
artifacts:
|
52
|
+
paths:
|
53
|
+
- public
|
54
|
+
tags:
|
55
|
+
- docker
|
56
|
+
only:
|
57
|
+
- master
|
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at qqshfox@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development, :test do
|
6
|
+
gem 'bundler', '~> 1.7'
|
7
|
+
gem 'rake', '~> 10.0'
|
8
|
+
gem 'minitest', '~> 5.0'
|
9
|
+
gem 'simplecov'
|
10
|
+
gem 'coveralls', require: false
|
11
|
+
gem 'rubocop', require: false
|
12
|
+
gem 'yard', require: false
|
13
|
+
end
|
14
|
+
|
15
|
+
group :development do
|
16
|
+
gem 'guard'
|
17
|
+
gem 'guard-minitest'
|
18
|
+
gem 'guard-yard'
|
19
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
guard :minitest do
|
2
|
+
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
|
3
|
+
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
4
|
+
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
5
|
+
end
|
6
|
+
|
7
|
+
guard :yard, cli: '--reload' do
|
8
|
+
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| 'yard' }
|
9
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Hanfei Shen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# alo7-net [](https://travis-ci.org/qqshfox/alo7-net) [](https://coveralls.io/github/qqshfox/alo7-net) [](https://codeclimate.com/github/qqshfox/alo7-net) [](https://gemnasium.com/github.com/qqshfox/alo7-net)
|
2
|
+
|
3
|
+
alo7-net is the TCP server/client library we developed specifically for our ALO7 Learning Platform. This library provides a way to write asynchronous code in a straight-line fashion using fibers.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'alo7-net'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install alo7-net
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Server
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'alo7-net'
|
27
|
+
|
28
|
+
class EchoServer < Alo7::Net::Server
|
29
|
+
def receive_data(data)
|
30
|
+
send_data data
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Alo7::Net.run do
|
35
|
+
EchoServer.listen 3000
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
### Client
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'alo7-net'
|
43
|
+
|
44
|
+
class EchoClient < Alo7::Net::Client
|
45
|
+
def receive_data(data)
|
46
|
+
puts data
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Alo7::Net.run do
|
51
|
+
c = EchoClient.connect 'localhost', 3000
|
52
|
+
c.send_data 'Hello World!'
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
## Development
|
57
|
+
|
58
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
59
|
+
|
60
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
61
|
+
|
62
|
+
## Contributing
|
63
|
+
|
64
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/qqshfox/alo7-net. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
65
|
+
|
66
|
+
|
67
|
+
## License
|
68
|
+
|
69
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.libs << 'lib'
|
8
|
+
t.test_files = FileList['test/**/test_*.rb']
|
9
|
+
end
|
10
|
+
|
11
|
+
YARD::Rake::YardocTask.new(:yard)
|
12
|
+
|
13
|
+
task default: :test
|
data/alo7-net.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'alo7/net/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'alo7-net'
|
8
|
+
spec.version = Alo7::Net::VERSION
|
9
|
+
spec.authors = ['Hanfei Shen']
|
10
|
+
spec.email = ['qqshfox@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'A TCP server/client library used at ALO7.'
|
13
|
+
spec.description = 'alo7-net is the TCP server/client library we developed specifically for our ALO7 Learning Platform. This library provides a way to write asynchronous code in a straight-line fashion using fibers.'
|
14
|
+
spec.homepage = 'https://github.com/qqshfox/alo7-net'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'eventmachine', '~> 1.0'
|
23
|
+
spec.add_runtime_dependency 'em-synchrony', '~> 1.0'
|
24
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'alo7-net'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/alo7-net.rb
ADDED
data/lib/alo7/net.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'alo7/net/version'
|
5
|
+
require 'alo7/net/connection'
|
6
|
+
require 'alo7/net/server'
|
7
|
+
require 'alo7/net/client'
|
8
|
+
require 'alo7/net/error'
|
9
|
+
require 'alo7/net/defer'
|
10
|
+
|
11
|
+
module Alo7
|
12
|
+
module Net
|
13
|
+
# Run an event loop in the fiber way.
|
14
|
+
#
|
15
|
+
# @overload run(block)
|
16
|
+
# @overload run(&block)
|
17
|
+
#
|
18
|
+
# @param block [MethodObject] the block to run
|
19
|
+
# @return [void]
|
20
|
+
#
|
21
|
+
# @example Starting an event loop in the current thread to run the "Hello, world"-like Echo server example
|
22
|
+
#
|
23
|
+
# !!!ruby
|
24
|
+
# require 'alo7-net'
|
25
|
+
#
|
26
|
+
# class EchoServer < Alo7::Net::Server
|
27
|
+
# def receive_data(data)
|
28
|
+
# send_data data
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# Alo7::Net.run do
|
33
|
+
# EchoServer.listen 3000
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @note This method blocks the calling thread.
|
37
|
+
# @note This method only returns if the event loop stopped.
|
38
|
+
#
|
39
|
+
# @see stop
|
40
|
+
def self.run(blk = nil, &block)
|
41
|
+
EM.synchrony(blk || block)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Run an event loop and then terminate the loop as soon as the block completes.
|
45
|
+
#
|
46
|
+
# @param block [MethodObject] the block to run once
|
47
|
+
# @return [void]
|
48
|
+
def self.run_once(&block)
|
49
|
+
run do
|
50
|
+
block.call
|
51
|
+
stop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Stop the running event loop.
|
56
|
+
#
|
57
|
+
# @return [void]
|
58
|
+
#
|
59
|
+
# @example Stopping a running event loop.
|
60
|
+
#
|
61
|
+
# !!!ruby
|
62
|
+
# require 'alo7-net'
|
63
|
+
#
|
64
|
+
# class OnceClient < Alo7::Net::Client
|
65
|
+
# def post_init
|
66
|
+
# puts 'sending a dump HTTP request'
|
67
|
+
# send_data "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n"
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# def receive_data(data)
|
71
|
+
# puts "data.length: #{data.length}"
|
72
|
+
# puts 'stopping the event loop now'
|
73
|
+
# Net.stop
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# Alo7::Net.run do
|
78
|
+
# OnceClient.connect 'www.google.com', 80
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# @see run
|
82
|
+
def self.stop
|
83
|
+
EM.stop_event_loop
|
84
|
+
end
|
85
|
+
|
86
|
+
# Initiate a TCP server on the specified IP address and port.
|
87
|
+
#
|
88
|
+
# @overload listen(handler, host, port, *args)
|
89
|
+
# @param handler [ClassObject] a module or class that has an innerclass named `Impl`
|
90
|
+
# @param host [String] host to listen on
|
91
|
+
# @param port [Integer] port to listen on
|
92
|
+
# @param *args passed to the initializer of the handler
|
93
|
+
# @overload listen(handler, port, *args)
|
94
|
+
# @param handler [ClassObject] a module or class that has an innerclass named `Impl`
|
95
|
+
# @param port [Integer] port to listen on
|
96
|
+
# @param *args passed to the initializer of the handler
|
97
|
+
# @yield [handler] initiated when a connection is made
|
98
|
+
# @return [Integer] the internal signature
|
99
|
+
#
|
100
|
+
# @raise (see check_handler!)
|
101
|
+
#
|
102
|
+
# @see connect
|
103
|
+
def self.listen(handler, host_or_port, port = nil, *args)
|
104
|
+
check_handler! handler
|
105
|
+
if port
|
106
|
+
host = host_or_port
|
107
|
+
else
|
108
|
+
host = 'localhost'
|
109
|
+
port = host_or_port
|
110
|
+
end
|
111
|
+
EM.start_server host, port, handler::Impl, handler, *args do |server|
|
112
|
+
yield server.handler if block_given?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Initiate a TCP connection to a remote server and set up event handling for
|
117
|
+
# the connection.
|
118
|
+
#
|
119
|
+
# @param handler [ClassObject] a module or class that has an innerclass named `Impl`
|
120
|
+
# @param host [String] host to connect to
|
121
|
+
# @param port [Integer] port to connect to
|
122
|
+
# @return [handler] the initiated handler instance
|
123
|
+
#
|
124
|
+
# @raise (see check_handler!)
|
125
|
+
#
|
126
|
+
# @note This requires event loop to be running. (see {run}).
|
127
|
+
#
|
128
|
+
# @see listen
|
129
|
+
# @see reconnect
|
130
|
+
def self.connect(handler, host, port, *args)
|
131
|
+
check_handler! handler
|
132
|
+
connection = EM.connect host, port, handler::Impl, handler, *args
|
133
|
+
connection.handler
|
134
|
+
end
|
135
|
+
|
136
|
+
# Connect to a given host/port and re-use the provided handler instance.
|
137
|
+
#
|
138
|
+
# @param handler [#impl] a instance that has a property named `impl`
|
139
|
+
# @param host [String] host to reconnect to
|
140
|
+
# @param port [Integer] port to reconnect to
|
141
|
+
# @return [handler] the previous handler instance
|
142
|
+
#
|
143
|
+
# @raise [ArgumentError] if the handler doesn't implement a method named impl
|
144
|
+
#
|
145
|
+
# @see connect
|
146
|
+
# @see listen
|
147
|
+
def self.reconnect(handler, host, port)
|
148
|
+
raise ArgumentError, 'must provide a impl property' unless handler.respond_to? :impl
|
149
|
+
connection = EM.reconnect host, port, handler.impl
|
150
|
+
connection.handler
|
151
|
+
end
|
152
|
+
|
153
|
+
# Wait the defer succeed and then return anything the defer returns. Or
|
154
|
+
# raise the exception from the defer.
|
155
|
+
#
|
156
|
+
# @param defer [Defer] defer to wait
|
157
|
+
# @return anything the defer returns
|
158
|
+
#
|
159
|
+
# @raise [Exception] the exception returns from defer
|
160
|
+
def self.await(defer)
|
161
|
+
result = EM::Synchrony.sync defer
|
162
|
+
raise result if result.is_a? Exception
|
163
|
+
result
|
164
|
+
end
|
165
|
+
|
166
|
+
# Execute the block in a fiber.
|
167
|
+
#
|
168
|
+
# @param args passed as arguments to the block that are executed
|
169
|
+
# @yieldparam *args arguments passed from outside
|
170
|
+
# @yieldreturn [Object] anything
|
171
|
+
# @return [Object] anything returned from the block
|
172
|
+
def self.fiber_block(*args)
|
173
|
+
Fiber.new { yield(*args) }.resume
|
174
|
+
end
|
175
|
+
|
176
|
+
class << self
|
177
|
+
private
|
178
|
+
|
179
|
+
# @raise [ArgumentError] if the handler doesn't have a innerclass named Impl
|
180
|
+
def check_handler!(handler)
|
181
|
+
raise ArgumentError, 'must provide a innerclass of Impl' unless defined? handler::Impl
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'alo7/net'
|
2
|
+
require 'alo7/net/connection'
|
3
|
+
require 'alo7/net/defer'
|
4
|
+
|
5
|
+
module Alo7
|
6
|
+
module Net
|
7
|
+
# This is a class that provides the client logics.
|
8
|
+
class Client < Connection
|
9
|
+
# Initiate a TCP connection to a remote server and set up event handling
|
10
|
+
# for the connection.
|
11
|
+
#
|
12
|
+
# @param host [String] host to connect to
|
13
|
+
# @param port [Integer] port to connect to
|
14
|
+
# @param args passed to the initializer of the client
|
15
|
+
# @return [Client] the initiated client instance
|
16
|
+
def self.connect(host, port, *args)
|
17
|
+
connection = Net.connect self, host, port, *args
|
18
|
+
await_connect connection
|
19
|
+
end
|
20
|
+
|
21
|
+
# Connect to a given host/port and re-use the instance.
|
22
|
+
#
|
23
|
+
# @param host [String] host to connect to
|
24
|
+
# @param port [Integer] port to connect to
|
25
|
+
# @return [self]
|
26
|
+
def reconnect(host, port)
|
27
|
+
Net.reconnect self, host, port
|
28
|
+
self.class.await_connect self
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# @private
|
33
|
+
def await_connect(connection)
|
34
|
+
defer = Defer.new
|
35
|
+
connection.define_singleton_method :connection_completed do
|
36
|
+
defer.succeed
|
37
|
+
end
|
38
|
+
Net.await defer
|
39
|
+
connection.singleton_class.instance_eval do
|
40
|
+
remove_method :connection_completed
|
41
|
+
end
|
42
|
+
connection
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'alo7/net'
|
3
|
+
require 'alo7/net/error'
|
4
|
+
|
5
|
+
module Alo7
|
6
|
+
module Net
|
7
|
+
# This is a class that is instantiated by the event loop whenever a new
|
8
|
+
# connection is created. New connections can be created by {listen accepting
|
9
|
+
# a remote client} or {connect connecting to a remote server}.
|
10
|
+
#
|
11
|
+
# Users should overwrite {#initialize}, following callbacks included from
|
12
|
+
# {Callbacks} to implement their own business logics:
|
13
|
+
#
|
14
|
+
# * {#post_init}
|
15
|
+
# * {#connection_completed}
|
16
|
+
# * {#receive_data}
|
17
|
+
# * {#unbind}
|
18
|
+
#
|
19
|
+
# @note This class is considered to be a base class. Use the derived class
|
20
|
+
# {Server} or {Client} instead of this class directly.
|
21
|
+
# @note This class should never be instantiated by user code.
|
22
|
+
class Connection
|
23
|
+
# This method declaires the callbacks users can overwrite to implement
|
24
|
+
# their own business logics.
|
25
|
+
module Callbacks
|
26
|
+
# Called by the event loop immediately after the network connection has
|
27
|
+
# been established, and before resumption of the network loop.
|
28
|
+
#
|
29
|
+
# @return [void]
|
30
|
+
#
|
31
|
+
# @see #connection_completed
|
32
|
+
def post_init
|
33
|
+
end
|
34
|
+
|
35
|
+
# Called by the event loop when a remote TCP connection attempt
|
36
|
+
# completes successfully.
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
#
|
40
|
+
# @see Net.connect
|
41
|
+
# @see #post_init
|
42
|
+
def connection_completed
|
43
|
+
end
|
44
|
+
|
45
|
+
# Called by the event loop whenever data has been received by the
|
46
|
+
# network connection. It's called with a single parameter, a String
|
47
|
+
# containing the network protocol data, which may of course be binary.
|
48
|
+
# You will generally overwrite this method to perform your own
|
49
|
+
# processing of the incoming data.
|
50
|
+
#
|
51
|
+
# @param data [String] data received from the remote end
|
52
|
+
# @return [void]
|
53
|
+
#
|
54
|
+
# @see #send_data
|
55
|
+
def receive_data(data)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Called by the event loop whenever a connection (either a server or a
|
59
|
+
# client connection) is closed. The close can occur because of your code
|
60
|
+
# intentionally (using {#disconnect}), because of the remote end closed
|
61
|
+
# the connection, or because of a network error.
|
62
|
+
#
|
63
|
+
# @return [void]
|
64
|
+
#
|
65
|
+
# @see #disconnect
|
66
|
+
#
|
67
|
+
# @note You may not assume that the network connection is still open and
|
68
|
+
# able to send or receive data when the callback to unbind is made.
|
69
|
+
# This is intended only to give you a chance to clean up associations
|
70
|
+
# your code may have made to the connection object while it was open.
|
71
|
+
def unbind
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
include Callbacks
|
76
|
+
|
77
|
+
# @private
|
78
|
+
attr_accessor :impl
|
79
|
+
|
80
|
+
# @param args passed from {listen} or {connect}
|
81
|
+
def initialize(*args)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Send the data to the remote end of the connection asynchronously.
|
85
|
+
#
|
86
|
+
# @param data [String] data to send
|
87
|
+
# @return [void]
|
88
|
+
#
|
89
|
+
# @see #receive_data
|
90
|
+
#
|
91
|
+
# @note Data is buffered to be send which means it is not guaranteed to be
|
92
|
+
# sent immediately when calling this method.
|
93
|
+
def send_data(data)
|
94
|
+
@impl.send_data data
|
95
|
+
end
|
96
|
+
|
97
|
+
# Close the connection asynchronously after all of the outbound data has
|
98
|
+
# been written to the remote end. {#unbind} will be called later after
|
99
|
+
# this method returns.
|
100
|
+
#
|
101
|
+
# @return [void]
|
102
|
+
#
|
103
|
+
# @see #unbind
|
104
|
+
def disconnect
|
105
|
+
@impl.close_connection_after_writing
|
106
|
+
end
|
107
|
+
|
108
|
+
# (see Impl#await)
|
109
|
+
def await(defer)
|
110
|
+
@impl.await defer
|
111
|
+
end
|
112
|
+
|
113
|
+
# @private
|
114
|
+
class Impl < EM::Connection
|
115
|
+
# @private
|
116
|
+
attr_reader :handler
|
117
|
+
|
118
|
+
def initialize(klass, *args)
|
119
|
+
raise ArgumentError, "must provide a subclass of #{Connection.name}" \
|
120
|
+
unless klass <= Connection
|
121
|
+
@handler = klass.new(*args)
|
122
|
+
@handler.impl = self
|
123
|
+
end
|
124
|
+
|
125
|
+
def post_init
|
126
|
+
Net.fiber_block do
|
127
|
+
@unbinding = false
|
128
|
+
@defers = []
|
129
|
+
|
130
|
+
@handler.post_init
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def connection_completed
|
135
|
+
Net.fiber_block { @handler.connection_completed }
|
136
|
+
end
|
137
|
+
|
138
|
+
def receive_data(data)
|
139
|
+
Net.fiber_block { @handler.receive_data data }
|
140
|
+
end
|
141
|
+
|
142
|
+
def unbind
|
143
|
+
Net.fiber_block do
|
144
|
+
@unbinding = true
|
145
|
+
@defers.dup.each { |defer| defer.fail ConnectionLost.new }
|
146
|
+
|
147
|
+
@handler.unbind
|
148
|
+
|
149
|
+
@unbinding = false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# (see Net.await)
|
154
|
+
#
|
155
|
+
# @note It keeps the pending defers internally. When unbinding, it fails
|
156
|
+
# them with a ConnectionLost error.
|
157
|
+
# @note It fails the defer and raise a ConnectionLost error
|
158
|
+
# intermediately between unbinding.
|
159
|
+
def await(defer)
|
160
|
+
if @unbinding
|
161
|
+
err = ConnectionLost.new
|
162
|
+
defer.fail err
|
163
|
+
raise err
|
164
|
+
else
|
165
|
+
_await defer
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def _await(defer)
|
172
|
+
@defers.push defer
|
173
|
+
begin
|
174
|
+
Net.await defer
|
175
|
+
ensure
|
176
|
+
@defers.delete defer
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'em/deferrable'
|
2
|
+
|
3
|
+
module Alo7
|
4
|
+
module Net
|
5
|
+
# This is a callback which will be put off until later.
|
6
|
+
class Defer
|
7
|
+
include EM::Deferrable
|
8
|
+
|
9
|
+
# @!method callback(&block)
|
10
|
+
# Specify a block to be executed if and when the Deferrable object succeeded.
|
11
|
+
#
|
12
|
+
# * If there is no status, add a callback to an internal list.
|
13
|
+
# * If status is succeeded, execute the callback immediately.
|
14
|
+
# * If status is failed, do nothing.
|
15
|
+
#
|
16
|
+
# @param block [MethodObject]
|
17
|
+
# @return [self]
|
18
|
+
|
19
|
+
# @!method cancel_callback
|
20
|
+
# Cancels an outstanding callback to &block if any. Undoes the action of {#callback}.
|
21
|
+
#
|
22
|
+
# @return [MethodObject] The cancelled callback.
|
23
|
+
|
24
|
+
# @!method errback
|
25
|
+
# Specify a block to be executed if and when the Deferrable object failed.
|
26
|
+
#
|
27
|
+
# * If there is no status, add a errback to an internal list.
|
28
|
+
# * If status is failed, execute the errback immediately.
|
29
|
+
# * If status is succeeded, do nothing.
|
30
|
+
#
|
31
|
+
# @param block [MethodObject]
|
32
|
+
# @return [self]
|
33
|
+
|
34
|
+
# @!method cancel_errback
|
35
|
+
# Cancels an outstanding errback to &block if any. Undoes the action of {#errback}.
|
36
|
+
#
|
37
|
+
# @return [MethodObject] The cancelled errback.
|
38
|
+
|
39
|
+
# @!method succeed(*args)
|
40
|
+
# Execute all of the blocks passed to the object using the {#callback}
|
41
|
+
# method (if any) BEFORE this method returns. All of the blocks passed
|
42
|
+
# to the object using {#errback} will be discarded.
|
43
|
+
#
|
44
|
+
# @param args passed as arguments to any callbacks that are executed
|
45
|
+
# @return [void]
|
46
|
+
|
47
|
+
# @!method fail(*args)
|
48
|
+
# Execute all of the blocks passed to the object using the {#errback}
|
49
|
+
# method (if any) BEFORE this method returns. All of the blocks passed
|
50
|
+
# to the object using {#callback} will be discarded.
|
51
|
+
#
|
52
|
+
# @param args passed as arguments to any errbacks that are executed
|
53
|
+
# @return [void]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'alo7/net'
|
2
|
+
require 'alo7/net/connection'
|
3
|
+
|
4
|
+
module Alo7
|
5
|
+
module Net
|
6
|
+
# This is a class that provides the server logics.
|
7
|
+
class Server < Connection
|
8
|
+
# Initiate a TCP server on the specified IP address and port.
|
9
|
+
#
|
10
|
+
# @overload listen(host, port)
|
11
|
+
# @param host [String] host to listen on
|
12
|
+
# @param port [Integer] port to listen on
|
13
|
+
# @param *args passed to the initializer of the server
|
14
|
+
# @overload listen(port)
|
15
|
+
# @param port [Integer] port to listen on
|
16
|
+
# @param *args passed to the initializer of the server
|
17
|
+
# @yield [connection] initiated when a connection is made
|
18
|
+
# @return (see Net.listen)
|
19
|
+
#
|
20
|
+
# @raise (see Net.listen)
|
21
|
+
def self.listen(host_or_port, port = nil, *args, &block)
|
22
|
+
Net.listen self, host_or_port, port, *args, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alo7-net
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hanfei Shen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: eventmachine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-synchrony
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description: alo7-net is the TCP server/client library we developed specifically for
|
42
|
+
our ALO7 Learning Platform. This library provides a way to write asynchronous code
|
43
|
+
in a straight-line fashion using fibers.
|
44
|
+
email:
|
45
|
+
- qqshfox@gmail.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".editorconfig"
|
51
|
+
- ".gitignore"
|
52
|
+
- ".gitlab-ci.yml"
|
53
|
+
- ".rubocop.yml"
|
54
|
+
- ".travis.yml"
|
55
|
+
- ".yardopts"
|
56
|
+
- CHANGELOG.md
|
57
|
+
- CONTRIBUTING.md
|
58
|
+
- Gemfile
|
59
|
+
- Guardfile
|
60
|
+
- LICENSE.txt
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- alo7-net.gemspec
|
64
|
+
- bin/console
|
65
|
+
- bin/setup
|
66
|
+
- examples/echo/Gemfile
|
67
|
+
- examples/echo/client.rb
|
68
|
+
- examples/echo/server.rb
|
69
|
+
- lib/alo7-net.rb
|
70
|
+
- lib/alo7/net.rb
|
71
|
+
- lib/alo7/net/client.rb
|
72
|
+
- lib/alo7/net/connection.rb
|
73
|
+
- lib/alo7/net/defer.rb
|
74
|
+
- lib/alo7/net/error.rb
|
75
|
+
- lib/alo7/net/server.rb
|
76
|
+
- lib/alo7/net/version.rb
|
77
|
+
homepage: https://github.com/qqshfox/alo7-net
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.5.1
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: A TCP server/client library used at ALO7.
|
101
|
+
test_files: []
|
102
|
+
has_rdoc:
|