ruby-ajp 0.1.5 → 0.2.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/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: []