packet 0.1.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.
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