crap_server 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eedef645e51d16d796efdd9893f69809adce20d0
4
+ data.tar.gz: e220bbd8a3878f29037216942fb3cc00ba5d4e60
5
+ SHA512:
6
+ metadata.gz: f2e78629c99e7564e0352f3a754623b7108e84ed9f695ce7f5f12e9f1c0ab1829830223f2f16aa43b343ed0423e8eb37f1f3e046a5939ba5bf48ffef88630163
7
+ data.tar.gz: 167fcab8bc1047971cee75ad00de1e0297b245e455de3210ce7a3dce0e8b032df6a0e94143a9ee49f1d5bd8a9e2b57418ab765164838228970e51edc7aa1336b
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .idea/
24
+ .idea/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in crap_server.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andres B.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # CrapServer
2
+
3
+ Really thin a non intuitive ruby server and framework. Made to be fast and ready for really heavy servers (not only http server).
4
+
5
+ # Another one?
6
+
7
+ Yes. Why? because 2 main reasons. First, this is not a HTTP Web server, this is a generic server that can be used for any kind of TCP Socket server.
8
+ Second and most important, because if funny :)
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'crap_server'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install crap_server
23
+
24
+ ## Basic Usage
25
+
26
+ CrapServer::Application.run! do |data|
27
+ if data =~ /^GET/
28
+ write "Hello world"
29
+ elsif data =~ /^SET/
30
+ write "Setting value"
31
+ else
32
+ write "Something is wrong"
33
+ end
34
+ end
35
+
36
+ ## Configuring the app
37
+
38
+ CrapServer::Application.configure do |config|
39
+ config.port = 80
40
+ config.read_method = :partial
41
+ config.read_buffer_size = 1024 # 1K
42
+ end
43
+
44
+ See all available options in lib/crap_server/configure.rb
45
+
46
+ # Running our application
47
+
48
+ ruby my_app.rb
49
+
50
+ # Production ready?
51
+
52
+ No. At the moment is only a thin server that abstract you from TCP sockets works.
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( https://github.com/[my-github-username]/crap_server/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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 'crap_server/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "crap_server"
8
+ spec.version = CrapServer::VERSION
9
+ spec.authors = ["Andres Joser Borek"]
10
+ spec.email = ["andres.b.dev@gmail.com"]
11
+ spec.summary = %q{Really thin a non intuitive ruby server and framework.}
12
+ spec.description = %q{Really thin a non intuitive ruby server and framework. Made to be fast and ready for really heavy servers (not only http server).}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ # celluloid is for future use. Right now is not used
23
+ # spec.add_dependency "celluloid", '~> 0.15.2'
24
+ end
@@ -0,0 +1,8 @@
1
+ require 'crap_server/version'
2
+ require 'crap_server/configure'
3
+ require 'crap_server/connection_instance'
4
+ require 'crap_server/helpers/socket_reader'
5
+ require 'crap_server/application'
6
+ module CrapServer
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,196 @@
1
+ require 'socket'
2
+ require 'logger'
3
+
4
+ module CrapServer
5
+ class ConnectionError < StandardError; end
6
+ # Example:
7
+ #
8
+ # Crap::Application.run! do |data|
9
+ # if data =~ /^GET/i
10
+ # write SomeDatabase.get('column')
11
+ # elsif data =~/SET/
12
+ # SomeDatabase.set('column', data.split(' ')[1])
13
+ # else
14
+ # write 'Error. Invalid action.'
15
+ # end
16
+ # end
17
+ class Application
18
+ class << self
19
+
20
+ def configure(&block)
21
+ @config ||= CrapServer::Configure.new
22
+ block.yield @config
23
+ end
24
+
25
+ # Main method. This setup all the connections and make the logic of the app
26
+ def run!(&block)
27
+
28
+ # Bup the maximum opened file to the maximum allowed by the system
29
+ Process.setrlimit(:NOFILE, Process.getrlimit(:NOFILE)[1])
30
+
31
+ # Start IPv4 and IPv6 connection for the current port
32
+ open_connections
33
+
34
+
35
+ # Some log info to the user :)
36
+ logger.info 'Initializing Crap Server'
37
+ logger.info "Listening 0.0.0.0:#{config.port}"
38
+ logger.debug "Maximum allowed waiting connections: #{Socket::SOMAXCONN}"
39
+ logger.debug "Maximum number of allowed connections: #{Process.getrlimit(:NOFILE)[1]}" # Same as maximum of opened files
40
+ logger.info ''
41
+
42
+ # The main loop. Listening IPv4 and IPv6 connections
43
+ Socket.accept_loop([socket_ipv4, socket_ipv6]) do |remote_socket, address_info|
44
+ connection_loop(remote_socket, address_info, &block)
45
+ end
46
+
47
+ close_connections
48
+ # If any kind of error happens, we MUST close the sockets
49
+ rescue => e
50
+ close_connections
51
+
52
+ raise e
53
+ end
54
+
55
+ protected
56
+ def connection_loop(remote_socket, addres_info, &block)
57
+ # Work with the connection...
58
+ if we_should_read?
59
+ reader = CrapServer::Helpers::SocketReader.new(remote_socket, config.method)
60
+ reader.address = addres_info
61
+ reader.config = config
62
+ reader.on_message(&block)
63
+ else
64
+ begin
65
+ if block.parameters == 1
66
+ block.call(remote_socket)
67
+ else
68
+ block.call(remote_socket, addres_info)
69
+ end
70
+ # If we get out of data to read (but still having an opened connection), we wait for new data.
71
+ rescue IO::WaitReadable
72
+ # This, prevent to execute so many retry and block the code until a new bunch of data gets available
73
+ IO.select([remote_socket])
74
+ # Yay!, we have more data. Now we can continue!
75
+ retry
76
+ end
77
+ end
78
+ # ...
79
+
80
+ # Close the connection
81
+ remote_socket.close if config.auto_close_connection
82
+ end
83
+
84
+ # Return true or false if the read process is done by the server.
85
+ def we_should_read?
86
+ not config.manual_read
87
+ end
88
+
89
+ # Open TCP connection (IPv4 and IPv6)
90
+ def open_connections
91
+ start_ipv4_socket
92
+ start_ipv6_socket
93
+ end
94
+
95
+ # Close all the sockets.
96
+ def close_connections
97
+ # If any kind of error happens, we MUST close the sockets
98
+ if socket_ipv4
99
+ # Shuts down communication on all copies of the connection.
100
+ socket_ipv4.shutdown
101
+ socket_ipv4.close
102
+ end
103
+
104
+ if socket_ipv6
105
+ # Shuts down communication on all copies of the connection.
106
+ socket_ipv6.shutdown
107
+ socket_ipv6.close
108
+ end
109
+ # TODO: Close all opened sockets connections from other threads and processes
110
+ end
111
+
112
+ def start_ipv6_socket
113
+ # :INET6 is to open an IPv6 connection
114
+ # :STREAM is to open a TCP socket
115
+ # After this line, the app is not yet ready to work, we only opened a socket
116
+ socket_ipv6 = Socket.new(:INET6, :STREAM)
117
+
118
+ begin
119
+ # Now, bind the port.
120
+ # ::1 is loopback for IPv6
121
+ socket_ipv6.bind(Socket.pack_sockaddr_in(config.port, '::1'))
122
+ rescue Errno::EADDRINUSE
123
+ socket_ipv6.close
124
+ raise ConnectionError.new "Unable to bind #{config.port} port."
125
+ end
126
+ socket_ipv6.listen(config.max_pending_connections)
127
+ # Tell to the Kernel that is ok to rebind the port if is in TIME_WAIT state (after close the connection
128
+ # and the Kernel wait for client acknowledgement)
129
+ socket_ipv6.setsockopt(:SOCKET, :REUSEADDR, true)
130
+ @socket6 = socket_ipv6
131
+ end
132
+
133
+ def start_ipv4_socket
134
+ # :INET6 is to open an IPv6 connection
135
+ # :STREAM is to open a TCP socket
136
+ # After this line, the app is not yet ready to work, we only opened a socket
137
+ socket_ipv4 = Socket.new(:INET, :STREAM)
138
+
139
+ begin
140
+ # Now, bind the port.
141
+ socket_ipv4.bind(Socket.pack_sockaddr_in(config.port, '0.0.0.0'))
142
+ rescue Errno::EADDRINUSE
143
+ socket_ipv4.close
144
+ raise ConnectionError.new "Unable to bind #{config.port} port."
145
+ end
146
+
147
+ puts "config: #{config.manual_read}"
148
+
149
+ socket_ipv4.listen(config.max_pending_connections)
150
+ # Tell to the Kernel that is ok to rebind the port if is in TIME_WAIT state (after close the connection
151
+ # and the Kernel wait for client acknowledgement)
152
+ socket_ipv4.setsockopt(:SOCKET, :REUSEADDR, true)
153
+ @socket4 = socket_ipv4
154
+ end
155
+
156
+ def socket_ipv6
157
+ @socket6
158
+ end
159
+
160
+ def socket_ipv6=(value)
161
+ @socket6 = value
162
+ end
163
+
164
+ def socket_ipv4
165
+ @socket4
166
+ end
167
+
168
+ def socket_ipv4=(value)
169
+ @socket4 = value
170
+ end
171
+
172
+ # TCP Socket reader
173
+ def reader(socket)
174
+
175
+ end
176
+
177
+ # Main configuration.
178
+ # See Crap::Configure
179
+ def config
180
+ @config
181
+ end
182
+
183
+ def logger=(value)
184
+ @logger = value
185
+ end
186
+
187
+ def logger
188
+ if not @logger
189
+ @logger = Logger.new(config.log_file)
190
+ @logger.level = @config.log_level
191
+ end
192
+ @logger
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,41 @@
1
+ module CrapServer
2
+ class Configure
3
+ # The port used.
4
+ # Default: 7331
5
+ attr_accessor :port
6
+ # Set to true if you want to manage the read.
7
+ # Default false
8
+ attr_accessor :manual_read
9
+ # Max read buffer size
10
+ # Default: 16K
11
+ attr_accessor :read_buffer_size
12
+ # The number of maximum penning connections.
13
+ # Default: Max allowed by the OS
14
+ attr_accessor :max_pending_connections
15
+ # Working method (read, write, accept, connect).
16
+ # Available values are:
17
+ # :normal
18
+ # :partial
19
+ # :non_blocking
20
+ # If :non_blocking is used TODO
21
+ # Default :readpartial
22
+ attr_accessor :method
23
+ # Set to false if you want to manage the close of the connection.
24
+ # Note that this require manual_read set to true.
25
+ attr_accessor :auto_close_connection
26
+ # The file to use as log
27
+ attr_accessor :log_file
28
+ # The log level used
29
+ attr_accessor :log_level
30
+ def initialize
31
+ @port = 7331
32
+ @manual_read = false
33
+ @read_buffer_size = 1024*16 # 16K for read buffer
34
+ @max_pending_connections = Socket::SOMAXCONN
35
+ @method = :partial
36
+ @auto_close_connection = true
37
+ @log_file = STDOUT
38
+ @log_level = Logger::DEBUG
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ module CrapServer
2
+ # This is the class used to bind the block that is passed to run!. Every method defined here is available inside
3
+ # the run! block
4
+ class ConnectionInstance
5
+ attr_accessor :socket
6
+ attr_accessor :address
7
+ attr_accessor :config
8
+ attr_accessor :method
9
+ def initialize; end
10
+
11
+ # This method execute the block sent to run! method
12
+ def run(&block)
13
+ # Undefine the last definition if was defined
14
+ undef :call if self.respond_to? :call
15
+ # Define the new method to bind the block with this class.
16
+ self.class.send :define_method, :call, &block
17
+ # Running the code depending of the number of args
18
+ if block.parameters.size == 1
19
+ self.call(read_data)
20
+ elsif block.parameters.size == 2
21
+ self.call(read_data, socket)
22
+ else
23
+ self.call(read_data, socket, address)
24
+ end
25
+ end
26
+
27
+ # Write to the client the given string
28
+ def write(string)
29
+ if @method == :normal or @method == :partial
30
+ @socket.write(string)
31
+ elsif @method == :non_blocking
32
+ @socket.write_nonblock(string)
33
+ end
34
+ end
35
+
36
+ # Give access to logger class to the user
37
+ def logger
38
+ @config.logger
39
+ end
40
+ protected
41
+ # Read the data from the socket
42
+ def read_data
43
+ # Read the data from the socket
44
+ if @method == :normal
45
+ @socket.read(config.read_buffer_size)
46
+ elsif @method == :partial
47
+ @socket.readpartial(config.read_buffer_size)
48
+ elsif @method == :non_blocking
49
+ @socket.read_nonblock(config.read_buffer_size)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ module CrapServer
2
+ module Helpers
3
+ # Makes easier work with sockets and read.
4
+ class SocketReader
5
+ attr_accessor :method
6
+ attr_accessor :socket
7
+ attr_accessor :address
8
+ attr_accessor :config
9
+ def initialize(socket_, method_=:partial)
10
+ @socket = socket_
11
+ @method = method_
12
+ end
13
+
14
+ def on_message(&block)
15
+ begin
16
+ instance = CrapServer::ConnectionInstance.new
17
+ instance.socket = socket
18
+ instance.config = config
19
+ instance.address = address
20
+ instance.send(:method=, method)
21
+ instance.run &block
22
+ # If we get out of data to read (but still having an opened connection), we wait for new data.
23
+ rescue IO::WaitReadable
24
+ # This, prevent to execute so many retry and block the code until a new bunch of data gets available
25
+ IO.select([@socket])
26
+ # Yay!, we have more data. Now we can continue!
27
+ retry
28
+ # When we use non_blocking method, and the client close the connection we will get EOF after that moment
29
+ # We do nothing special in that moment
30
+ rescue EOFError
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module CrapServer
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crap_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Andres Joser Borek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ description: Really thin a non intuitive ruby server and framework. Made to be fast
28
+ and ready for really heavy servers (not only http server).
29
+ email:
30
+ - andres.b.dev@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - Gemfile
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - crap_server.gemspec
41
+ - lib/crap_server.rb
42
+ - lib/crap_server/application.rb
43
+ - lib/crap_server/configure.rb
44
+ - lib/crap_server/connection_instance.rb
45
+ - lib/crap_server/helpers/socket_reader.rb
46
+ - lib/crap_server/version.rb
47
+ homepage: ''
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.2.2
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Really thin a non intuitive ruby server and framework.
71
+ test_files: []