leech 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.
- data/.document +8 -0
- data/.gitignore +24 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE +20 -0
- data/README.md +132 -0
- data/Rakefile +50 -0
- data/TODO.md +8 -0
- data/VERSION +1 -0
- data/lib/leech.rb +19 -0
- data/lib/leech/client.rb +7 -0
- data/lib/leech/handler.rb +120 -0
- data/lib/leech/handlers/auth.rb +51 -0
- data/lib/leech/server.rb +370 -0
- data/spec/handler_spec.rb +43 -0
- data/spec/handlers/auth_spec.rb +59 -0
- data/spec/server_spec.rb +178 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +96 -0
data/.document
ADDED
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## Jul.29.2010
|
4
|
+
|
5
|
+
*by Kriss Kowalik*
|
6
|
+
|
7
|
+
Released 0.1.0 experimental version for testing. For now it provides only server.
|
8
|
+
|
9
|
+
## Jul.28.2010
|
10
|
+
|
11
|
+
*by Kriss Kowalik*
|
12
|
+
|
13
|
+
### Misc
|
14
|
+
|
15
|
+
* Created github repository
|
16
|
+
* Writen specs for server, handler and auth handler
|
17
|
+
* Writen server, base handler and auth handler
|
18
|
+
* Documentation
|
19
|
+
* Writen README.md
|
20
|
+
* Created TODO.md
|
21
|
+
* Rakefile (prepared for YARD)
|
22
|
+
|
23
|
+
### Created files
|
24
|
+
|
25
|
+
lib/leech.rb, lib/leech/server.rb, lib/leech/handler.rb, lib/leach/client.rb,
|
26
|
+
lib/handlers/auth_spec.rb, spec/spec_helper.rb, spec/spec.opts, spec/server_spec.rb,
|
27
|
+
spec/handler_spec.rb, spec/handlers/auth_spec.rb, LICENSE, CHANGELOG.md, VERSION,
|
28
|
+
README.md, Rakefile
|
29
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Kriss Kowalik
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Leech
|
2
|
+
|
3
|
+
Leech is simple TCP client/server framework. Server is similar to rack,
|
4
|
+
but is designed for asynchronously handling a short text commands. It can
|
5
|
+
be used for some monitoring purposes or simple communication between few
|
6
|
+
machines.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
You can install Leach directly from rubygems:
|
11
|
+
|
12
|
+
gem install leach
|
13
|
+
|
14
|
+
## Gettings started
|
15
|
+
|
16
|
+
Leech is using simple TCP client/server architecture. It's a simple processor
|
17
|
+
for text commands passed through TCP socket.
|
18
|
+
|
19
|
+
### Server
|
20
|
+
|
21
|
+
Server can be created in two ways:
|
22
|
+
|
23
|
+
server = Leech::Server.new :port => 666
|
24
|
+
server.use :auth
|
25
|
+
server.run
|
26
|
+
|
27
|
+
...or using block style:
|
28
|
+
|
29
|
+
Leech::Server.new do
|
30
|
+
port 666
|
31
|
+
use :auth
|
32
|
+
run
|
33
|
+
end
|
34
|
+
|
35
|
+
#### Server configuration
|
36
|
+
|
37
|
+
You can pass frew configuration options when you are creating new server, eg:
|
38
|
+
|
39
|
+
Leech::Server.new(port => 666, :host => 'myhost.com', :timeout => 60)
|
40
|
+
|
41
|
+
For more information about allowed parameters visit
|
42
|
+
[This doc page](http://yardoc.org/doc/Leech/Server.html#initialize-instance_method).
|
43
|
+
|
44
|
+
#### Handlers
|
45
|
+
|
46
|
+
Handlers are extending functionality of server by defining custom
|
47
|
+
command handling callbacks or server instance methods.
|
48
|
+
|
49
|
+
For simple commands handling you can use inline handler, which is automatically
|
50
|
+
used by server, eg.
|
51
|
+
|
52
|
+
Leech::Server.new do
|
53
|
+
handle(/^PRINT) (.*)$/ {|env,params| print params[0]}
|
54
|
+
handle(/^DELETE FILE (.*)$/) {|env,params| File.delete(params[0])}
|
55
|
+
end
|
56
|
+
|
57
|
+
For advanced tasks, you can write own handler, which will add new functionality
|
58
|
+
to server instance.
|
59
|
+
|
60
|
+
class CustomHandler < Leech::Handler
|
61
|
+
# This method is automaticaaly called when server will use this handler
|
62
|
+
def self.used(server)
|
63
|
+
server.instance_eval { extend ServerMethods }
|
64
|
+
end
|
65
|
+
|
66
|
+
module ServerMethods
|
67
|
+
def say_hello
|
68
|
+
answer("Hello!")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
handle /^Hello server!$/, :say_hello
|
73
|
+
handle /^Bye!$/ do |env,params|
|
74
|
+
env.answer("Take care!")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
To enable this handler in the server you can simply type:
|
79
|
+
|
80
|
+
Leech::Server.new do
|
81
|
+
use CustomHandler
|
82
|
+
end
|
83
|
+
|
84
|
+
You should notice that block-style callbacks are passing two arguments:
|
85
|
+
**env** - instance of server which is using this handler for processing,
|
86
|
+
and **params** - array of strings fetched from matched command.
|
87
|
+
|
88
|
+
#### Answering
|
89
|
+
|
90
|
+
For sending answers to clients server have the `#answer` method. It can be used
|
91
|
+
like here:
|
92
|
+
|
93
|
+
Leech::Server.new do
|
94
|
+
handle /^HELLO$/ do |env,params| env.answer("HELLO MY FRIEND!\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
#### Running / Listening
|
98
|
+
|
99
|
+
Server is partialy acting as `Thread`. You can join it's instance so application
|
100
|
+
will be waiting to interrupt or some unhandled server error:
|
101
|
+
|
102
|
+
server = Leech::Server.new
|
103
|
+
server.run
|
104
|
+
server.join # on server.acceptor.join
|
105
|
+
|
106
|
+
You can also simple use:
|
107
|
+
|
108
|
+
server.run.join
|
109
|
+
|
110
|
+
### Client
|
111
|
+
|
112
|
+
Client will be implemented in 0.2.0 version.
|
113
|
+
|
114
|
+
## Links
|
115
|
+
|
116
|
+
* [Author blog](http://neverendingcoding.com/)
|
117
|
+
* [YARD documentation](http://yardoc.org/doc/Leech)
|
118
|
+
* [Changelog](http://yardoc.org/doc/file:CHANGELOG.md)
|
119
|
+
|
120
|
+
## Note on Patches/Pull Requests
|
121
|
+
|
122
|
+
* Fork the project.
|
123
|
+
* Make your feature addition or bug fix.
|
124
|
+
* Add tests for it. This is important so I don't break it in a
|
125
|
+
future version unintentionally.
|
126
|
+
* Commit, do not mess with rakefile, version, or history.
|
127
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
128
|
+
* Send me a pull request. Bonus points for topic branches.
|
129
|
+
|
130
|
+
## Copyright
|
131
|
+
|
132
|
+
Copyright (c) 2010 Kriss Kowalik. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "leech"
|
8
|
+
gem.summary = %Q{Simple TCP client/server framework with commands handling}
|
9
|
+
gem.description = %Q{Leech is simple TCP client/server framework. Server is
|
10
|
+
similar to rack. It allows to define own handlers for received text commands. }
|
11
|
+
gem.email = "kriss.kowalik@gmail.com"
|
12
|
+
gem.homepage = "http://github.com/kriss/leech"
|
13
|
+
gem.authors = ["Kriss Kowalik"]
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'yard'
|
40
|
+
YARD::Rake::YardocTask.new do |t|
|
41
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
+
title = "Leech #{version}"
|
43
|
+
t.files = ['lib/**/*.rb', 'README*']
|
44
|
+
t.options = ['--title', title, '--markup', 'markdown', '--files', 'CHANGELOG.md,TODO.md']
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
task :yard do
|
48
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
49
|
+
end
|
50
|
+
end
|
data/TODO.md
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/leech.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'logger'
|
3
|
+
require 'timeout'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'fastthread'
|
8
|
+
rescue LoadError
|
9
|
+
$stderr.puts("The fastthread gem not found. Using standard ruby threads.")
|
10
|
+
end
|
11
|
+
|
12
|
+
# Leech is simple TCP client/server framework. Server is similar to rack,
|
13
|
+
# but is designed for asynchronously handling a short text commands. It can
|
14
|
+
# be used for some monitoring purposes or simple communication between few
|
15
|
+
# machines.
|
16
|
+
module Leech
|
17
|
+
# Namespace for handlers.
|
18
|
+
module Handlers; end
|
19
|
+
end # Leech
|
data/lib/leech/client.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Leech
|
2
|
+
# Handlers are extending functionality of server by defining custom
|
3
|
+
# command handling callbacks or server instance methods.
|
4
|
+
#
|
5
|
+
# @example Writing own handler
|
6
|
+
# class CustomHandler < Leech::Handler
|
7
|
+
# def self.used(server)
|
8
|
+
# server.instance_eval { extend ServerMethods }
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# module ServerMethods
|
12
|
+
# def say_hello
|
13
|
+
# answer("Hello!")
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# handle /^Hello server!$/, :say_hello
|
18
|
+
# handle /^Bye!$/ do |env,params|
|
19
|
+
# env.answer("Take care!")
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Enabling handlers by server
|
24
|
+
# Leech::Server.new { use CustomHandler }
|
25
|
+
#
|
26
|
+
# @abstract
|
27
|
+
class Handler
|
28
|
+
# Default handler error
|
29
|
+
class Error < StandardError; end
|
30
|
+
|
31
|
+
# List of declared matchers
|
32
|
+
def self.matchers
|
33
|
+
@matchers ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# It defines matching pattern and callback related with. When specified
|
37
|
+
# command will match this pattern then callback will be called.
|
38
|
+
#
|
39
|
+
# @param [Regexp] patern
|
40
|
+
# Block will be executed for passed command will be maching it.
|
41
|
+
# @param [Symbol, nil] method
|
42
|
+
# Name of server method, which should be called when pattern will
|
43
|
+
# match with command
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# handle(/^PRINT) (.*)$/ {|env,params| print params[0]}
|
47
|
+
# handle(/^DELETE FILE (.*)$/) {|env,params| File.delete(params[0])}
|
48
|
+
# handle(/^HELLO$/, :say_hello)
|
49
|
+
#
|
50
|
+
# @raise [Leech::Error]
|
51
|
+
# When specified callback is not valid method name or Proc.
|
52
|
+
def self.handle(pattern, method=nil, &block)
|
53
|
+
if block_given?
|
54
|
+
method = block
|
55
|
+
end
|
56
|
+
if method.is_a?(Proc) || method.is_a?(Symbol)
|
57
|
+
self.matchers[pattern] = method
|
58
|
+
else
|
59
|
+
raise Error, "Invalid handler callback"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# You should implement this method in your handler eg. if you would like
|
64
|
+
# to modify server class.
|
65
|
+
def self.used(server)
|
66
|
+
# nothing...
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Leecher::Server]
|
70
|
+
# Passed server instance
|
71
|
+
attr_reader :env
|
72
|
+
|
73
|
+
# @return [Array<String>]
|
74
|
+
# Parameters matched in passed command
|
75
|
+
attr_reader :params
|
76
|
+
|
77
|
+
# Constructor.
|
78
|
+
#
|
79
|
+
# @param [Leech::Server]
|
80
|
+
# Server instance
|
81
|
+
def initialize(env)
|
82
|
+
@env = env
|
83
|
+
end
|
84
|
+
|
85
|
+
# Compare specified command with declared patterns.
|
86
|
+
#
|
87
|
+
# @param [String] command
|
88
|
+
# Command to handle.
|
89
|
+
#
|
90
|
+
# @return [Leech::Handler,nil]
|
91
|
+
# When command match one of declared patterns then it returns itself,
|
92
|
+
# otherwise it returns nil.
|
93
|
+
def match(command)
|
94
|
+
self.class.matchers.each_pair do |p,m|
|
95
|
+
if @params = p.match(command)
|
96
|
+
@matcher = m
|
97
|
+
return self
|
98
|
+
end
|
99
|
+
end
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# This method can be called only after #match. It executes callback
|
104
|
+
# related with matched pattern.
|
105
|
+
#
|
106
|
+
# @raise [Leech::Error]
|
107
|
+
# When @matcher is not defined, which means that #match method wasn't
|
108
|
+
# called before.
|
109
|
+
def call
|
110
|
+
case @matcher
|
111
|
+
when Proc
|
112
|
+
@matcher.call(env, params)
|
113
|
+
when String, Symbol
|
114
|
+
env.send(@matcher.to_sym, params)
|
115
|
+
else
|
116
|
+
raise Error, "Can not call unmatched command"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end # Handler
|
120
|
+
end # Leech
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Leech
|
2
|
+
module Handlers
|
3
|
+
# Simple authorization handler. It uses password string (passcode) for
|
4
|
+
# authorize client session.
|
5
|
+
#
|
6
|
+
# ### Usage
|
7
|
+
# Leech::Server.new { use :auth }
|
8
|
+
#
|
9
|
+
# ### Supported commands
|
10
|
+
# AUTHORIZE passcode
|
11
|
+
#
|
12
|
+
# ### Possible Answers
|
13
|
+
# UNAUTHORIZED
|
14
|
+
# AUTHORIZED
|
15
|
+
class Auth < Handler
|
16
|
+
def self.used(server)
|
17
|
+
server.instance_eval do
|
18
|
+
extend ServerMethods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ServerMethods
|
23
|
+
# Authorize client session using simple passcode.
|
24
|
+
#
|
25
|
+
# @param [String] passcode
|
26
|
+
# Password sent by client
|
27
|
+
def authorize(passcode)
|
28
|
+
if options[:passcode].to_s.strip == passcode.to_s.strip
|
29
|
+
Thread.current[:authorized] = true
|
30
|
+
answer("AUTHORIZED\n")
|
31
|
+
logger.info("Client #{info[:uri]} authorized")
|
32
|
+
else
|
33
|
+
Thread.current[:authorized] = false
|
34
|
+
answer("UNAUTHORIZED\n")
|
35
|
+
logger.info("Client #{info[:uri]} unauthorized: invalid passcode")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean]
|
40
|
+
# Is client session authorized?
|
41
|
+
def authorized?
|
42
|
+
!!Thread.current[:authorized]
|
43
|
+
end
|
44
|
+
end # ServerMethods
|
45
|
+
|
46
|
+
# Available commands
|
47
|
+
|
48
|
+
handle(/^AUTHORIZE[\s+]?(.*)?$/m) {|env,params| env.authorize(params[1])}
|
49
|
+
end # AuthHandler
|
50
|
+
end # Handlers
|
51
|
+
end # Leech
|
data/lib/leech/server.rb
ADDED
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'leech'
|
2
|
+
require 'leech/handler'
|
3
|
+
|
4
|
+
module Leech
|
5
|
+
# This is simple TCP server similar to rack, but designed for asynchronously
|
6
|
+
# handling a short text commands. It can be used for some monitoring purposes
|
7
|
+
# or simple communication between few machines.
|
8
|
+
#
|
9
|
+
# ### Creating server instance
|
10
|
+
#
|
11
|
+
# Server can be created in two ways:
|
12
|
+
#
|
13
|
+
# server = Leech::Server.new :port => 666
|
14
|
+
# server.use :auth
|
15
|
+
# server.run
|
16
|
+
#
|
17
|
+
# ...or using block style:
|
18
|
+
#
|
19
|
+
# Leech::Server.new do
|
20
|
+
# port 666
|
21
|
+
# use :auth
|
22
|
+
# run
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# ### Complete server example
|
26
|
+
#
|
27
|
+
# Simple server with authorization and few commands handling can be configured
|
28
|
+
# such like this one:
|
29
|
+
#
|
30
|
+
# server = Leech::Server.new do
|
31
|
+
# # simple authorization handler, see Leech::AuthHandler for
|
32
|
+
# # more informations.
|
33
|
+
# use :auth
|
34
|
+
#
|
35
|
+
# host 'localhost'
|
36
|
+
# port 666
|
37
|
+
# max_workers 100
|
38
|
+
# timeout 30
|
39
|
+
# logger Logger.new('/var/logs/leech/server.log')
|
40
|
+
#
|
41
|
+
# handle /^SHOW FILE (.*)$/ do |env,params|
|
42
|
+
# if File.exists?(param[1])
|
43
|
+
# answer(File.open(params[0]).read)
|
44
|
+
# else
|
45
|
+
# answer('NOT FOUND')
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# handle /^DELETE FILE (.*)$/ do |env,params|
|
49
|
+
# answer(File.delete(params[1]))
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# run
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# # Now we have to join server thread with main thread.
|
56
|
+
# server.join
|
57
|
+
class Server
|
58
|
+
# Default server error
|
59
|
+
class Error < StandardError; end
|
60
|
+
|
61
|
+
# Used to stop server via Thread#raise
|
62
|
+
class StopServer < Error; end
|
63
|
+
|
64
|
+
# Thrown at a thread when it is timed out.
|
65
|
+
class TimeoutError < Timeout::Error; end
|
66
|
+
|
67
|
+
# Server main thread
|
68
|
+
#
|
69
|
+
# @return [Thread]
|
70
|
+
attr_reader :acceptor
|
71
|
+
|
72
|
+
# Server configuration
|
73
|
+
#
|
74
|
+
# @return [Hash]
|
75
|
+
attr_reader :options
|
76
|
+
|
77
|
+
# Server will bind to this host
|
78
|
+
#
|
79
|
+
# @return [String]
|
80
|
+
attr_reader :host
|
81
|
+
|
82
|
+
# Server will be listening on this port
|
83
|
+
#
|
84
|
+
# @return [Int]
|
85
|
+
attr_reader :port
|
86
|
+
|
87
|
+
# Logging object
|
88
|
+
#
|
89
|
+
# @return [Logger]
|
90
|
+
attr_reader :logger
|
91
|
+
|
92
|
+
# The maximum number of concurrent processors to accept, anything over
|
93
|
+
# this is closed immediately to maintain server processing performance.
|
94
|
+
# This may seem mean but it is the most efficient way to deal with overload.
|
95
|
+
# Other schemes involve still parsing the client's request wchich defeats
|
96
|
+
# the point of an overload handling system.
|
97
|
+
#
|
98
|
+
# @return [Int,Float]
|
99
|
+
attr_reader :max_workers
|
100
|
+
|
101
|
+
# Maximum idle time
|
102
|
+
#
|
103
|
+
# @return [Int,Float]
|
104
|
+
attr_reader :timeout
|
105
|
+
|
106
|
+
# A sleep timeout (in hundredths of a second) that is placed between
|
107
|
+
# socket.accept calls in order to give the server a cheap throttle time.
|
108
|
+
# It defaults to 0 and actually if it is 0 then the sleep is not done
|
109
|
+
# at all.
|
110
|
+
#
|
111
|
+
# @return [Int,Float]
|
112
|
+
attr_reader :throttle
|
113
|
+
|
114
|
+
# Here we have to define block-style setters for each startup parameter.
|
115
|
+
%w{host port logger throttle timeout max_workers}.each do |meth|
|
116
|
+
eval <<-EVAL
|
117
|
+
def #{meth}(*args)
|
118
|
+
@#{meth} = args.first if args.size > 0
|
119
|
+
@#{meth}
|
120
|
+
end
|
121
|
+
EVAL
|
122
|
+
end
|
123
|
+
|
124
|
+
# Creates a working server on host:port. Use #run to start the server
|
125
|
+
# and `acceptor.join` to join the thread that's processing incoming requests
|
126
|
+
# on the socket.
|
127
|
+
#
|
128
|
+
# @param [Hash] opts see #options
|
129
|
+
# @option opts [String] :host ('localhost') see #host
|
130
|
+
# @option opts [Int] :port (9933) see #port
|
131
|
+
# @option opts [Logger] :logger (Logger.new(STDOUT)) see #logger
|
132
|
+
# @option opts [Int] :max_workers (100) see #max_workers
|
133
|
+
# @option opts [Int,Float] :timeout (30) see #timeout
|
134
|
+
# @option opts [Int,Float] :throttle (0) see #throttle
|
135
|
+
#
|
136
|
+
# @see Leech::Server#run
|
137
|
+
def initialize(opts={}, &block)
|
138
|
+
@handlers = []
|
139
|
+
@workers = ThreadGroup.new
|
140
|
+
@acceptor = nil
|
141
|
+
@mutex = Mutex.new
|
142
|
+
|
143
|
+
@options = opts
|
144
|
+
@host = opts[:host] || 'localhost'
|
145
|
+
@port = opts[:port] || 9933
|
146
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
147
|
+
@max_workers = opts[:max_workers] || 100
|
148
|
+
@timeout = opts[:timeout] || 30
|
149
|
+
@throttle = opts[:throttle].to_i / 100.0
|
150
|
+
|
151
|
+
@inline_handler = Class.new(Leech::Handler)
|
152
|
+
instance_eval(&block) if block_given?
|
153
|
+
end
|
154
|
+
|
155
|
+
# Port to acceptor thread #join method.
|
156
|
+
#
|
157
|
+
# @see Thread#join
|
158
|
+
def join
|
159
|
+
@acceptor.join if @acceptor
|
160
|
+
end
|
161
|
+
|
162
|
+
# It registers specified handler for using in this server instance.
|
163
|
+
# Handlers are extending functionality of server by defining custom
|
164
|
+
# command handling callbacks or server instance methods.
|
165
|
+
#
|
166
|
+
# @param [Leech::Handler, Symbol] handler
|
167
|
+
# Handler class or name
|
168
|
+
#
|
169
|
+
# @raise [Leech::Server::Error,Leech::Handler::Error]
|
170
|
+
# When specified adapter was not found or handler is invalid
|
171
|
+
#
|
172
|
+
# @see Leech::Handler
|
173
|
+
def use(handler)
|
174
|
+
case handler
|
175
|
+
when Class
|
176
|
+
@handlers << handler
|
177
|
+
handler.used(self)
|
178
|
+
when Symbol, String
|
179
|
+
begin
|
180
|
+
require "leech/handlers/#{handler.to_s}"
|
181
|
+
klass = handler.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
182
|
+
use(eval("Leech::Handlers::#{klass}"))
|
183
|
+
rescue LoadError
|
184
|
+
raise Error, "Could not find #{handler} handler"
|
185
|
+
end
|
186
|
+
else
|
187
|
+
raise Leech::Handler::Error, "Invalid handler #{handler}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# It defines matching pattern in @inline_handler. It is similar to
|
192
|
+
# `Leech::Handler#handle` but can be used only in block-style.
|
193
|
+
#
|
194
|
+
# @param [Regexp] patern
|
195
|
+
# Block will be executed for passed command will be maching it.
|
196
|
+
#
|
197
|
+
# @example
|
198
|
+
# Leech::Server.new do
|
199
|
+
# handle(/^PRINT) (.*)$/ {|env,params| print params[0]}
|
200
|
+
# handle(/^DELETE FILE (.*)$/) {|env,params| File.delete(params[0])}
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# @see Leech::Handler#handle
|
204
|
+
def handle(pattern, &block)
|
205
|
+
@inline_handler.handle(pattern, &block)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Used internally to kill off any worker threads that have taken too long
|
209
|
+
# to complete processing. Only called if there are too many processors
|
210
|
+
# currently servicing. It returns the count of workers still active
|
211
|
+
# after the reap is done. It only runs if there are workers to reap.
|
212
|
+
#
|
213
|
+
# @param [String] reason
|
214
|
+
# Reason why method was executed
|
215
|
+
#
|
216
|
+
# @return [Array<Thread>]
|
217
|
+
# List of still active workers threads.
|
218
|
+
def reap_dead_workers(reason='unknown')
|
219
|
+
if @workers.list.length > 0
|
220
|
+
logger.error "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
|
221
|
+
error_msg = "Leech timed out this thread: #{reason}"
|
222
|
+
mark = Time.now
|
223
|
+
@workers.list.each do |worker|
|
224
|
+
worker[:started_on] = Time.now if not worker[:started_on]
|
225
|
+
if mark - worker[:started_on] > @timeout + @throttle
|
226
|
+
logger.error "Thread #{worker.inspect} is too old, killing."
|
227
|
+
worker.raise(TimeoutError.new(error_msg))
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
return @workers.list.length
|
232
|
+
end
|
233
|
+
|
234
|
+
# Performs a wait on all the currently running threads and kills any that take
|
235
|
+
# too long. It waits by `@timeout seconds`, which can be set in `#initialize`.
|
236
|
+
# The `@throttle` setting does extend this waiting period by that much longer.
|
237
|
+
def graceful_shutdown
|
238
|
+
while reap_dead_workers("shutdown") > 0
|
239
|
+
logger.error "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds."
|
240
|
+
sleep @timeout / 10
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
245
|
+
# off the request queue before finally exiting. It's also reseting freezed
|
246
|
+
# settings.
|
247
|
+
def stop
|
248
|
+
if running?
|
249
|
+
@acceptor.raise(StopServer.new)
|
250
|
+
@handlers = Array.new(@handlers)
|
251
|
+
@options = Hash.new(@options)
|
252
|
+
sleep(0.5) while @acceptor.alive?
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Starts serving TCP listener on host and port declared in options.
|
257
|
+
# It returns the thread used so you can join it. Each client connection
|
258
|
+
# will be processed in separated thread.
|
259
|
+
#
|
260
|
+
# @return [Thread]
|
261
|
+
# Main thread for this server instance.
|
262
|
+
def run
|
263
|
+
use(@inline_handler)
|
264
|
+
@socket = TCPServer.new(@host, @port)
|
265
|
+
@handlers = @handlers.uniq.freeze
|
266
|
+
@options = @options.freeze
|
267
|
+
@acceptor = Thread.new do
|
268
|
+
begin
|
269
|
+
logger.debug "Starting leech server on tcp://#{@host}:#{@port}"
|
270
|
+
loop do
|
271
|
+
begin
|
272
|
+
client = @socket.accept
|
273
|
+
worker_list = @workers.list
|
274
|
+
|
275
|
+
if worker_list.length >= @max_workers
|
276
|
+
logger.error "Server overloaded with #{worker_list.length} workers (#@max_workers max). Dropping connection."
|
277
|
+
client.close rescue nil
|
278
|
+
reap_dead_workers("max processors")
|
279
|
+
else
|
280
|
+
thread = Thread.new(client) {|c| process_client(c) }
|
281
|
+
thread[:started_on] = Time.now
|
282
|
+
@workers.add(thread)
|
283
|
+
sleep @throttle if @throttle > 0
|
284
|
+
end
|
285
|
+
rescue StopServer
|
286
|
+
break
|
287
|
+
rescue Errno::EMFILE
|
288
|
+
reap_dead_workers("too many open files")
|
289
|
+
sleep 0.5
|
290
|
+
rescue Errno::ECONNABORTED
|
291
|
+
client.close rescue nil
|
292
|
+
rescue Object => e
|
293
|
+
logger.error "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
|
294
|
+
logger.error e.backtrace.join("\n")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
graceful_shutdown
|
298
|
+
ensure
|
299
|
+
@socket.close
|
300
|
+
logger.debug "Closing leech server on tcp://#{@host}:#{@port}"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
return @acceptor
|
305
|
+
end
|
306
|
+
|
307
|
+
# It is getting information about client connection and starts conversation
|
308
|
+
# with him. Received commands are passed to declared handlers, where will
|
309
|
+
# be processed.
|
310
|
+
#
|
311
|
+
# @param [TCPSocket] c
|
312
|
+
# Client socket
|
313
|
+
def process_client(c)
|
314
|
+
Thread.current[:client] = c
|
315
|
+
Thread.current[:info] = {
|
316
|
+
:port => client.peeraddr[1],
|
317
|
+
:host => client.peeraddr[2],
|
318
|
+
:addr => client.peeraddr[3],
|
319
|
+
}
|
320
|
+
info[:uri] = [info[:host], info[:port]].join(':')
|
321
|
+
logger.debug "Processing client from #{info[:uri]}"
|
322
|
+
while line = client.gets
|
323
|
+
line = line.chomp.strip
|
324
|
+
logger.info "Dispatching command (#{info[:uri]}): #{line}"
|
325
|
+
@handlers.each do |handler|
|
326
|
+
if handler = handler.new(self).match(line.chomp.strip)
|
327
|
+
handler.call
|
328
|
+
next
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Sends answer to current connected socket. Method should be called only
|
335
|
+
# in worker thread.
|
336
|
+
#
|
337
|
+
# @param [String] msg
|
338
|
+
# Text to send
|
339
|
+
def answer(msg)
|
340
|
+
logger.debug("Answering to (#{info[:uri]}): #{msg.chomp.strip}")
|
341
|
+
client.puts(msg)
|
342
|
+
end
|
343
|
+
alias_method :say, :answer
|
344
|
+
|
345
|
+
# @return [Boolean]
|
346
|
+
# Actual server state. Returns `true` server acceptor thread is alive.
|
347
|
+
def running?
|
348
|
+
@acceptor && @acceptor.alive?
|
349
|
+
end
|
350
|
+
|
351
|
+
private
|
352
|
+
|
353
|
+
# Informations about connected client. Method should be called only in
|
354
|
+
# woker thread.
|
355
|
+
#
|
356
|
+
# @return [Hash]
|
357
|
+
# Client informations such as host, remote address and port
|
358
|
+
def info
|
359
|
+
Thread.current[:info]
|
360
|
+
end
|
361
|
+
|
362
|
+
# Client socket from. Method should be called only in woker thread.
|
363
|
+
#
|
364
|
+
# @return [TCPSocket]
|
365
|
+
# Client socket
|
366
|
+
def client
|
367
|
+
Thread.current[:client]
|
368
|
+
end
|
369
|
+
end # Server
|
370
|
+
end # Leech
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require 'socket'
|
3
|
+
require 'leech/handler'
|
4
|
+
|
5
|
+
describe "An Leech server handler" do
|
6
|
+
before do
|
7
|
+
@test_handler = Class.new(Leech::Handler)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should receive #used method when it's used by server" do
|
11
|
+
srv = Leech::Server.new
|
12
|
+
@test_handler.should_receive(:used).once.with(srv)
|
13
|
+
srv.use(@test_handler)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "saves custom matchers described in #handle method" do
|
17
|
+
@test_handler.class_eval do
|
18
|
+
handle(/^TESTING$/, :test)
|
19
|
+
handle(/^ANOTHER$/) {|env,params|}
|
20
|
+
end
|
21
|
+
@test_handler.matchers.keys.size.should == 2
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should dispatch command to valid matcher" do
|
25
|
+
@test_handler.class_eval do
|
26
|
+
handle /^HELLO$/, :hello
|
27
|
+
handle /^SECOND (.*)$/ do |env,params| "Hello" end
|
28
|
+
end
|
29
|
+
env = OpenStruct.new
|
30
|
+
env.class_eval { define_method(:hello) {|p| }}
|
31
|
+
th = @test_handler.new(env)
|
32
|
+
th.match('HELLO').should be_kind_of(Leech::Handler)
|
33
|
+
env.should_receive(:hello).once.with(an_instance_of(MatchData))
|
34
|
+
th.call
|
35
|
+
th = @test_handler.new(env)
|
36
|
+
@test_handler.matchers[/^SECOND (.*)$/].should_receive(:call).once.with(env, an_instance_of(MatchData))
|
37
|
+
th.match('SECOND yadayada')
|
38
|
+
th.call
|
39
|
+
th = @test_handler.new(env)
|
40
|
+
th.match('NOT EXIST').should == nil
|
41
|
+
lambda { th.call }.should raise_error(Leech::Handler::Error)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper')
|
2
|
+
require 'socket'
|
3
|
+
require 'leech/server'
|
4
|
+
require 'leech/handler'
|
5
|
+
require 'leech/handlers/auth'
|
6
|
+
|
7
|
+
describe "An Leech Auth handler" do
|
8
|
+
before do
|
9
|
+
@srv = Leech::Server.new(:logger => Logger.new(StringIO.new))
|
10
|
+
@srv.use :auth
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should append #authorize and #authorized? methods to server instance" do
|
14
|
+
@srv.should respond_to :authorize
|
15
|
+
@srv.should respond_to :authorized?
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should define matcher for AUTHORIZE command" do
|
19
|
+
Leech::Handlers::Auth.matchers.size.should == 1
|
20
|
+
pattern = Leech::Handlers::Auth.matchers.keys.first
|
21
|
+
'AUTHORIZE'.should =~ pattern
|
22
|
+
'AUTHORIZE passcode'.should =~ pattern
|
23
|
+
end
|
24
|
+
|
25
|
+
context "powered server" do
|
26
|
+
it "should authorize client with empty passcode when options[:passcode] is not set" do
|
27
|
+
@srv.run
|
28
|
+
sock = TCPSocket.new(@srv.host, @srv.port)
|
29
|
+
sleep 1
|
30
|
+
sock.puts("AUTHORIZE\n")
|
31
|
+
sock.gets.should == "AUTHORIZED\n"
|
32
|
+
sock.close
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should authorize client with valid passcode" do
|
36
|
+
@srv.options[:passcode] = 'secret'
|
37
|
+
@srv.run
|
38
|
+
sock = TCPSocket.new(@srv.host, @srv.port)
|
39
|
+
sleep 1
|
40
|
+
sock.puts("AUTHORIZE secret\n")
|
41
|
+
sock.gets.should == "AUTHORIZED\n"
|
42
|
+
sock.close
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not authorize client with invalid passcode" do
|
46
|
+
@srv.options[:passcode] = 'secret'
|
47
|
+
@srv.run
|
48
|
+
sock = TCPSocket.new(@srv.host, @srv.port)
|
49
|
+
sleep 1
|
50
|
+
sock.puts("AUTHORIZE not-secret\n")
|
51
|
+
sock.gets.should == "UNAUTHORIZED\n"
|
52
|
+
sock.close
|
53
|
+
end
|
54
|
+
|
55
|
+
after do
|
56
|
+
@srv.stop
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require 'socket'
|
3
|
+
require 'leech/server'
|
4
|
+
|
5
|
+
NULL_LOGGER = Logger.new(StringIO.new)
|
6
|
+
|
7
|
+
describe "An new Leech server" do
|
8
|
+
it "should properly store passed arguments" do
|
9
|
+
srv = Leech::Server.new(:host => "host", :port => 111, 1 => 2)
|
10
|
+
srv.host.should == "host"
|
11
|
+
srv.port.should == 111
|
12
|
+
srv.options[1] = 2
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should recognize max_workers option in passed arguments" do
|
16
|
+
srv = Leech::Server.new(:host => "host.com", :port => 111, :max_workers => 10)
|
17
|
+
srv.max_workers.should == 10
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should recognize timeout option in passed arguments" do
|
21
|
+
srv = Leech::Server.new(:host => "host.com", :port => 111, :timeout => 100)
|
22
|
+
srv.timeout.should == 100
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should recognize max_workers option in passed arguments" do
|
26
|
+
srv = Leech::Server.new(:host => "host.com", :port => 111, :max_workers => 10)
|
27
|
+
srv.max_workers.should == 10
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should recognize logger option in passed arguments" do
|
31
|
+
srv = Leech::Server.new(:host => "host.com", :port => 111, :logger => "logger")
|
32
|
+
srv.logger.should == "logger"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should recognize throttle option in passed arguments" do
|
36
|
+
srv = Leech::Server.new(:host => "host.com", :port => 111, :throttle => 10)
|
37
|
+
srv.throttle.should == 10 / 100.0
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should allow to pass arguments in block" do
|
41
|
+
srv = Leech::Server.new do
|
42
|
+
use(Leech::Handler)
|
43
|
+
host 'myhost.com'
|
44
|
+
port 12345
|
45
|
+
end
|
46
|
+
srv.host.should == 'myhost.com'
|
47
|
+
srv.port.should == 12345
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "An instnace of Leech server" do
|
52
|
+
before do
|
53
|
+
@srv = Leech::Server.new(:port => 'localhost', :port => 1234, :timeout => 3,
|
54
|
+
:max_workers => 5, :logger => NULL_LOGGER)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should allow to use additional handlers" do
|
58
|
+
@srv.use(handler = Class.new(Leech::Handler))
|
59
|
+
@srv.instance_variable_get('@handlers').should include(handler)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should provide inline handler" do
|
63
|
+
@srv.handle(/^TEST$/) {|env,params| }
|
64
|
+
inline_handler = @srv.instance_variable_get('@inline_handler')
|
65
|
+
inline_handler.matchers.keys.size.should == 1
|
66
|
+
inline_handler.matchers.should have_key(/^TEST$/)
|
67
|
+
end
|
68
|
+
|
69
|
+
context "on run" do
|
70
|
+
before do
|
71
|
+
@srv.run
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should freeze handlers list" do
|
75
|
+
@srv.instance_variable_get('@handlers').frozen?.should == true
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should change it's running state" do
|
79
|
+
@srv.running?.should == true
|
80
|
+
end
|
81
|
+
|
82
|
+
after do
|
83
|
+
@srv.stop
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "on stop" do
|
88
|
+
before do
|
89
|
+
@srv.run
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should change it's running state" do
|
93
|
+
@srv.stop
|
94
|
+
@srv.running?.should == false
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should unfreeze handlers list" do
|
98
|
+
frozen = @srv.instance_variable_get('@handlers')
|
99
|
+
@srv.stop if @srv.running?
|
100
|
+
unfrozen = @srv.instance_variable_get('@handlers')
|
101
|
+
unfrozen.frozen?.should == false
|
102
|
+
unfrozen.should == frozen
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "on client connection" do
|
107
|
+
before do
|
108
|
+
@srv.handle(/^TEST MESSAGE$/) {|env,params| env.answer("HELLO\n") }
|
109
|
+
@srv.run
|
110
|
+
@sock = TCPSocket.new(@srv.host, @srv.port)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should create new worker for it" do
|
114
|
+
sleep 1
|
115
|
+
@srv.instance_variable_get('@workers').list.size.should == 1
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should get client informations" do
|
119
|
+
sleep 1
|
120
|
+
info = @srv.instance_variable_get('@workers').list.first[:info]
|
121
|
+
info.should be_kind_of(Hash)
|
122
|
+
info.should have_key(:host)
|
123
|
+
info.should have_key(:port)
|
124
|
+
info.should have_key(:addr)
|
125
|
+
info.should have_key(:uri)
|
126
|
+
end
|
127
|
+
|
128
|
+
context "when command is received" do
|
129
|
+
it "should handle it by matching handler" do
|
130
|
+
@sock.puts("TEST MESSAGE\n")
|
131
|
+
answer = @sock.gets
|
132
|
+
answer.should == "HELLO\n"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
after do
|
137
|
+
@sock.close
|
138
|
+
@srv.stop
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should handle multiple connections" do
|
143
|
+
@srv.handle(/^MESSAGE FROM (.*)$/) {|env,params| env.answer("HELLO #{params[1]}\n") }
|
144
|
+
@srv.run
|
145
|
+
sock1 = TCPSocket.new(@srv.host, @srv.port)
|
146
|
+
sock2 = TCPSocket.new(@srv.host, @srv.port)
|
147
|
+
sock3 = TCPSocket.new(@srv.host, @srv.port)
|
148
|
+
sleep 1
|
149
|
+
@srv.instance_variable_get('@workers').list.size.should == 3
|
150
|
+
sock1.close
|
151
|
+
sock2.close
|
152
|
+
sock3.close
|
153
|
+
@srv.stop
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should handle multiple connections" do
|
157
|
+
@srv.run
|
158
|
+
sock1 = TCPSocket.new(@srv.host, @srv.port)
|
159
|
+
sock2 = TCPSocket.new(@srv.host, @srv.port)
|
160
|
+
sock3 = TCPSocket.new(@srv.host, @srv.port)
|
161
|
+
sleep 1
|
162
|
+
@srv.instance_variable_get('@workers').list.size.should == 3
|
163
|
+
sock1.close
|
164
|
+
sock2.close
|
165
|
+
sock3.close
|
166
|
+
@srv.stop
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should respect :max_workers option" do
|
170
|
+
@srv.run
|
171
|
+
sockets = []
|
172
|
+
8.times { sockets << TCPSocket.new(@srv.host, @srv.port) }
|
173
|
+
sleep 1
|
174
|
+
@srv.instance_variable_get('@workers').list.size.should == 5
|
175
|
+
sockets.each {|s| s.close }
|
176
|
+
@srv.stop
|
177
|
+
end
|
178
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: leech
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Kriss Kowalik
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-28 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 9
|
31
|
+
version: 1.2.9
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: "Leech is simple TCP client/server framework. Server is\n similar to rack. It allows to define own handlers for received text commands. "
|
35
|
+
email: kriss.kowalik@gmail.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- LICENSE
|
42
|
+
- README.md
|
43
|
+
files:
|
44
|
+
- .document
|
45
|
+
- .gitignore
|
46
|
+
- CHANGELOG.md
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- TODO.md
|
51
|
+
- VERSION
|
52
|
+
- lib/leech.rb
|
53
|
+
- lib/leech/client.rb
|
54
|
+
- lib/leech/handler.rb
|
55
|
+
- lib/leech/handlers/auth.rb
|
56
|
+
- lib/leech/server.rb
|
57
|
+
- spec/handler_spec.rb
|
58
|
+
- spec/handlers/auth_spec.rb
|
59
|
+
- spec/server_spec.rb
|
60
|
+
- spec/spec.opts
|
61
|
+
- spec/spec_helper.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/kriss/leech
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options:
|
68
|
+
- --charset=UTF-8
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.3.6
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Simple TCP client/server framework with commands handling
|
92
|
+
test_files:
|
93
|
+
- spec/handler_spec.rb
|
94
|
+
- spec/server_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- spec/handlers/auth_spec.rb
|