ruby-ajp 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/net/ajp13.rb ADDED
@@ -0,0 +1,815 @@
1
+ # = Ruby/AJP
2
+ # An implementation of AJP(Apache Jserv Protocol) 1.3 in Ruby,
3
+ # based on http://tomcat.apache.org/connectors-doc/common/ajpv13a.html.
4
+ #
5
+ # [Net::AJP13::Client] provides high-level API to implement AJP clients.
6
+ # The interface of the client-side library is similar to
7
+ # net/http.
8
+ # see ajp13client.rb[link:files/lib/net/ajp13client_rb.html]
9
+ # for more detail.
10
+ # [Net::AJP13::Server] provides high-level API to implement AJP servers.
11
+ # see ajp13server.rb[link:files/lib/net/ajp13server_rb.html]
12
+ # for more detail.
13
+ #
14
+ # Author:: Yugui (mailto:yugui@yugui.sakura.ne.jp)
15
+ # Copyright:: Copyright (c) 2005-2006 Yugui
16
+ # License:: LGPL
17
+ #
18
+ # This library is free software; you can redistribute it and/or
19
+ # modify it under the terms of the GNU Lesser General Public
20
+ # License as published by the Free Software Foundation; version 2.1
21
+ # of the License any later version.
22
+ #
23
+ # This library is distributed in the hope that it will be useful,
24
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26
+ # Lesser General Public License for more details.
27
+ #
28
+ # You should have received a copy of the GNU Lesser General Public
29
+ # License along with this library; if not, write to the Free Software
30
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
31
+ # MA 02110-1301 USA
32
+ #
33
+
34
+ require 'net/http'
35
+
36
+ # :stopdoc:
37
+ module Net; end
38
+ # :startdoc:
39
+
40
+ module Net::AJP13
41
+ module Constants
42
+ # default AJP port
43
+ DEFAULT_PORT = 8009
44
+
45
+ # :stopdoc:
46
+ FORWARD_REQUEST = 0x02
47
+ SHUTDOWN = 0x07
48
+ PING = 0x08
49
+ CPING = 0x0A
50
+
51
+ SEND_BODY_CHUNK = 0x03
52
+ SEND_HEADERS = 0x04
53
+ END_RESPONSE = 0x05
54
+ GET_BODY_CHUNK = 0x06
55
+ CPONG_REPLY = 0x09
56
+
57
+ MAX_PACKET_SIZE = 8 * 1024 - 4 # limitaion of Jakarta's implementation
58
+ MAX_BODY_CHUNK_SIZE = MAX_PACKET_SIZE - 2
59
+ # :startdoc:
60
+ end
61
+ end
62
+
63
+ #
64
+ # Represents AJP1.3 Request.
65
+ #
66
+ # Mixes int the Net::HTTPHeader module
67
+ #
68
+ class Net::AJP13::Request
69
+ include Net::HTTPHeader
70
+ include Net::AJP13::Constants
71
+
72
+ # :stopdoc:
73
+
74
+ # maps HTTP/WebDAV method into its 16bit code
75
+ REQUEST_METHOD_CODES = {
76
+ :OPTIONS => 1, :GET => 2, :HEAD => 3, :POST => 4,
77
+ :PUT => 5, :DELETE => 6, :TRACE => 7, :PROPFIND => 8,
78
+ :PROPPATCH => 9, :MKCOL => 10, :COPY => 11, :MOVE => 12,
79
+ :LOCK => 13, :UNLOCK => 14, :ACL => 15, :REPORT => 16,
80
+ 'VERSION-CONTROL'.intern => 17, :CHECKIN => 18, :CHECKOUT => 19,
81
+ :UNCHECKOUT => 20, :SEARCH => 21
82
+ }.freeze
83
+
84
+ # Maps core HTTP headers into its 16bit code
85
+ SC_REQ_HEADER_NAMES = {
86
+ 'accept' => 0xA001, 'accept-charset' => 0xA002,
87
+ 'accept-encoding' => 0xA003, 'accept-language' => 0xA004,
88
+ 'authorization' => 0xA005, 'connection' => 0xA006,
89
+ 'content-type' => 0xA007, 'content-length' => 0xA008,
90
+ 'cookie' => 0xA009, 'cookie2' => 0xA00A,
91
+ 'host' => 0xA00B, 'pragma' => 0xA00C,
92
+ 'referer' => 0xA00D, 'user_agent' => 0xA00E
93
+ }.freeze
94
+ SC_REQ_HEADER_NAMES.each_key {|k| k.freeze}
95
+
96
+ SC_A_REQ_ATTRIBUTE = 0xA0
97
+ # Maps request attribute names into their codes
98
+ SC_A_NAMES = {
99
+ :context => 0x01,
100
+ :servlet_path => 0x02,
101
+ :remote_user => 0x03,
102
+ :auth_type => 0x04,
103
+ :query_string => 0x05,
104
+ :jvm_route => 0x06,
105
+ :ssl_cert => 0x07,
106
+ :ssl_cipher => 0x08,
107
+ :ssl_session => 0x09,
108
+ :req_attribute => SC_A_REQ_ATTRIBUTE,
109
+ :ssl_key_size => 0x0B
110
+ }.freeze
111
+
112
+ # The termination byte of AJP request packet.
113
+ REQUEST_TERMINATOR = 0xFF
114
+
115
+ # :startdoc:
116
+
117
+ #
118
+ # Creates a AJP13 request object.
119
+ # +path+:: path to request
120
+ #
121
+ def initialize(path, init_header = nil)
122
+ @method = self.class::METHOD
123
+ @request_has_body = self.class::REQUEST_HAS_BODY
124
+ @response_has_body = self.class::RESPONSE_HAS_BODY
125
+ @path, qs = path.split(/\?/, 2)
126
+ initialize_http_header init_header
127
+ @attributes = {}
128
+ add_attribute('query_string', qs) if qs
129
+
130
+ @is_ssl = false
131
+ @protocol = nil
132
+ @remote_addr = nil
133
+ @remote_host = nil
134
+ @server_name = nil
135
+ @server_port = nil # Net::HTTP.default_port
136
+ end
137
+
138
+ METHOD = nil
139
+ REQUEST_HAS_BODY = nil
140
+ RESPONSE_HAS_BODY = nil
141
+
142
+ # Reads a AJP packet from +io+, creates the new AJP13Request object
143
+ # corresponding to the packet, and returns the object.
144
+ #
145
+ # When the request has the entity body, also reads some packets from +io+
146
+ # until the body ends. You can get the entity body as +request+.+body+.
147
+ #
148
+ # +io+:: packet source
149
+ # [pre-condition]
150
+ # The read position of +io+ is the top of an AJP packet.
151
+ # [post-condition]
152
+ # The read position of +io+ is just behind of the AJP packets
153
+ # corresponding to the returned AJP13Request object.
154
+ # If an exception raised, the read position is unknown.
155
+ #
156
+ # If called with a block, yields the given block with a fragment of the body
157
+ # on reading the body.
158
+ #
159
+ def self.from_io(io)
160
+ from_packet(Net::AJP13::Packet.from_io(io))
161
+ end
162
+
163
+ def self.from_packet(packet) #:nodoc: internal use
164
+ raise 'The AJP packet is not to application server' unless packet.to_app?
165
+ raise 'The AJP packet is not a forward request' unless
166
+ packet.message_type == FORWARD_REQUEST
167
+
168
+ method = packet.read_byte
169
+ method =
170
+ REQUEST_METHOD_CODES.index(method) ||
171
+ raise("Unrecognized HTTP method code #{method}")
172
+
173
+ protocol = packet.read_string
174
+ path = packet.read_string
175
+
176
+ req = self.new(path)
177
+ req.method = method.to_s.upcase
178
+ req.protocol = protocol
179
+
180
+ req.remote_addr = packet.read_string
181
+ req.remote_host = packet.read_string
182
+ req.server_name = packet.read_string
183
+ req.server_port = packet.read_integer
184
+ req.is_ssl = packet.read_boolean
185
+
186
+ num_headers = packet.read_integer
187
+ 1.upto(num_headers) do
188
+ if packet.peek_byte == 0xA0
189
+ header_name = packet.read_integer
190
+ header_name =
191
+ SC_REQ_HEADER_NAMES.index(header_name) ||
192
+ raise("Unrecognized HTTP header code #{header_name}")
193
+ header_name = header_name.tr('_', '-')
194
+ else
195
+ header_name = packet.read_string
196
+ end
197
+ req[header_name] = packet.read_string
198
+ end
199
+ loop do
200
+ case attr_name = packet.read_byte
201
+ when nil
202
+ raise 'Missing AJP request packet terminator'
203
+ when REQUEST_TERMINATOR
204
+ break
205
+ when SC_A_REQ_ATTRIBUTE
206
+ attr_name = packet.read_string
207
+ else
208
+ attr_name = SC_A_NAMES.index(attr_name) ||
209
+ raise("Unrecognized AJP request attribute #{attr_name}")
210
+ attr_name = attr_name.to_s
211
+ end
212
+ attr_value = packet.read_string
213
+ req.add_attribute(attr_name, attr_value)
214
+ end
215
+
216
+ return req
217
+ end
218
+
219
+ def inspect #:nodoc:
220
+ "\#<#{self.class} #{@method}>"
221
+ end
222
+
223
+ # Path to request
224
+ attr_reader :path
225
+ alias :request_uri :path
226
+
227
+ # HTTP request method
228
+ attr_reader :method
229
+ attr_writer :method #:nodoc: internal use
230
+
231
+ # IP address of the HTTP client
232
+ attr_accessor :remote_addr
233
+ # Host name of the HTTP client
234
+ attr_accessor :remote_host
235
+ # HTTP server name
236
+ attr_accessor :server_name
237
+ # HTTP port
238
+ attr_accessor :server_port
239
+
240
+ # Returns if it is permitted that the client sends this request
241
+ # with a request body.
242
+ def request_body_permitted?
243
+ @request_has_body
244
+ end
245
+
246
+ # Returns if it is permitted that the server sends the corresponding response
247
+ # with a response body
248
+ def response_body_permitted?
249
+ @response_has_body
250
+ end
251
+
252
+ # HTTP-side connection is over SSL or not.
253
+ attr_accessor :is_ssl
254
+ alias :is_ssl? :is_ssl
255
+ def is_ssl=(value) #:nodoc:
256
+ @is_ssl = !!value
257
+ end
258
+
259
+ # HTTP protocol name/version
260
+ attr_accessor :protocol
261
+
262
+ # Adds request attribute instead of replace
263
+ # Second argument +value+ must be a String
264
+ #
265
+ # See also #set_attribute
266
+ def add_attribute(name, value)
267
+ name = name.downcase
268
+ @attributes[name] ||= []
269
+ @attributes[name] << value
270
+ end
271
+
272
+ # Set the request attribute corresponding to the case-insensitive name.
273
+ # See also #add_attribute
274
+ def set_attribute(name, value)
275
+ @attributes[name.downcase] = [value]
276
+ end
277
+
278
+ # Returns an array of attribute values corresponding to the case-insensitive
279
+ # +name+.
280
+ def get_attributes(name)
281
+ name = name.downcase
282
+ values = @attributes[name]
283
+ values and values.dup
284
+ end
285
+
286
+ # Deletes request attributes whose name are +name+
287
+ def delete_attribute(name)
288
+ @attributes.delete(name.downcase)
289
+ end
290
+
291
+ # Iterates for each request attribute values.
292
+ # +name+ can appears more than once if multiple values exist for the +name+.
293
+ def each_attribute(&block) #:yield: +name+, +value+
294
+ @attributes.each do |name, values|
295
+ values.each do |value|
296
+ yield name, value
297
+ end
298
+ end
299
+ end
300
+
301
+ # Request body part
302
+ attr_accessor :body
303
+ # Stream like a IO object that provides the request body part
304
+ attr_accessor :body_stream
305
+
306
+ def send_to(io)
307
+ to_packet.send_to(io)
308
+ end
309
+
310
+ # Returns an AJP packet object that represents the FORWARD_REQUEST packet
311
+ # corresponding to this request.
312
+ def to_packet
313
+ packet = Net::AJP13::Packet.new
314
+ packet.direction = :to_app
315
+ packet.append_byte FORWARD_REQUEST
316
+ packet.append_byte REQUEST_METHOD_CODES[method.upcase.intern]
317
+
318
+ # Mandatory parameters
319
+ [:protocol, :request_uri].each do |name|
320
+ raise "Mandatory parameter #{name} not supplied." unless __send__(name)
321
+ packet.append_string self.__send__(name)
322
+ end
323
+ [:remote_addr, :remote_host, :server_name].each do |name|
324
+ packet.append_string self.__send__(name)
325
+ end
326
+ raise "Mandatory parameter server_port not supplied" unless server_port
327
+ packet.append_integer server_port
328
+
329
+ packet.append_boolean is_ssl?
330
+
331
+ packet.append_integer self.length
332
+ self.each_header do |key, val|
333
+ if packed_name = SC_REQ_HEADER_NAMES[key.downcase]
334
+ packet.append_integer packed_name
335
+ packet.append_string val
336
+ else
337
+ packet.append_string key
338
+ packet.append_string val
339
+ end
340
+ end
341
+ self.each_attribute do |key, val|
342
+ if packed_name = SC_A_NAMES[key.downcase.to_sym]
343
+ packet.append_byte packed_name
344
+ packet.append_string val
345
+ else
346
+ packet.append_byte SC_A_REQ_ATTRIBUTE
347
+ packet.append_string key
348
+ packet.append_string val
349
+ end
350
+ end
351
+ packet.append_byte REQUEST_TERMINATOR
352
+
353
+ return packet
354
+ end
355
+ end
356
+
357
+ [
358
+ [ 'OPTIONS', true, true, 'Options' ],
359
+ [ 'GET', false, true, 'Get' ],
360
+ [ 'HEAD', false, false, 'Head' ],
361
+ [ 'POST', true, true, 'Post' ],
362
+ [ 'PUT', true, true, 'Put' ],
363
+ [ 'DELETE', false, true, 'Delete' ],
364
+ [ 'TRACE', true, true, 'Trace' ],
365
+ [ 'PROPFIIND', true, true, 'PropFind' ],
366
+ [ 'PROPPATCH', true, true, 'PropPatch' ],
367
+ [ 'MKCOL', true, true, 'MkCol' ],
368
+ [ 'COPY', true, true, 'Copy' ],
369
+ [ 'MOVE', true, true, 'Move' ],
370
+ [ 'LOCK', true, true, 'Lock' ],
371
+ [ 'UNLOCK', false, false, 'Unlock' ],
372
+ [ 'ACL', true, true, 'Acl' ],
373
+ [ 'REPORT', true, true, 'Report' ],
374
+ [ 'VERSION-CONTROL', true, true, 'VersionControl' ],
375
+ [ 'CHECKIN', true, true, 'CheckIn' ],
376
+ [ 'CHECKOUT', true, true, 'CheckOut' ],
377
+ [ 'UNCHECKOUT', true, true, 'UnCheckOut' ],
378
+ [ 'SEARCH', true, true, 'Search' ],
379
+ [ 'MKWORKSPACE', true, true, 'MkWorkSpace' ],
380
+ [ 'UPDATE', true, true, 'Update' ],
381
+ [ 'LABEL', true, true, 'Label' ],
382
+ [ 'MERGE', true, true, 'Merge' ],
383
+ [ 'BASELINE_CONTROL',true, true, 'BaseLineControl'],
384
+ [ 'MKACTIVITY', true, true, 'MkActivity' ]
385
+ ].each do |method_name, has_req_body, has_res_body, class_name, klass|
386
+ klass = Class.new(Net::AJP13::Request) do
387
+ const_set :METHOD, method_name
388
+ const_set :REQUEST_HAS_BODY, has_req_body
389
+ const_set :RESPONSE_HAS_BODY, has_res_body
390
+ end
391
+ Net::AJP13.const_set "#{class_name}Request", klass
392
+ end
393
+
394
+ # Represents AJP1.3 response
395
+ class Net::AJP13::Response
396
+ include Net::HTTPHeader
397
+ include Net::AJP13::Constants
398
+
399
+ # :stopdoc:
400
+ REASON_PHRASES = {
401
+ 100 => 'Continue',
402
+ 101 => 'Switching Protocols',
403
+ 200 => 'OK',
404
+ 201 => 'Created',
405
+ 202 => 'Accepted',
406
+ 203 => 'Non-Authoritative Information',
407
+ 204 => 'No Content',
408
+ 205 => 'Reset Content',
409
+ 206 => 'Partial Content',
410
+ 300 => 'Multiple Choices',
411
+ 301 => 'Moved Permanently',
412
+ 302 => 'Moved Temporarily',
413
+ 303 => 'See Other',
414
+ 304 => 'Not Modified',
415
+ 305 => 'Use Proxy',
416
+ 400 => 'Bad Request',
417
+ 401 => 'Unauthorized',
418
+ 402 => 'Payment Required',
419
+ 403 => 'Forbidden',
420
+ 404 => 'Not Found',
421
+ 405 => 'Method Not Allowed',
422
+ 406 => 'Not Acceptable',
423
+ 407 => 'Proxy Authentication Required',
424
+ 408 => 'Request Time-out',
425
+ 409 => 'Conflict',
426
+ 410 => 'Gone',
427
+ 411 => 'Length Required',
428
+ 412 => 'Precondition Failed',
429
+ 413 => 'Request Entity Too Large',
430
+ 414 => 'Request-URI Too Large',
431
+ 415 => 'Unsupported Media Type',
432
+ 500 => 'Internal Server Error',
433
+ 501 => 'Not Implemented',
434
+ 502 => 'Bad Gateway',
435
+ 503 => 'Service Unavailable',
436
+ 504 => 'Gateway Time-out',
437
+ }.freeze
438
+ REASON_PHRASES.each do |k,v|; v.freeze end
439
+
440
+ # Maps HTTP response header names into their codes
441
+ SC_RES_HEADER_NAMES = {
442
+ 0xA001 => 'Content-Type',
443
+ 0xA002 => 'Content-Language',
444
+ 0xA003 => 'Content-Length',
445
+ 0xA004 => 'Date',
446
+ 0xA005 => 'Last-Modified',
447
+ 0xA006 => 'Location',
448
+ 0xA007 => 'Set-Cookie',
449
+ 0xA008 => 'Set-Cookie2',
450
+ 0xA009 => 'Servlet-Engine',
451
+ 0xA00A => 'Status',
452
+ 0xA00B => 'WWW-AUthenticate'
453
+ }.freeze
454
+ SC_RES_HEADER_NAMES.each do |k,v|; v.freeze end
455
+
456
+ # Maps HTTP response header codes into their names.
457
+ SC_RES_HEADER_CODES = SC_RES_HEADER_NAMES.inject({}) do |memo, item|
458
+ code, name = item
459
+ memo[name.downcase.freeze] = code
460
+ memo
461
+ end
462
+ # :startdoc:
463
+
464
+ #
465
+ # Creates a new AJP13Response object
466
+ # +status+:: HTTP response status code. (in integer)
467
+ # +options+:: Hash that contains response headers.
468
+ # If key is :reason_phrase, it overrides the reason phrase.
469
+ def initialize(status, options = {})
470
+ @status = status.to_i
471
+ @reason_phrase =
472
+ options[:reason_phrase] || options['reason_phrase'] ||
473
+ REASON_PHRASES[@status]
474
+ initialize_http_header options.reject{|k, v| k.to_sym == :reason_phrase }
475
+ end
476
+
477
+ # Status Code
478
+ attr_reader :status
479
+ # Status Code as String
480
+ attr_reader :code
481
+ def code #:nodoc:
482
+ status.to_s
483
+ end
484
+
485
+ # Reason Phrase
486
+ attr_reader :reason_phrase
487
+ alias :message :reason_phrase
488
+
489
+ # The response body
490
+ attr_accessor :body
491
+ # Input stream like an IO object, which provides the response body.
492
+ attr_accessor :body_stream
493
+
494
+ # Creates a new AJP13::Response object based on bytes read from +io+.
495
+ # [Pre-Condition] The read position of +io+ is the head of an AJP packet
496
+ # whose prefix code is SEND_HEADERS.
497
+ # [Post-Condition] The read positioin of +io+ is just behind of the AJP
498
+ # packet if no exception raised.
499
+ # The read position is unspecified if an exception raised.
500
+ # Raises
501
+ # AJPPacketError:: if the given packet is bloken.
502
+ # ArgumentError:: if the given packet is not a SEND_HEADERS packet.
503
+ def self.from_io(io)
504
+ from_packet(Net::AJP13::Packet.from_io(io))
505
+ end
506
+
507
+ def self.from_packet(packet) #:nodoc: internal use
508
+ raise ArgumentError, 'The AJP response packet is not from an application container' unless packet.from_app?
509
+
510
+ raise ArgumentError, "The AJP response packet is not SEND_HEADERS but #{packet.messge_type}" unless packet.message_type == SEND_HEADERS
511
+
512
+ status = packet.read_integer
513
+ phrase = packet.read_string
514
+ res = self.new(status, :reason_phrase => phrase)
515
+
516
+ num_headers = packet.read_integer
517
+ 1.upto(num_headers) do
518
+ if packet.peek_byte == 0xA0
519
+ header_code = packet.read_integer
520
+ header_name = SC_RES_HEADER_NAMES[header_code]
521
+ raise Net::AJP13::AJPPacketError, "Unrecognized header code #{header_code}" unless header_name
522
+ else
523
+ header_name = packet.read_string
524
+ end
525
+ header_value = packet.read_string
526
+ res.add_field(header_name, header_value)
527
+ end
528
+
529
+ return res
530
+ end
531
+
532
+ def send_to(io)
533
+ to_packet.send_to(io)
534
+ end
535
+
536
+ # Returns a Net::AJP13::Packet object that represents the SEND_HEADER packet
537
+ # corresponding to this response.
538
+ def to_packet
539
+ packet = Net::AJP13::Packet.new
540
+ packet.direction = :from_app
541
+ packet.append_byte SEND_HEADERS
542
+ packet.append_integer self.status
543
+ packet.append_string self.reason_phrase
544
+ packet.append_integer self.length
545
+ self.each_header do |key, val|
546
+ if packed_name = SC_RES_HEADER_CODES[key.downcase]
547
+ packet.append_integer packed_name
548
+ else
549
+ packet.append_string key
550
+ end
551
+ packet.append_string val
552
+ end
553
+ packet
554
+ end
555
+ end
556
+
557
+ # Raised when ajp session takes an illegal state.
558
+ class Net::AJP13::AJPStateError < IOError
559
+ end
560
+
561
+ # Represents errors on AJP packet format
562
+ class Net::AJP13::AJPPacketError < IOError
563
+ end
564
+
565
+ # :stopdoc:
566
+ # Represents AJP1.3 Packet
567
+ class Net::AJP13::Packet
568
+ include Net::AJP13::Constants
569
+
570
+ # The magic number that represents a packet is from a web server to
571
+ # an app server.
572
+ AJP13_WS_HEADER = "\x12\x34"
573
+
574
+ # The magic number that represents a packet is from an app server to
575
+ # a web server.
576
+ # corresponds to 'AB' in ASCII.
577
+ AJP13_SW_HEADER = "\x41\x42"
578
+
579
+ # :stopdoc:
580
+ # suiting Ruby 1.9 feature
581
+ if RUBY_VERSION < '1.9'
582
+ # calls private methods
583
+ alias_method :fcall, :__send__
584
+ end
585
+ # :startdoc:
586
+
587
+ # Creates a new packet object.
588
+ def initialize
589
+ @direction = nil
590
+ @packet_length = 0
591
+ @buf = ''
592
+ end
593
+
594
+ # Reads an AJP packet from +io+, and creates a Packet object based on it.
595
+ # [Pre-condition] The read position of +io+ is the head of an AJP packet.
596
+ # [Post-condition] The read position of +io+ is just behind of the AJP packet
597
+ # header when the object creation succeeded.
598
+ # The read position is unspecified if an exception raised.
599
+ #
600
+ # Raises
601
+ # [AJPPacketError] when the read packet has invalid format.
602
+ # [IOError] when +io+ raises it.
603
+ def self.from_io(io)
604
+ p = self.new
605
+ p.fcall(:initialize_by_io, io)
606
+ p
607
+ end
608
+
609
+ # Initializer used by Packet.from_io
610
+ def initialize_by_io(io) #:nodoc: internal use
611
+ header = io.read(4)
612
+ raise Net::AJP13::AJPPacketError, "The AJP packet is broken" unless
613
+ header and header.length == 4
614
+
615
+ case header[0..1]
616
+ when AJP13_WS_HEADER
617
+ @direction = :to_app
618
+ when AJP13_SW_HEADER
619
+ @direction = :from_app
620
+ else
621
+ raise Net::AJP13::AJPPacketError, "Unrecognized AJP packet direction"
622
+ end
623
+
624
+ @packet_length = header[2..3].unpack('n')[0]
625
+
626
+ @byte_stream = io
627
+ @pos = 0 # read position
628
+
629
+ @buf = nil
630
+ end
631
+ private :initialize_by_io
632
+
633
+ # The value is
634
+ # [:from_app] if this packet is from an app server to a web server.
635
+ # [:to_app] if this packet is from an web server to an app server.
636
+ # [nil] if this packet's direction has not been not specified yet.
637
+ attr_accessor :direction
638
+
639
+ def direction=(val) #:nodoc:
640
+ if [nil, :to_app, :from_app].include? val
641
+ @direction = val
642
+ else
643
+ raise ArgumentError, "Illegal packet direction value #{val}"
644
+ end
645
+ end
646
+
647
+ # returns whether the packet is from a web server to an app server
648
+ def to_app?
649
+ direction == :to_app
650
+ end
651
+
652
+ # return wheter the packet is from an app server to a web server
653
+ def from_app?
654
+ direction == :from_app
655
+ end
656
+
657
+ def append_bytes(bytes)
658
+ @buf << bytes
659
+ end
660
+
661
+ def append_byte(val)
662
+ raise ArgumentError, "Too large to pack into a byte: #{val}" if val > 0xFF
663
+ @buf << [val].pack('C')
664
+ end
665
+
666
+ def append_boolean(val)
667
+ @buf <<
668
+ if val then "\x01" else "\x00" end
669
+ end
670
+ def append_integer(val)
671
+ val = val.to_int
672
+ raise ArgumentError, "#{val} is too large to store into an AJP packet" if
673
+ val > 0xFFFF
674
+ @buf << [val].pack('n')
675
+ end
676
+ def append_string(str)
677
+ if str.nil?
678
+ @buf << "\xFF\xFF"
679
+ else
680
+ str = str.to_str
681
+ @buf << [str.length].pack('n') << str << "\0"
682
+ end
683
+ end
684
+
685
+ # returns whether the read position is over the packet length.
686
+ def eof?
687
+ @byte_stream.eof? or
688
+ @pos >= @packet_length
689
+ end
690
+
691
+ # Type of message contained in the packet.
692
+ #
693
+ # Reads a byte (type code) from the packet when called first.
694
+ def message_type
695
+ @message_type ||= read_byte
696
+ end
697
+
698
+ # Reads bytes from the packet, and advance the read position.
699
+ # returns +nil+ if the read position is over the packet length.
700
+ def read_bytes(length)
701
+ return nil unless @pos < @packet_length
702
+ length = @packet_length - @pos if @packet_length - @pos < length
703
+ bytes = @byte_stream.read(length)
704
+ @pos += bytes.length if bytes
705
+ bytes
706
+ end
707
+
708
+ # reads a byte from the packet, and advance the read position.
709
+ # returns +nil+ if the read position is over the packet length.
710
+ def read_byte
711
+ return nil unless @pos < @packet_length
712
+ byte = @byte_stream.getc
713
+ @pos += 1 if byte
714
+ byte
715
+ end
716
+
717
+ def unread_byte(byte)
718
+ @byte_stream.ungetc(byte)
719
+ @pos -= 1
720
+ nil
721
+ end
722
+
723
+ # reads a byte from the packet, but does not advance the read position.
724
+ # returns +nil+ if the read position is over the packet length.
725
+ def peek_byte
726
+ return nil unless @pos < @packet_length
727
+ byte = @byte_stream.getc
728
+ @byte_stream.ungetc(byte) if byte
729
+ byte
730
+ end
731
+
732
+ # reads a boolean value from the packet, and advance the read position.
733
+ # returns +nil+ if the read position is over the packet length.
734
+ def read_boolean
735
+ byte = read_byte
736
+
737
+ case byte
738
+ when nil: nil
739
+ when 0x00: false
740
+ when 0x01: true
741
+ else
742
+ raise Net::AJP13::AJPPacketError,
743
+ "Can't recognize #{byte} as an boolean value"
744
+ end
745
+ end
746
+
747
+ # reads a 16bit integer from the packet, and advance the read position.
748
+ # returns +nil+ if the read position is over the packet length.
749
+ def read_integer
750
+ return nil unless @pos < @packet_length
751
+ raise Net::AJP13::AJPPacketError, "too short to read as integer" if
752
+ @packet_length - @pos < 2
753
+
754
+ int = @byte_stream.read(2)
755
+ raise Net::AJP13::AJPPacketError, "broken packet" if int.nil?
756
+ @pos += int.length
757
+ raise Net::AJP13::AJPPacketError, "broken packet" if int.length < 2
758
+ int.unpack('n')[0]
759
+ end
760
+
761
+ # reads a string from the packet, and advance the read position.
762
+ # returns +nil+ if the read position is over the packet length.
763
+ def read_string
764
+ len = read_integer
765
+ return nil unless len and len != 0xFFFF
766
+ raise Net::AJP13::AJPPacketError, "str length too large: #{len}" if
767
+ len > @packet_length - @pos
768
+
769
+ str = @byte_stream.read(len)
770
+ @pos += str.length
771
+ if str.nil? or str.length != len
772
+ raise Net::AJP13::AJPPacketError, "Invalid string format"
773
+ end
774
+
775
+ trailer = @byte_stream.getc
776
+ @pos += 1 if trailer
777
+ raise Net::AJP13::AJPPacketError, "Missing trailing NUL" unless trailer == 0x00
778
+
779
+ str
780
+ end
781
+
782
+ # Sends this packet into the specified socket
783
+ # +io+:: IO object to which this packet will be written.
784
+ def send_to(io)
785
+ raise 'The packet is too large' if @buf.length > 0xFFFF + 4
786
+ warn "The packet is too larget for some implementations: #{@buf.length} bytes" if @buf.length > MAX_PACKET_SIZE
787
+
788
+ header = "\0\0\0\0"
789
+ header[0..1] =
790
+ case @direction
791
+ when :from_app
792
+ AJP13_SW_HEADER
793
+ when :to_app
794
+ AJP13_WS_HEADER
795
+ else
796
+ raise Net::AJP13::AJPPacketError, 'Packet direction not specified'
797
+ end
798
+
799
+ header[2..3] = [@buf.length].pack('n')
800
+ io.write header
801
+ io.write @buf
802
+ io.flush
803
+ end
804
+
805
+ # length of the packet content, without including the payload
806
+ def length
807
+ if @buf
808
+ @buf.length
809
+ else
810
+ @packet_length
811
+ end
812
+ end
813
+ end
814
+ # :startdoc:
815
+