crap_server 0.0.2.2 → 0.0.3.0

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