ruby-ajp 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/rdoctask'
4
4
  require 'rake/gempackagetask'
5
5
 
6
6
  Rake::TestTask.new do |t|
7
- t.libs << "test"
7
+ t.libs << "test" << "lib"
8
8
  t.pattern = [ 'test/**/test_ajp13*.rb', 'test/**/more_test_ajp13*.rb' ]
9
9
  t.verbose = true
10
10
  end
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/ruby
2
- require 'net/ajp13server'
2
+ require 'net/ajp13/server'
3
3
  require 'erb'
4
4
 
5
5
  class DumpServer < Net::AJP13::Server
@@ -58,7 +58,7 @@ __END__
58
58
  <% if req.body_stream %>
59
59
  <% body = req.body_stream.read %>
60
60
  <% if %r(\Atext/) =~ req['content-type'] or
61
- req['content-type'] == 'application/x-form-www-urlencoded' %>
61
+ req['content-type'] == 'application/x-www-form-urlencoded' %>
62
62
  <pre><%=h body %></pre>
63
63
  <% else %>
64
64
  <pre><%=[body].pack('m')%></pre>
@@ -0,0 +1,187 @@
1
+ require 'net/ajp13'
2
+ require 'cgi'
3
+ require 'forwardable'
4
+
5
+ # The adapter to adapt Net::AJP13::Request and Net::AJP13::Response into
6
+ # CGI's interface.
7
+ class Net::AJP13::AJP13CGI
8
+ include ::CGI::QueryExtension
9
+ extend Forwardable
10
+
11
+ def initialize(req)
12
+ @req = req
13
+ if req.method == "POST" and
14
+ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(req['content-type']) then
15
+ boundary = $1.dup
16
+ @multipart = true
17
+ @params = read_multipart(boundary, req.content_length)
18
+ elsif req.body_stream and
19
+ req['content-type'] == 'application/x-www-form-urlencoded'
20
+ @multipart = false
21
+ @params = CGI::parse(req.body = req.body_stream.read)
22
+ req.body_stream = nil
23
+ elsif qs = query_string
24
+ @multipart = false
25
+ @params = CGI::parse(qs)
26
+ else
27
+ @multipart = false
28
+ @params = {}
29
+ end
30
+
31
+ @env_table = self.method(:env)
32
+ class << @env_table
33
+ def include?(key)
34
+ call(key).nil?
35
+ end
36
+ alias :key? :include?
37
+ end
38
+ @cookies = CGI::Cookie::parse(req['cookie'])
39
+ end
40
+
41
+ # Created AJP13::Response object.
42
+ attr_reader :response
43
+
44
+ MESSAGE_TO_STATUS = {
45
+ :OK => [200, "OK"],
46
+ :PARTIAL_CONTENT => [206, "Partial Content"],
47
+ :MULTIPLE_CHOICES => [300, "Multiple Choices"],
48
+ :MOVED => [301, "Moved Permanently"],
49
+ :REDIRECT => [302, "Found"],
50
+ :NOT_MODIFIED => [304, "Not Modified"],
51
+ :BAD_REQUEST => [400, "Bad Request"],
52
+ :AUTH_REQUIRED => [401, "Authorization Required"],
53
+ :FORBIDDEN => [403, "Forbidden"],
54
+ :NOT_FOUND => [404, "Not Found"],
55
+ :METHOD_NOT_ALLOWED => [405, "Method Not Allowed"],
56
+ :NOT_ACCEPTABLE => [406, "Not Acceptable"],
57
+ :LENGTH_REQUIRED => [411, "Length Required"],
58
+ :PRECONDITION_FAILED => [412, "Rrecondition Failed"],
59
+ :SERVER_ERROR => [500, "Internal Server Error"],
60
+ :NOT_IMPLEMENTED => [501, "Method Not Implemented"],
61
+ :BAD_GATEWAY => [502, "Bad Gateway"],
62
+ :VARIANT_ALSO_VARIES => [506, "Variant Also Negotiates"],
63
+ }.freeze
64
+
65
+ def header(arg = "text/html")
66
+ if arg.kind_of? String
67
+ @response = Net::AJP13::Response.new(200)
68
+ @response['content-type'] = arg
69
+ elsif arg.respond_to?(:each) and arg.respond_to?(:[])
70
+ if status = arg['status']
71
+ raise ArgumentError, "Unrecognized status line format: #{status}" unless /\A(?:([0-9]{3}) )?(\w+)\Z/ =~ status
72
+ status_line = $1 ? [$1.to_i, $2] : MESSAGE_TO_STATUS[$2.to_sym]
73
+ raise ArgumentError, "Unrecognized status line: #{status}" unless status_line
74
+ @response = Net::AJP13::Response.new(status_line[0], :reason_phrase => status_line[1])
75
+ else
76
+ @response = Net::AJP13::Response.new(200)
77
+ end
78
+ type = nil; charset = nil
79
+ arg.each do |name, value|
80
+ case name.downcase
81
+ when 'nph', 'status'
82
+ # do nothing
83
+ when "type"
84
+ type = value
85
+ when "charset"
86
+ charset = value
87
+ when 'length'
88
+ @response['content-length'] = value.to_s
89
+ when 'language'
90
+ @response['content-language'] = value
91
+ when 'cookie'
92
+ case value
93
+ when String
94
+ @respose.add_header('set-cookie', value)
95
+ when Array, Hash
96
+ value.each {|val| @response.add_header('set-cookie', val.to_s) }
97
+ end
98
+ else
99
+ @response[name] = value
100
+ end
101
+ end
102
+ type = 'text/html' unless type
103
+ @response['content-type'] = charset ? ("%s; charset=%s" % [type, charset]) : type
104
+ else
105
+ raise ArgumentError, "argument is not a String nor Hash"
106
+ end
107
+ end
108
+
109
+
110
+ def output(arg = 'text/html')
111
+ content = yield
112
+ header(arg)
113
+ @response['content-length'] ||= content.length
114
+ unless content.nil? or @request.method == 'HEAD'
115
+ @response.body = content
116
+ end
117
+ end
118
+
119
+ # Method object that contains #env
120
+ attr_reader :env_table
121
+
122
+ # Simulates environment variable table that Common Gateway Interface defines.
123
+ def env(name)
124
+ name = name.downcase
125
+ key = name.to_sym
126
+ if [
127
+ :auth_type, :content_length, :content_type, :gateway_interface,
128
+ :path_info, :path_translated, :query_string, :remote_addr, :remote_host,
129
+ :remote_ident, :remote_user, :request_method, :request_url, :script_name,
130
+ :server_name, :server_port, :server_protocol, :server_software
131
+ ].include?(key) then
132
+ return __send__(key)
133
+ elsif /\Ahttp_(\w+)\Z/ =~ name
134
+ return @req[$1.tr('_', '-')]
135
+ else
136
+ return nil
137
+ end
138
+ end
139
+ private :env
140
+
141
+ def_delegators :@req,
142
+ :content_length, :remote_addr, :remote_host, :server_name, :server_port
143
+
144
+ [
145
+ :auth_type,
146
+ # :path_info,
147
+ :path_translated,
148
+ :remote_ident, :remote_user,
149
+ :script_name, :server_software
150
+ ].each do |attr_name|
151
+ define_method(attr_name) do
152
+ val = @req.get_attributes(attr_name.to_s)
153
+ val and val[0]
154
+ end
155
+ end
156
+
157
+ def content_type
158
+ @req['content-type']
159
+ end
160
+
161
+ def gateway_interface
162
+ 'AJP/1.3'
163
+ end
164
+
165
+ def path_info
166
+ #val = @req.get_attributes('path_info')
167
+ #val && val[0] or @req.path
168
+ @req.path
169
+ end
170
+
171
+ def query_string
172
+ qs = @req.get_attributes('query_string')
173
+ qs and qs.join('&')
174
+ end
175
+
176
+ def request_method
177
+ @req.method
178
+ end
179
+
180
+ def request_url
181
+ @req.path
182
+ end
183
+
184
+ def server_protocol
185
+ @req.protocol
186
+ end
187
+ end
File without changes
@@ -83,7 +83,6 @@ class Net::AJP13::Server
83
83
  end
84
84
 
85
85
  def start(sock = nil)
86
- logger.info("Starting #{self.class}")
87
86
  if sock
88
87
  @sock = sock
89
88
  else
@@ -96,13 +95,12 @@ class Net::AJP13::Server
96
95
  accepted = @sock.accept
97
96
  Thread.new {
98
97
  begin
99
- until accepted.closed?
100
- process(accepted)
101
- end
98
+ accepted.sync = false
99
+ process(accepted)
102
100
  rescue StandardError => err
103
- logger.error("#{err.message} from #{err.backtrace("\n")}")
101
+ logger.error("#{err.message} from #{err.backtrace.join("\n")}")
104
102
  rescue Object => err
105
- logger.fatal("#{err.message} from #{err.backtrace("\n")}")
103
+ logger.fatal("#{err.message} from #{err.backtrace.join("\n")}")
106
104
  else
107
105
  logger.debug("closed")
108
106
  ensure
@@ -133,18 +131,23 @@ class Net::AJP13::Server
133
131
 
134
132
  # +conn+:: Accepted connection. +conn+ is an IO object or something like it.
135
133
  def process(conn)
136
- packet = Net::AJP13::Packet.from_io(conn)
137
- case packet.message_type
138
- when FORWARD_REQUEST
139
- process_forward_request(packet, conn)
140
- when SHUTDOWN
141
- process_shutdown(packet, conn)
142
- when PING
143
- process_ping(packet, conn)
144
- when CPING
145
- process_cping(packet, conn)
146
- else
147
- raise AJPPacketError, "Unrecognized packet type #{packet.message_type}"
134
+ loop do
135
+ break unless c = conn.getc
136
+ conn.ungetc c
137
+
138
+ packet = Net::AJP13::Packet.from_io(conn)
139
+ case packet.message_type
140
+ when FORWARD_REQUEST
141
+ process_forward_request(packet, conn)
142
+ when SHUTDOWN
143
+ process_shutdown(packet, conn)
144
+ when PING
145
+ process_ping(packet, conn)
146
+ when CPING
147
+ process_cping(packet, conn)
148
+ else
149
+ raise AJPPacketError, "Unrecognized packet type #{packet.message_type}"
150
+ end
148
151
  end
149
152
  end
150
153
 
@@ -178,6 +181,7 @@ class Net::AJP13::Server
178
181
  conn.write "\x41\x42#{[message.length + 4].pack('n')}\x03#{[message.length].pack('n')}#{message}\x00"
179
182
  else
180
183
  # SEND_HEADERS packet
184
+ res ||= Net::AJP13::Response.new(500)
181
185
  res['content-length'] ||= res.body.length.to_s if res.body
182
186
  res.send_to conn
183
187
 
@@ -241,6 +245,7 @@ class Net::AJP13::Server
241
245
  @sock = sock
242
246
  @packet = Net::AJP13::Packet.from_io(sock)
243
247
  @length = length
248
+ packet_content_length = @packet.read_integer
244
249
  @read_length = 0
245
250
  end
246
251
 
@@ -346,7 +351,8 @@ class Net::AJP13::Server
346
351
  # this means eof
347
352
  break
348
353
  else
349
- chunk = @packet.read_bytes(length - written_length)
354
+ packet_content_length = @packet.read_integer
355
+ chunk = @packet.read_bytes([length - written_length, packet_content_length].min)
350
356
  buf[written_length, chunk.length] = chunk
351
357
  written_length += chunk.length
352
358
  @read_length += chunk.length
data/lib/net/ajp13.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # = Ruby/AJP
2
- # An implementation of AJP(Apache Jserv Protocol) 1.3 in Ruby,
2
+ # Ruby/AJP is an implementation of AJP(Apache Jserv Protocol) 1.3 in Ruby,
3
3
  # based on http://tomcat.apache.org/connectors-doc/common/ajpv13a.html.
4
4
  #
5
5
  # [Net::AJP13::Client] provides high-level API to implement AJP clients.
6
- # The interface of the client-side library is similar to
6
+ # The interface of this client-side library is similar to
7
7
  # net/http.
8
8
  # see ajp13client.rb[link:files/lib/net/ajp13client_rb.html]
9
9
  # for more detail.
@@ -33,9 +33,8 @@
33
33
 
34
34
  require 'net/http'
35
35
 
36
- # :stopdoc:
37
- module Net; end
38
- # :startdoc:
36
+ module Net #:nodoc:
37
+ end
39
38
 
40
39
  module Net::AJP13
41
40
  module Constants
@@ -93,7 +92,7 @@ class Net::AJP13::Request
93
92
  }.freeze
94
93
  SC_REQ_HEADER_NAMES.each_key {|k| k.freeze}
95
94
 
96
- SC_A_REQ_ATTRIBUTE = 0xA0
95
+ SC_A_REQ_ATTRIBUTE = 0x0A
97
96
  # Maps request attribute names into their codes
98
97
  SC_A_NAMES = {
99
98
  :context => 0x01,
@@ -194,7 +193,7 @@ class Net::AJP13::Request
194
193
  else
195
194
  header_name = packet.read_string
196
195
  end
197
- req[header_name] = packet.read_string
196
+ req.add_field(header_name, packet.read_string)
198
197
  end
199
198
  loop do
200
199
  case attr_name = packet.read_byte
@@ -252,6 +251,7 @@ class Net::AJP13::Request
252
251
  # HTTP-side connection is over SSL or not.
253
252
  attr_accessor :is_ssl
254
253
  alias :is_ssl? :is_ssl
254
+ alias :ssl? :is_ssl
255
255
  def is_ssl=(value) #:nodoc:
256
256
  @is_ssl = !!value
257
257
  end
@@ -562,7 +562,7 @@ end
562
562
  class Net::AJP13::AJPPacketError < IOError
563
563
  end
564
564
 
565
- # :stopdoc:
565
+ # :enddoc:
566
566
  # Represents AJP1.3 Packet
567
567
  class Net::AJP13::Packet
568
568
  include Net::AJP13::Constants
data/ruby-ajp.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "ruby-ajp"
3
- spec.version = "0.1.5"
3
+ spec.version = "0.2.0"
4
4
  spec.required_ruby_version = ">= 1.8.3"
5
5
  spec.summary = "An implementation of Apache Jserv Protocol 1.3 in Ruby"
6
6
  spec.author = "Yugui"
@@ -20,8 +20,7 @@ Gem::Specification.new do |spec|
20
20
  "COPYING"
21
21
  spec.files.reject! {|fn| fn.include?('.svn') }
22
22
  spec.test_files = [
23
- 'packet', 'request',
24
- 'response', 'client'
23
+ 'packet', 'request', 'response', 'client', 'server'
25
24
  ].map{|x| "test/net/test_ajp13#{x}.rb"}
26
25
  spec.has_rdoc = true
27
26
  end
@@ -0,0 +1,104 @@
1
+ require 'test/unit'
2
+ require 'stringio'
3
+ require File.dirname(__FILE__) + "/../../lib/net/ajp13/ajp13cgi.rb"
4
+
5
+ class Net::AJP13::AJP13CGITest < Test::Unit::TestCase
6
+ def setup
7
+ @encoded_body =
8
+ "test%5Bfirst%5Fname%5D=foo" +
9
+ "&test%5blast%5Fname%5D=bar+hoge" +
10
+ "&test%5Bemail%5D=foo%2Dbar%40yet%2Eanother%2Edomain%2Etest" +
11
+ "&key+with+SP=huga" +
12
+ "&nanika=unyuu" +
13
+ "&last=%5Ce" +
14
+ ""
15
+ @encoded_body.freeze
16
+ @qs = "last=%5Cufin.%5Ce"
17
+
18
+ req = Net::AJP13::PostRequest.new('/path/to/resource')
19
+ req.server_name = 'www.domain.test'
20
+ req.server_port = 8181
21
+ req.is_ssl = true
22
+ req.body_stream = StringIO.new(@encoded_body)
23
+ req['Host'] = 'www.domain.test'
24
+ req['Content-Length'] = @encoded_body.length.to_s
25
+ req['Content-Type'] = 'application/x-www-form-urlencoded'
26
+ req['Content-Language'] = 'ja-JP'
27
+ req['User-Agent'] = @ua = "testcase #{__FILE__}:#{__LINE__}"
28
+ req.set_attribute('QUERY_STRING', @qs)
29
+
30
+ @cgi = Net::AJP13::AJP13CGI.new(req)
31
+ end
32
+
33
+ def test_at
34
+ assert_equal 'foo', @cgi['test[first_name]']
35
+ assert_equal 'bar hoge', @cgi['test[last_name]']
36
+ assert_equal 'foo-bar@yet.another.domain.test', @cgi['test[email]']
37
+ assert_equal 'huga', @cgi['key with SP']
38
+ assert_equal 'unyuu', @cgi['nanika']
39
+ assert_equal '\e', @cgi['last']
40
+ assert_equal '', @cgi['test[invalid_key]']
41
+ assert_equal '', @cgi['']
42
+
43
+ assert_equal '', @cgi['Test[first_name]'] # case sensitive
44
+ assert_equal '', @cgi['test[Last_name]'] # case sensitive
45
+ end
46
+
47
+ def test_has_key?
48
+ assert @cgi.has_key?('test[first_name]')
49
+ assert @cgi.has_key?('test[last_name]')
50
+ assert @cgi.has_key?('test[email]')
51
+ assert @cgi.has_key?('key with SP')
52
+ assert !@cgi.has_key?('test[invalid_key]')
53
+ assert !@cgi.has_key?('')
54
+
55
+ assert !@cgi.has_key?('Test[first_name]')
56
+ end
57
+
58
+ def test_env_table
59
+ assert_equal @encoded_body.length, @cgi.env_table['CONTENT_LENGTH']
60
+ assert_equal 8181, @cgi.env_table['SERVER_PORT']
61
+ assert_equal 'www.domain.test', @cgi.env_table['HTTP_HOST']
62
+ assert_equal @qs, @cgi.env_table['Query_String']
63
+ assert_equal 'application/x-www-form-urlencoded', @cgi.env_table['content_type']
64
+ assert_equal 'AJP/1.3', @cgi.env_table['gateway_interface']
65
+ assert_equal @ua, @cgi.env_table['HTTP_USER_AGENT']
66
+ assert_equal 'ja-JP', @cgi.env_table['HTTP_CONTENT_LANGUAGE']
67
+ end
68
+
69
+ def test_params
70
+ assert_equal ['foo'], @cgi.params['test[first_name]']
71
+ assert_equal ['bar hoge'], @cgi.params['test[last_name]']
72
+ assert_equal ['foo-bar@yet.another.domain.test'], @cgi.params['test[email]']
73
+ assert_equal ['huga'], @cgi.params['key with SP']
74
+ assert_equal ['unyuu'], @cgi.params['nanika']
75
+ assert_equal ['\e'], @cgi.params['last']
76
+ assert_equal [], @cgi.params['test[invalid_key]']
77
+ assert_equal [], @cgi.params['']
78
+
79
+ assert_equal [], @cgi.params['Test[first_name]'] # case sensitive
80
+ assert_equal [], @cgi.params['test[Last_name]'] # case sensitive
81
+ end
82
+
83
+ def test_header_0
84
+ assert_nil @cgi.response
85
+ @cgi.header
86
+ assert_equal 'text/html', @cgi.response['Content-Type']
87
+ end
88
+
89
+ def test_header_1
90
+ assert_nil @cgi.response
91
+ @cgi.header('text/plain')
92
+ assert_equal 'text/plain', @cgi.response['Content-Type']
93
+ end
94
+
95
+ def test_header
96
+ assert_nil @cgi.response
97
+ @cgi.header(
98
+ "status" => "200 OK",
99
+ "charset" => "EUC-KR"
100
+ )
101
+ assert_equal 'text/html; charset=EUC-KR', @cgi.response['Content-Type']
102
+ assert_equal 'OK', @cgi.response.message
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  require 'test/unit'
2
- require File.dirname(__FILE__) + '/../../lib/net/ajp13client'
2
+ require File.dirname(__FILE__) + '/../../lib/net/ajp13/client'
3
3
 
4
4
  class Net::AJP13::ClientTest < Test::Unit::TestCase
5
5
  def setup
@@ -159,8 +159,8 @@ class Net::AJP13::RequestTest < Test::Unit::TestCase
159
159
  assert_equal "deflate, gzip, x-gzip, identity, *;q=0", value
160
160
  when 'cookie'
161
161
  assert_block {
162
- "JSESSIONID=54104A3A77560CEF8967D263F1A7193" == value or
163
- "$Version=1" == value
162
+ "JSESSIONID=54104A3A775650CEF8967D263F1A7193, $Version=1" == value or
163
+ "$Version=1, JSESSIONID=54104A3A775650CEF8967D263F1A7193" == value
164
164
  }
165
165
  when 'cache-control'
166
166
  assert_equal "no-cache", value
@@ -1,6 +1,6 @@
1
1
  require 'test/unit'
2
2
  require 'stringio'
3
- require File.dirname(__FILE__) + '/../../lib/net/ajp13server'
3
+ require File.dirname(__FILE__) + '/../../lib/net/ajp13/server'
4
4
 
5
5
  class Net::AJP13::Server
6
6
  unless method_defined?(:fcall)
@@ -26,7 +26,7 @@ class Net::AJP13::Server::BodyInputTest < Test::Unit::TestCase
26
26
  class MockSocket
27
27
  def initialize(contents)
28
28
  @bodies = (
29
- contents.map{|c| "\x12\x34#{[c.length].pack('n')}" + c } <<
29
+ contents.map{|c|"\x12\x34#{[c.length+2, c.length].pack('nn')}" + c } <<
30
30
  "\x12\x34\x00\x00"
31
31
  ).map {|str| StringIO.new(str)}
32
32
  @write_buf = ''
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: ruby-ajp
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.5
7
- date: 2006-01-09 00:00:00 +09:00
6
+ version: 0.2.0
7
+ date: 2006-01-24 00:00:00 +09:00
8
8
  summary: An implementation of Apache Jserv Protocol 1.3 in Ruby
9
9
  require_paths:
10
10
  - lib
@@ -28,9 +28,11 @@ cert_chain:
28
28
  authors:
29
29
  - Yugui
30
30
  files:
31
- - lib/net/ajp13server.rb
32
31
  - lib/net/ajp13.rb
33
- - lib/net/ajp13client.rb
32
+ - lib/net/ajp13/server.rb
33
+ - lib/net/ajp13/ajp13cgi.rb
34
+ - lib/net/ajp13/client.rb
35
+ - test/net/test_ajp13cgi.rb
34
36
  - test/net/test_ajp13request.rb
35
37
  - test/net/test_ajp13server.rb
36
38
  - test/net/test_ajp13response.rb
@@ -59,6 +61,7 @@ test_files:
59
61
  - test/net/test_ajp13request.rb
60
62
  - test/net/test_ajp13response.rb
61
63
  - test/net/test_ajp13client.rb
64
+ - test/net/test_ajp13server.rb
62
65
  rdoc_options: []
63
66
 
64
67
  extra_rdoc_files: []