ruby-ajp 0.1.5
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/COPYING +504 -0
- data/Install.en +231 -0
- data/Install.ja +250 -0
- data/NEWS.en +13 -0
- data/NEWS.ja +12 -0
- data/README.en +56 -0
- data/README.ja +54 -0
- data/Rakefile +24 -0
- data/example/dump-server.rb +68 -0
- data/example/error-server.rb +13 -0
- data/example/hello-server.rb +18 -0
- data/lib/net/ajp13.rb +815 -0
- data/lib/net/ajp13client.rb +361 -0
- data/lib/net/ajp13server.rb +430 -0
- data/ruby-ajp.gemspec +27 -0
- data/setup.rb +1585 -0
- data/test/net/data/ajp13request-data.1 +0 -0
- data/test/net/data/ajp13response-header.1 +0 -0
- data/test/net/data/ajp13response-webdav.1 +0 -0
- data/test/net/data/ajp13response-webdav.2 +0 -0
- data/test/net/data/rand +0 -0
- data/test/net/test_ajp13client.rb +353 -0
- data/test/net/test_ajp13packet.rb +320 -0
- data/test/net/test_ajp13request.rb +177 -0
- data/test/net/test_ajp13response.rb +162 -0
- data/test/net/test_ajp13server.rb +548 -0
- metadata +73 -0
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
|
+
|