crap_server 0.0.2.2 → 0.0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 636e155531fe01738c575fc3d23dcd24557cd866
4
- data.tar.gz: eb2b425befbd907828b6bdf55d1981643ef4f05d
3
+ metadata.gz: 3b7a2133b068b88f831896a41664b94448243b84
4
+ data.tar.gz: 49f6bebeaf9bdb5b51e33a39053630bc47068b40
5
5
  SHA512:
6
- metadata.gz: 07230fa972fdf9f4b9fc5cf10043a0a4593fee1811012d9f1d93785dd585b82eb03bf02d7b225478bfb3b553286bfac0b54438ceb2f71f4effffb61d81e5c3fc
7
- data.tar.gz: a3e470a6d2e4c3043b2c872da4e8cbfca3166e28f7795c8008d4cb50bf67b4ea6a8514e9b184c5e704cb534c02f2d4d2bb89fba4517daf008532ca938be50add
6
+ metadata.gz: 9bb7f5661dd2b9b51d96abf188a4038a275ae77a3ff13d4315b6c0ac03c616354bbc91293f8a65df6f2157c01616bc4432fbf1d841143cd7286e8163f3577414
7
+ data.tar.gz: 694265fae58d630a42b6b973014590fe1f3ec66e29b300a42f2919d9691b6e987154878e08b5ac6bd980032d38ccdc6358d4614bab3663606c4cf2cb86ce12e3
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![Code Climate](https://codeclimate.com/github/anga/crap_server/badges/gpa.svg)](https://codeclimate.com/github/anga/crap_server)
4
4
 
5
5
  Really thin and non intuitive ruby server. Made to be fast and ready for really heavy servers (not only http server).
6
+ Use Preforking and Evented pattern.
6
7
 
7
8
  # Another one?
8
9
 
@@ -35,14 +36,6 @@ Or install it yourself as:
35
36
  end
36
37
  end
37
38
 
38
- ## Configuring the app
39
-
40
- CrapServer::Application.configure do |config|
41
- config.port = 80
42
- config.read_method = :partial
43
- config.read_buffer_size = 1024 # 1K
44
- end
45
-
46
39
  See all available options in lib/crap_server/configure.rb
47
40
 
48
41
  # Running our application
@@ -51,7 +44,7 @@ ruby my_app.rb
51
44
 
52
45
  # Production ready?
53
46
 
54
- No. At the moment it's only a thin server that abstract you from TCP sockets works.
47
+ No. Use it under your own risk. Right now, the interface can change.
55
48
 
56
49
  ## Contributing
57
50
 
@@ -24,7 +24,6 @@ module CrapServer
24
24
 
25
25
  # Main method. This setup all the connections and make the logic of the app
26
26
  def run!(&block)
27
-
28
27
  begin
29
28
  # Bup the maximum opened file to the maximum allowed by the system
30
29
  Process.setrlimit(:NOFILE, Process.getrlimit(:NOFILE)[1])
@@ -40,11 +39,12 @@ module CrapServer
40
39
  logger.debug "Maximum number of allowed connections: #{Process.getrlimit(:NOFILE)[1]}" # Same as maximum of opened files
41
40
  logger.info ''
42
41
 
43
- # The main loop. Listening IPv4 and IPv6 connections
44
- Socket.accept_loop([socket_ipv4, socket_ipv6]) do |remote_socket, address_info|
45
- connection_loop(remote_socket, address_info, &block)
46
- end
42
+ # Prefork and handle the connections in each process.
43
+ forker = CrapServer::Forker.new([socket_ipv4, socket_ipv6])
44
+ # Run loop. (basically, waiting until Ctrl+C)
45
+ forker.run &block
47
46
 
47
+ # NOTE: I think this line never will be executed
48
48
  close_connections
49
49
 
50
50
  # If any kind of error happens, we MUST close the sockets
@@ -61,38 +61,6 @@ module CrapServer
61
61
  end
62
62
 
63
63
  protected
64
- def connection_loop(remote_socket, addres_info, &block)
65
- # Work with the connection...
66
- if we_should_read?
67
- reader = CrapServer::Helpers::SocketReader.new(remote_socket, config.method)
68
- reader.address = addres_info
69
- reader.config = config
70
- reader.on_message(&block)
71
- else
72
- begin
73
- if block.parameters == 1
74
- block.call(remote_socket)
75
- else
76
- block.call(remote_socket, addres_info)
77
- end
78
- # If we get out of data to read (but still having an opened connection), we wait for new data.
79
- rescue IO::WaitReadable
80
- # This, prevent to execute so many retry and block the code until a new bunch of data gets available
81
- IO.select([remote_socket])
82
- # Yay!, we have more data. Now we can continue!
83
- retry
84
- end
85
- end
86
- # ...
87
-
88
- # Close the connection
89
- remote_socket.close if config.auto_close_connection
90
- end
91
-
92
- # Return true or false if the read process is done by the server.
93
- def we_should_read?
94
- not config.manual_read
95
- end
96
64
 
97
65
  # Open TCP connection (IPv4 and IPv6)
98
66
  def open_connections
@@ -107,14 +75,14 @@ module CrapServer
107
75
  # If any kind of error happens, we MUST close the sockets
108
76
  if socket_ipv4
109
77
  # Shuts down communication on all copies of the connection.
110
- socket_ipv4.shutdown
111
- socket_ipv4.close
78
+ # socket_ipv4.shutdown
79
+ # socket_ipv4.close
112
80
  end
113
81
 
114
82
  if socket_ipv6
115
83
  # Shuts down communication on all copies of the connection.
116
- socket_ipv6.shutdown
117
- socket_ipv6.close
84
+ # socket_ipv6.shutdown
85
+ # socket_ipv6.close
118
86
  end
119
87
  # TODO: Close all opened sockets connections from other threads and processes
120
88
  end
@@ -137,6 +105,7 @@ module CrapServer
137
105
  # Tell to the Kernel that is ok to rebind the port if is in TIME_WAIT state (after close the connection
138
106
  # and the Kernel wait for client acknowledgement)
139
107
  socket_ipv6.setsockopt(:SOCKET, :REUSEADDR, true)
108
+
140
109
  @socket6 = socket_ipv6
141
110
  end
142
111
 
@@ -158,6 +127,7 @@ module CrapServer
158
127
  # Tell to the Kernel that is ok to rebind the port if is in TIME_WAIT state (after close the connection
159
128
  # and the Kernel wait for client acknowledgement)
160
129
  socket_ipv4.setsockopt(:SOCKET, :REUSEADDR, true)
130
+
161
131
  @socket4 = socket_ipv4
162
132
  end
163
133
 
@@ -22,6 +22,7 @@ module CrapServer
22
22
  attr_accessor :method
23
23
  # Set to false if you want to manage the close of the connection.
24
24
  # Note that this require manual_read set to true.
25
+ # DEPERCATED
25
26
  attr_accessor :auto_close_connection
26
27
  # The file to use as log
27
28
  attr_accessor :log_file
@@ -0,0 +1,158 @@
1
+ module CrapServer
2
+ class ConnectionHandler
3
+
4
+ def initialize(sockets)
5
+ @sockets = sockets
6
+ @sockets.each do |io|
7
+ add_to_read io
8
+ end
9
+ end
10
+
11
+ def add_to_write(io)
12
+ @to_write ||= {}
13
+ @to_write[io.fileno] = io
14
+ end
15
+
16
+ def to_write
17
+ (@to_write ||= {}).values
18
+ end
19
+
20
+ def remove_to_write(io)
21
+ @to_write.delete io.fileno
22
+ @buffer.delete io.fileno
23
+ end
24
+
25
+ def buffer(io)
26
+ @buffer ||= {}
27
+ @buffer[io.fileno]
28
+ end
29
+
30
+ def set_buffer(io, string)
31
+ @buffer ||= {}
32
+ @buffer[io.fileno] = string
33
+ end
34
+
35
+ def to_read
36
+ (@to_read ||= {}).values
37
+ end
38
+
39
+ def add_to_read(io)
40
+ @to_read ||= {}
41
+ @to_read[io.fileno] = io
42
+ end
43
+
44
+ def remove_to_read(io)
45
+ @to_read.delete io.fileno
46
+ @address.delete io.fileno
47
+ end
48
+
49
+ def address(io)
50
+ @address ||= {}
51
+ @address[io.fileno]
52
+ end
53
+
54
+ def set_address(io, addrs)
55
+ @address ||= {}
56
+ @address[io.fileno] = addrs
57
+ end
58
+
59
+ def read_buffer(io)
60
+ @rbuffer ||= {}
61
+ @rbuffer[io.fileno]
62
+ end
63
+
64
+ def add_read_buffer(io, string)
65
+ @rbuffer ||= {}
66
+ @rbuffer[io.fileno] ||= ''
67
+ @rbuffer[io.fileno] << string
68
+ end
69
+
70
+ def set_close_after_write(io)
71
+ @closeaw ||= {}
72
+ @closeaw[io.fileno] = true
73
+ end
74
+
75
+ def close_after_write(io)
76
+ @closeaw ||= {}
77
+ @closeaw[io.fileno]
78
+ end
79
+
80
+ def close(io)
81
+ remove_to_read io
82
+ remove_to_write io
83
+ @closeaw.delete io.fileno
84
+ io.close
85
+ end
86
+
87
+ def handle(&block)
88
+ # The main loop. Listening IPv4 and IPv6 connections
89
+ accept_loop do |data, remote_socket, address_info|
90
+ instance = CrapServer::ConnectionInstance.new
91
+ instance.socket = remote_socket
92
+ instance.config = config
93
+ instance.address = address_info
94
+ instance.send(:method=, config.method)
95
+ instance.handler = self
96
+ instance.run data, &block
97
+ end
98
+ end
99
+
100
+ protected
101
+
102
+ # Evented loop (Reactor pattern)
103
+ def accept_loop
104
+ loop {
105
+ @readables, @writables = IO.select(to_read, to_write)
106
+
107
+ @readables.each do |socket|
108
+ if @sockets.include? socket
109
+ io, addr = socket.accept
110
+ set_address io, addr
111
+ set_close_after_write io if config.auto_close_connection
112
+ # Disabling Nagle's algorithm. Is fucking slow :P
113
+ io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
114
+ # We add him to the read queue
115
+ add_to_read io
116
+ else
117
+ begin
118
+ _, data = socket, socket.read_nonblock(config.read_buffer_size)
119
+ yield data, socket, address(socket)
120
+ rescue Errno::EAGAIN
121
+ rescue EOFError
122
+ remove_to_read socket
123
+ end
124
+ end
125
+ end
126
+
127
+ @writables.each do |socket|
128
+ begin
129
+ string = buffer socket
130
+ bytes = socket.write_nonblock string
131
+ string.slice! 0, bytes
132
+ if string.empty?
133
+ # If we don't have more data to send to the client
134
+ if close_after_write socket
135
+ close socket
136
+ else
137
+ remove_to_write socket
138
+ end
139
+ else
140
+ set_buffer socket, string
141
+ remove_to_read socket
142
+ end
143
+ # If the client close the connection, we remove is from read and from write
144
+ rescue Errno::ECONNRESET, Errno::EPIPE
145
+ if close_after_write socket
146
+ close socket
147
+ end
148
+ end
149
+ end
150
+ }
151
+ end
152
+
153
+ protected
154
+ def config
155
+ CrapServer::Application.send(:config)
156
+ end
157
+ end
158
+ end
@@ -6,57 +6,43 @@ module CrapServer
6
6
  attr_accessor :address
7
7
  attr_accessor :config
8
8
  attr_accessor :method
9
+ attr_accessor :handler
9
10
  def initialize; end
10
11
 
11
12
  # This method execute the block sent to run! method
12
- def run(&block)
13
+ def run(data, &block)
13
14
  # Undefine the last definition if was defined
14
15
  undef :call if self.respond_to? :call
15
16
  # Define the new method to bind the block with this class.
16
17
  self.class.send :define_method, :call, &block
17
18
  # Running the code depending of the number of args
18
19
  if block.parameters.size == 1
19
- self.call(read_data)
20
+ self.call(data)
20
21
  elsif block.parameters.size == 2
21
- self.call(read_data, socket)
22
+ self.call(data, socket)
22
23
  else
23
- self.call(read_data, socket, address)
24
+ self.call(data, socket, address)
24
25
  end
26
+ @socket.flush
25
27
  end
26
28
 
27
29
  # Write to the client the given string
28
30
  def write(string)
29
- begin
30
- if @method == :normal or @method == :partial
31
- @socket.write(string)
32
- elsif @method == :non_blocking
33
- @socket.write_nonblock(string)
34
- end
35
- rescue IO::WaitWritable, Errno::EINTR
36
- IO.select(nil, [@socket], nil, config.timeout)
37
- end
31
+ @handler.add_to_write @socket
32
+ @handler.set_buffer @socket, string
33
+ end
34
+
35
+ def close_after_write
36
+ @handler.set_close_after_write @socket
37
+ end
38
+
39
+ def close
40
+ @handler.close @socket
38
41
  end
39
42
 
40
43
  # Give access to logger class to the user
41
44
  def logger
42
45
  @config.logger
43
46
  end
44
- protected
45
- # Read the data from the socket
46
- def read_data
47
- begin
48
- # Read the data from the socket
49
- if @method == :normal
50
- @socket.read(config.read_buffer_size)
51
- elsif @method == :partial
52
- @socket.readpartial(config.read_buffer_size)
53
- elsif @method == :non_blocking
54
- @socket.read_nonblock(config.read_buffer_size)
55
- end
56
- rescue Errno::EAGAIN
57
- IO.select([connection],nil,nil, config.timeout)
58
- retry
59
- end
60
- end
61
47
  end
62
48
  end
@@ -0,0 +1,108 @@
1
+ module CrapServer
2
+ # Handle preforking task.
3
+ # Will spawn 1 process per core.
4
+ class Forker
5
+ def initialize(sockets)
6
+ @sockets = sockets
7
+ end
8
+
9
+ # Initialize
10
+ def run(&block)
11
+ begin
12
+ @block_proc = block
13
+ child_pids = []
14
+ processor_count.times do
15
+ child_pids << spawn_child
16
+ end
17
+
18
+ # We take care of our children. If someone kill one, me made another one.
19
+ # PS: Is a hard work :P
20
+ loop do
21
+ pid = Process.wait
22
+ child_pids.delete(pid)
23
+ child_pids << spawn_child
24
+ end
25
+ # If someone kill us, we kill our children. Yes, is sad, but we must do it :'(
26
+ rescue Interrupt
27
+ child_pids.each do |cpid|
28
+ begin
29
+ # We send Ctrl+C to the process
30
+ Process.kill(:INT, cpid)
31
+ rescue Errno::ESRCH
32
+ end
33
+ end
34
+ @sockets.each do |socket|
35
+ # Shuts down communication on all copies of the connection.
36
+ socket.shutdown
37
+ socket.close
38
+ end
39
+
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def spawn_child
46
+ fork do
47
+ begin
48
+ handler = CrapServer::ConnectionHandler.new @sockets
49
+ handler.handle &@block_proc
50
+ rescue Interrupt
51
+ end
52
+ end
53
+ end
54
+
55
+ # Extracted from https://github.com/grosser/parallel/blob/master/lib/parallel.rb
56
+ # Number of processors seen by the OS and used for process scheduling.
57
+ #
58
+ # * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev
59
+ # * BSD: /sbin/sysctl
60
+ # * Cygwin: /proc/cpuinfo
61
+ # * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl
62
+ # * HP-UX: /usr/sbin/ioscan
63
+ # * IRIX: /usr/sbin/sysconf
64
+ # * Linux: /proc/cpuinfo
65
+ # * Minix 3+: /proc/cpuinfo
66
+ # * Solaris: /usr/sbin/psrinfo
67
+ # * Tru64 UNIX: /usr/sbin/psrinfo
68
+ # * UnixWare: /usr/sbin/psrinfo
69
+ #
70
+ def processor_count
71
+ @processor_count ||= begin
72
+ os_name = RbConfig::CONFIG["target_os"]
73
+ if os_name =~ /mingw|mswin/
74
+ require 'win32ole'
75
+ result = WIN32OLE.connect("winmgmts://").ExecQuery(
76
+ "select NumberOfLogicalProcessors from Win32_Processor")
77
+ result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
78
+ elsif File.readable?("/proc/cpuinfo")
79
+ IO.read("/proc/cpuinfo").scan(/^processor/).size
80
+ elsif File.executable?("/usr/bin/hwprefs")
81
+ IO.popen("/usr/bin/hwprefs thread_count").read.to_i
82
+ elsif File.executable?("/usr/sbin/psrinfo")
83
+ IO.popen("/usr/sbin/psrinfo").read.scan(/^.*on-*line/).size
84
+ elsif File.executable?("/usr/sbin/ioscan")
85
+ IO.popen("/usr/sbin/ioscan -kC processor") do |out|
86
+ out.read.scan(/^.*processor/).size
87
+ end
88
+ elsif File.executable?("/usr/sbin/pmcycles")
89
+ IO.popen("/usr/sbin/pmcycles -m").read.count("\n")
90
+ elsif File.executable?("/usr/sbin/lsdev")
91
+ IO.popen("/usr/sbin/lsdev -Cc processor -S 1").read.count("\n")
92
+ elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i
93
+ IO.popen("/usr/sbin/sysconf NPROC_ONLN").read.to_i
94
+ elsif File.executable?("/usr/sbin/sysctl")
95
+ IO.popen("/usr/sbin/sysctl -n hw.ncpu").read.to_i
96
+ elsif File.executable?("/sbin/sysctl")
97
+ IO.popen("/sbin/sysctl -n hw.ncpu").read.to_i
98
+ else
99
+ $stderr.puts "Unknown platform: " + RbConfig::CONFIG["target_os"]
100
+ $stderr.puts "Assuming 1 processor."
101
+ 1
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+ end
108
+ end
@@ -1,3 +1,3 @@
1
1
  module CrapServer
2
- VERSION = '0.0.2.2'
2
+ VERSION = '0.0.3.0'
3
3
  end
data/lib/crap_server.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  require 'crap_server/version'
2
2
  require 'crap_server/configure'
3
+ require 'crap_server/forker'
4
+ require 'crap_server/connection_handler'
3
5
  require 'crap_server/connection_instance'
4
- require 'crap_server/helpers/socket_reader'
5
6
  require 'crap_server/application'
6
7
  module CrapServer
7
8
  # Your code goes here...
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crap_server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.2
4
+ version: 0.0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andres Jose Borek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-05 00:00:00.000000000 Z
11
+ date: 2014-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -55,8 +55,9 @@ files:
55
55
  - lib/crap_server.rb
56
56
  - lib/crap_server/application.rb
57
57
  - lib/crap_server/configure.rb
58
+ - lib/crap_server/connection_handler.rb
58
59
  - lib/crap_server/connection_instance.rb
59
- - lib/crap_server/helpers/socket_reader.rb
60
+ - lib/crap_server/forker.rb
60
61
  - lib/crap_server/version.rb
61
62
  - spec/application_spec.rb
62
63
  - spec/spec_helper.rb
@@ -1,35 +0,0 @@
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