packet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Packet is copyrighted free software by Hemant Kumar <mail@gnufied.org>
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ version 2 (see the file GPL), or Ruby License.
4
+
data/README ADDED
@@ -0,0 +1,251 @@
1
+ Packet is a pure ruby library for writing network applications in Ruby.
2
+ It follows Evented Model of network programming and implements almost all the
3
+ features provided by EventMachine.
4
+
5
+ It also provides real easy to user UNIX workers for concurrent programming.
6
+
7
+ Its best to have some examples going:
8
+
9
+ == Examples
10
+ === A Simple Echo Server:
11
+ require "rubygems"
12
+ require "packet"
13
+
14
+ class Foo
15
+ def receive_data p_data
16
+ send_data(p_data)
17
+ end
18
+
19
+ def post_init
20
+ puts "Client connected"
21
+ end
22
+
23
+ def connection_completed
24
+ puts "Whoa man"
25
+ end
26
+
27
+ def unbind
28
+ puts "Client Disconnected"
29
+ end
30
+ end
31
+
32
+ Packet::Reactor.run do |t_reactor|
33
+ t_reactor.start_server("localhost",11006,Foo)
34
+ end
35
+
36
+ Those new to network programming with events and callbacks, will note that,
37
+ each time a new client connects an instance of class Foo is instantiated.
38
+ When client writes some data to the socket, receive_data method is invoked.
39
+
40
+ Although Packet implements an API similar to EventMachine, but it differs
41
+ slightly because of the fact that, for a packet app, there can be more than one
42
+ reactor loop running and hence, we don't use Packet.start_server(...).
43
+
44
+ === A Simple Http Client
45
+ class WikiHandler
46
+ def receive_data p_data
47
+ p p_data
48
+ end
49
+
50
+ def post_init
51
+ end
52
+
53
+ def unbind
54
+ end
55
+
56
+ def connection_completed
57
+ send_data("GET / \r\n")
58
+ end
59
+ end
60
+
61
+ Packet::Reactor.run do |t_reactor|
62
+ t_reactor.connect("en.wikipedia.org",80,WikiHandler)
63
+ end
64
+
65
+ === Using Callbacks and Deferables
66
+ Documentation to come.
67
+
68
+ === Using Workers
69
+ Packet enables you to write simple workers, which will run in
70
+ different process and gives you nice
71
+ evented handle for concurrent execution of various tasks.
72
+
73
+ When, you are writing a scalable networking application
74
+ using Event Model of network programming,
75
+ sometimes when processing of certain events take time,
76
+ your event loop is stuck there. With green
77
+ threads, you don't really have a way of paralleling
78
+ your request processing. Packet library, allows
79
+ you to write simple workers, for executing long
80
+ running tasks. You can pass data and callbacks as an
81
+ argument.
82
+
83
+ When you are going to use workers in
84
+ your application, you need to define
85
+ constant WORKER_ROOT,
86
+ which is the directory location, where
87
+ your workers are located. All the workers defined in that directory
88
+ will be automatically, picked and forked in a
89
+ new process when your packet app starts. So, a typical
90
+ packet_app, that wants to use workers, will look like this:
91
+
92
+ packet_app_root
93
+ |
94
+ |__ lib
95
+ |
96
+ |___ worker
97
+ |
98
+ |___ config
99
+ |
100
+ |___ log
101
+
102
+ You would define WORKER_ROOT = PACKET_APP_ROOT/worker
103
+
104
+ All the workers must inherit class Packet::Worker, and hence a
105
+ general skeleton of worker will look like:
106
+
107
+ class FooWorker < Packet::Worker
108
+ set_worker_name :foo_worker #=> This is necessary.
109
+ def receive_data p_data
110
+ end
111
+
112
+ def connection_completed
113
+ end
114
+
115
+ def unbind
116
+ end
117
+
118
+ def post_init
119
+ end
120
+ end
121
+
122
+ All the forked workers are connected to master via
123
+ UNIX sockets, and hence messages passed to workers from master
124
+ will be available in receive_data method. Also,
125
+ when you are passing messages to workers, or worker is passing
126
+ message to master ( in a nutshell, all the internal
127
+ communication between workers and master ) directly takes
128
+ place using ruby objects. All the passed ruby objects are
129
+ dumped and marshalled across unix sockets in a non blocking
130
+ manner. BinParser class parses dumped binary objects and
131
+ makes sure, packets received at other end are complete.
132
+ Usually, you wouldn't need to worry about this little detail.
133
+
134
+ Packet provides various ways of interacting with
135
+ workers. Usually, when a worker is instantiated, a proxy for
136
+ that worker will also be instantiated at master
137
+ process. Packet automatically provides a worker proxy(See meta_pimp.rb)
138
+ for you, but if you need to multiplex/demultiplex
139
+ requests based on certain criteria, you may as well define your
140
+ own worker proxies. Code, would like something like this:
141
+
142
+ class FooWorker < Packet::Worker
143
+ set_worker_proxy :foo_handler
144
+ end
145
+
146
+ When you define, :foo_handler as a proxy for
147
+ this worker, packet is gonna search for FooHandler class and
148
+ instantiate it when the worker gets started. All
149
+ the worker proxies must inherit from Packet::Pimp.
150
+ Have a look at, Packet::MetaPimp,
151
+ which acts as a meta pimp for all the workers,
152
+ which don't have a explicit worker proxy defined.
153
+
154
+ === A complete Case :
155
+
156
+ Just for kicks, lets write a sample server,
157
+ which evals whatever clients send to it. But, assuming this 'eval' of
158
+ client data can be potentially time/cpu
159
+ consuming ( not to mention dangerous too ), we are gonna ask our eval_worker, to
160
+ perform eval and return the result to master process, which in
161
+ turn returns the result to happy client.
162
+
163
+ # APP_ROOT/bin/eval_server.rb
164
+ EVAL_APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__) + "/.."))
165
+ ["bin","worker","lib"].each { |x| $LOAD_PATH.unshift(EVAL_APP_ROOT + "/#{x}")}
166
+ WORKER_ROOT = EVAL_APP_ROOT + "/worker"
167
+
168
+ require "packet"
169
+ class EvalServer
170
+ def receive_data p_data
171
+ ask_worker(:eval_worker,:data => p_data, :type => :request)
172
+ end
173
+
174
+ # will be called, when any worker sends data back to master process
175
+ # it should be noted that, you may have several instances of eval_server in
176
+ # your master, for each connected client, but worker_receive will be always
177
+ # be invoked for the instance, which originally made the request.
178
+ # If you need fine control, over this behaviour, you can implement a worker proxy
179
+ # on the lines of meta_pimp class. This API will change in future perhaps, as i
180
+ # expect, better ideas to come.
181
+ def worker_receive p_data
182
+ send_data "#{p_data[:data]}\n"
183
+ end
184
+
185
+ def show_result p_data
186
+ send_data("#{p_data[:response]}\n")
187
+ end
188
+
189
+ def connection_completed
190
+ end
191
+
192
+ def post_init
193
+ end
194
+
195
+ def wow
196
+ puts "Wow"
197
+ end
198
+ end
199
+
200
+ Packet::Reactor.run do |t_reactor|
201
+ t_reactor.start_server("localhost", 11006,EvalServer) do |instance|
202
+ instance.wow
203
+ end
204
+ end
205
+
206
+ # APP_ROOT/worker/eval_worker.rb
207
+ class EvalWorker < Packet::Worker
208
+ set_worker_name :eval_worker
209
+ def worker_init
210
+ p "Starting no proxy worker"
211
+ end
212
+
213
+ def receive_data data_obj
214
+ eval_data = eval(data_obj[:data])
215
+ data_obj[:data] = eval_data
216
+ data_obj[:type] = :response
217
+ send_data(data_obj)
218
+ end
219
+ end
220
+
221
+ === Passing file descriptors to workers :
222
+ Packet, allows this style of load
223
+ distribution as well, you can use method send_fd to
224
+ pass file descriptors to workers.
225
+ WARNING: This feature hasn't been tested well.
226
+
227
+ === Disable auto loading of certain workers:
228
+ Sometimes, you would need to start a
229
+ worker at runtime and don't want this pre-forking mechanism.
230
+ Packet, allows this. You just need to define
231
+ "set_no_auto_load true" in your worker class and worker
232
+ will not be automatically forked. Although name is a bit misleading perhaps.
233
+
234
+ Now, at runtime, you can call start_worker(:foo_worker, options)
235
+ to start a worker as usual. It should
236
+ be noted that, forking a worker, which is already
237
+ forked can be disastrous, since worker names are being
238
+ used as unique keys that represent a worker.
239
+
240
+ == Performance:
241
+ Although written in pure ruby, packet performs
242
+ reasonably well. Mongrel, running on top of Packet is a tad
243
+ slower than Mongrel running on top of EventMachine. More benchmarks coming soon.
244
+
245
+ == Credits
246
+ Francis for awesome EventMachine lib, which has constantly acted as an inspiration.
247
+ Ezra, for being a early user and porting mongrel to run on top of packet
248
+
249
+
250
+
251
+
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+ require 'spec/rake/spectask'
8
+ require 'fileutils'
9
+ def __DIR__
10
+ File.dirname(__FILE__)
11
+ end
12
+ include FileUtils
13
+
14
+ NAME = "packet"
15
+ $LOAD_PATH.unshift __DIR__+'/lib'
16
+ require 'packet'
17
+
18
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
19
+
20
+
21
+ @windows = (PLATFORM =~ /win32/)
22
+
23
+ SUDO = @windows ? "" : (ENV["SUDO_COMMAND"] || "sudo")
24
+
25
+
26
+
27
+ desc "Packages up Packet."
28
+ task :default => [:package]
29
+
30
+ task :doc => [:rdoc]
31
+
32
+
33
+ Rake::RDocTask.new do |rdoc|
34
+ files = ['README', 'LICENSE', 'CHANGELOG',
35
+ 'lib/**/*.rb']
36
+ rdoc.rdoc_files.add(files)
37
+ rdoc.main = 'README'
38
+ rdoc.title = 'Packet Docs'
39
+ rdoc.rdoc_dir = 'doc/rdoc'
40
+ rdoc.options << '--line-numbers' << '--inline-source'
41
+ end
42
+
43
+ spec = Gem::Specification.new do |s|
44
+ s.name = NAME
45
+ s.version = Packet::VERSION
46
+ s.platform = Gem::Platform::RUBY
47
+ s.has_rdoc = true
48
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
49
+ #s.rdoc_options += RDOC_OPTS +
50
+ # ['--exclude', '^(app|uploads)']
51
+ s.summary = "Packet, Events... we got em."
52
+ s.description = s.summary
53
+ s.author = "Hemant"
54
+ s.email = 'foo@bar.com'
55
+ s.homepage = 'http://code.google.com/p/packet/'
56
+ s.required_ruby_version = '>= 1.8.4'
57
+
58
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{bin,spec,lib,examples,script}/**/*")
59
+
60
+ s.require_path = "lib"
61
+ s.bindir = "bin"
62
+ end
63
+
64
+ Rake::GemPackageTask.new(spec) do |p|
65
+ #p.need_tar = true
66
+ p.gem_spec = spec
67
+ end
68
+
69
+ task :install do
70
+ sh %{rake package}
71
+ sh %{#{SUDO} gem install pkg/#{NAME}-#{Packet::VERSION} --no-rdoc --no-ri}
72
+ end
73
+
74
+ task :uninstall => [:clean] do
75
+ sh %{#{SUDO} gem uninstall #{NAME}}
76
+ end
77
+
78
+ ##############################################################################
79
+ # SVN
80
+ ##############################################################################
81
+
82
+ desc "Add new files to subversion"
83
+ task :svn_add do
84
+ system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
85
+ end
86
+
87
+
data/TODO ADDED
File without changes
@@ -0,0 +1,215 @@
1
+ # This module rewrites pieces of the very good Mongrel web server in
2
+ # order to change it from a threaded application to an event based
3
+ # application running inside an Packet event loop. It should
4
+ # be compatible with the existing Mongrel handlers for Rails,
5
+ # Camping, Nitro, etc....
6
+ PACKET_APP = File.expand_path(File.join(File.dirname(__FILE__) + "/.."))
7
+ ["bin","config","parser","worker","framework","lib","pimp"].each { |x| $LOAD_PATH.unshift(PACKET_APP + "/#{x}")}
8
+ require "packet"
9
+ require 'packet'
10
+
11
+ module Mongrel
12
+ class MongrelProtocol < Packet::Connection
13
+ def post_init
14
+ @parser = HttpParser.new
15
+ @params = HttpParams.new
16
+ @nparsed = 0
17
+ @request = nil
18
+ @request_len = nil
19
+ @linebuffer = ''
20
+ end
21
+
22
+ def receive_data data
23
+ @linebuffer << data
24
+ @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
25
+ if @parser.finished?
26
+ if @request_len.nil?
27
+ @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
28
+ script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
29
+ if handlers
30
+ @params[::Mongrel::Const::PATH_INFO] = path_info
31
+ @params[::Mongrel::Const::SCRIPT_NAME] = script_name
32
+ @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] #|| ::Socket.unpack_sockaddr_in(get_peername)[1]
33
+ @notifiers = handlers.select { |h| h.request_notify }
34
+ end
35
+ if @request_len > ::Mongrel::Const::MAX_BODY
36
+ new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE)
37
+ new_buffer.binmode
38
+ new_buffer << @linebuffer[@nparsed..-1]
39
+ @linebuffer = new_buffer
40
+ else
41
+ @linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
42
+ @linebuffer.pos = @linebuffer.length
43
+ end
44
+ end
45
+ if @linebuffer.length >= @request_len
46
+ @linebuffer.rewind
47
+ ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
48
+ @linebuffer.delete if Tempfile === @linebuffer
49
+ end
50
+ elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
51
+ close_connection
52
+ raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
53
+ end
54
+ rescue ::Mongrel::HttpParserError
55
+ if $mongrel_debug_client
56
+ STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
57
+ STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
58
+ end
59
+ close_connection
60
+ rescue Exception => e
61
+ close_connection
62
+ raise e
63
+ end
64
+
65
+ def write data
66
+ send_data data
67
+ end
68
+
69
+ def closed?
70
+ false
71
+ end
72
+
73
+ end
74
+
75
+ class HttpServer
76
+ def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later.
77
+ @socket = nil
78
+ @classifier = URIClassifier.new
79
+ @host = host
80
+ @port = port
81
+ @workers = ThreadGroup.new
82
+ if y
83
+ @throttle = x
84
+ @timeout = y || 60
85
+ else
86
+ @timeout = x
87
+ end
88
+ @num_processors = num_processors #num_processors is pointless for evented....
89
+ @death_time = 60
90
+ self.class.const_set(:Instance,self)
91
+ end
92
+
93
+ def run
94
+ trap('INT') { raise StopServer }
95
+ trap('TERM') { raise StopServer }
96
+ @acceptor = Thread.new do
97
+ Packet::Reactor.run do |t_reactor|
98
+ begin
99
+ t_reactor.start_server(@host,@port,MongrelProtocol)
100
+ rescue StopServer
101
+ t_reactor.start_server
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def process_http_request(params,linebuffer,client)
108
+ if not params[Const::REQUEST_PATH]
109
+ uri = URI.parse(params[Const::REQUEST_URI])
110
+ params[Const::REQUEST_PATH] = uri.request_uri
111
+ end
112
+
113
+ raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
114
+
115
+ script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
116
+
117
+ if handlers
118
+ notifiers = handlers.select { |h| h.request_notify }
119
+ request = HttpRequest.new(params, linebuffer, notifiers)
120
+
121
+ # request is good so far, continue processing the response
122
+ response = HttpResponse.new(client)
123
+
124
+ # Process each handler in registered order until we run out or one finalizes the response.
125
+ dispatch_to_handlers(handlers,request,response)
126
+
127
+ # And finally, if nobody closed the response off, we finalize it.
128
+ unless response.done
129
+ response.finished
130
+ else
131
+ response.close_connection
132
+ end
133
+ else
134
+ # Didn't find it, return a stock 404 response.
135
+ client.send_data(Const::ERROR_404_RESPONSE)
136
+ client.close_connection
137
+ end
138
+ end
139
+
140
+ def dispatch_to_handlers(handlers,request,response)
141
+ handlers.each do |handler|
142
+ handler.process(request, response)
143
+ break if response.done
144
+ end
145
+ end
146
+ end
147
+
148
+ class HttpRequest
149
+ def initialize(params, linebuffer, dispatchers)
150
+ @params = params
151
+ @dispatchers = dispatchers
152
+ @body = linebuffer
153
+ end
154
+ end
155
+
156
+ class HttpResponse
157
+ def send_file(path, small_file = false)
158
+ File.open(path, "rb") do |f|
159
+ while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
160
+ begin
161
+ write(chunk)
162
+ rescue Object => exc
163
+ break
164
+ end
165
+ end
166
+ end
167
+ @body_sent = true
168
+ end
169
+
170
+ def write(data)
171
+ @socket.send_data data
172
+ end
173
+
174
+ def close_connection_after_writing
175
+ @socket.close_connection
176
+ end
177
+
178
+ def socket_error(details)
179
+ @socket.close_connection
180
+ done = true
181
+ raise details
182
+ end
183
+
184
+ def finished
185
+ send_status
186
+ send_header
187
+ send_body
188
+ @socket.close_connection
189
+ end
190
+ end
191
+
192
+ class Configurator
193
+ # This version fixes a bug in the regular Mongrel version by adding
194
+ # initialization of groups.
195
+ def change_privilege(user, group)
196
+ if user and group
197
+ log "Initializing groups for {#user}:{#group}."
198
+ Process.initgroups(user,Etc.getgrnam(group).gid)
199
+ end
200
+
201
+ if group
202
+ log "Changing group to #{group}."
203
+ Process::GID.change_privilege(Etc.getgrnam(group).gid)
204
+ end
205
+
206
+ if user
207
+ log "Changing user to #{user}."
208
+ Process::UID.change_privilege(Etc.getpwnam(user).uid)
209
+ end
210
+ rescue Errno::EPERM
211
+ log "FAILED to change user:group #{user}:#{group}: #$!"
212
+ exit 1
213
+ end
214
+ end
215
+ end