leech 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|