Socket bug?

I’m not sure where to look for the bug that I seem to have found in the
socket library on Windows 2000. I’ve made some modifications to the Net
module (at end of message), and I am executing the following code using
it to send an HTTP GET request with a very long query string.

host = 'localhost’
port = 80
http = Net::HTTP.new( host, port )

request_method = 'GET’
request_has_body = FALSE
response_has_body = TRUE
N = 320
path = ‘/index.html’ + ‘?’ + ‘%FF’ * ( 41 * 1024 + N )
extra_headers = nil
req = Net::HTTPGenericRequest.new( request_method, request_has_body,
response_has_body, path, extra_headers )

post_data = nil
test = FALSE
response, = http.request( req, post_data, test )

I get the following error message from Ruby 1.7.3:

C:/Progra~1/Ruby/lib/ruby/1.7/net/protocol.rb:501:in sysread': Invalid argument (Errno::EINVAL) from C:/Progra~1/Ruby/lib/ruby/1.7/net/protocol.rb:501:inrbuf_fill’
from C:/Progra~1/Ruby/lib/ruby/1.7/net/protocol.rb:477:in
readuntil' from C:/Progra~1/Ruby/lib/ruby/1.7/net/protocol.rb:488:inreadline’
from C:/Progra~1/Ruby/lib/ruby/1.7/net/http.rb:1415:in
read_status_line' from C:/Progra~1/Ruby/lib/ruby/1.7/net/http.rb:1399:inread_new’
from
C:/Perforce_workspace/2.0.5/regress/bin/send_HTTP_request.rb:115:in
request' from C:/Perforce_workspace/2.0.5/regress/bin/send_HTTP_request.rb:108:inrequest’
from
C:/Perforce_workspace/2.0.5/regress/bin/send_HTTP_request.rb:106:in
start' from C:/Perforce_workspace/2.0.5/regress/bin/send_HTTP_request.rb:109:inrequest’
from
C:/Perforce_workspace/2.0.5/regress/bin/send_HTTP_request.rb:441

Ruby 1.6.7 gives a similar but not identical set of error messages:

C:/Perforce_workspace/2.0.5/nt/tools/Ruby/lib/ruby/1.6/net/protocol.rb:5
76:in write': Invalid argument (Errno::EINVAL) from C:/Perforce_workspace/2.0.5/nt/tools/Ruby/lib/ruby/1.6/net/protocol.rb:5 76:indo_write’
from
C:/Perforce_workspace/2.0.5/nt/tools/Ruby/lib/ruby/1.6/net/protocol.rb:5
59:in writeline' from C:/Perforce_workspace/2.0.5/nt/tools/Ruby/lib/ruby/1.6/net/protocol.rb:5 59:inwriting’
from
C:/Perforce_workspace/2.0.5/nt/tools/Ruby/lib/ruby/1.6/net/protocol.rb:5
59:in writeline' from C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:246:inrequest’
from
C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:194:in
exec' from C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:113:inrequest’
from
C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:108:in
request' from C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:107:instart’
from
C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:107:in
`request’
from
C:\Perforce_workspace\2.0.5\regress\bin\send_HTTP_request_TEST.rb:449

When the value of the variable “N” is less than 320, the sending of the
request works OK, but the error behavior occurs for N >= 320 for both
Ruby 1.7.3 and 1.6.7. I had hoped Ruby would be able to construct and
send query strings and POST request bodies of arbitrary size for
boundary case testing of our product, but I’m running up against this
error, which I don’t really know what to do about. Any ideas out there?

Thanks in advance,
Al

module Net
class HTTP
alias :old_request :request
# Modification: add “test” parameter.
def request( req, body = nil, test = FALSE, &block )
unless started? then
start {
req[‘connection’] = 'close’
return request(req, body, test, &block)
}
end

  begin_transport req
  req.exec @socket, @curr_http_version, edit_path(req.path), body,

test
begin
res = HTTPResponse.read_new(@socket)
end while HTTPContinue === res
res.reading_body(@socket, req.response_body_permitted?) {
yield res if block_given?
}
end_transport req, res

  res
end

attr_accessor :curr_http_version

end

module HTTPHeader
# Change behavior: do not set header names to lowercase.
alias :old_writer :[]=
def []=( key, val )
@header[ key.downcase ] = val
@header_array << [key, val]
end
end

class HTTPResponse
alias :old_initialize :initialize
def initialize( httpv, code, msg )
@http_version = httpv
@code = code
@message = msg

  @header = {}
  @body = nil
  @read = false

  @header_array = Array.new
end

attr_reader :header_array

end

class HTTPGenericRequest
attr_accessor :header

# Add header_array instance variable.
alias :old_initialize :initialize
def initialize( m, reqbody, resbody, path, initheader = nil )
  @method = m
  @request_has_body = reqbody
  @response_has_body = resbody
  @path = path

  @header = tmp = {}
  @header_array = Array.new

  return unless initheader
  initheader.each do |k,v|
    key = k.downcase
    if tmp.key? key then
      $stderr.puts "WARNING: duplicated HTTP header: #{k}" if

$VERBOSE
end
tmp[ key ] = v.strip
end
tmp[‘accept’] ||= '/'
end

alias :old_exec :exec
# Modification:  add "test" parameter.
def exec( sock, ver, path, body, test = FALSE )
  if body then
    raise ArgumentError, 'HTTP request body is not premitted' unless

request_body_permitted?
send_request_with_body sock, ver, path, body, test
else
request sock, ver, path, test
end
end

alias :old_send_request_with_body :send_request_with_body
# Modification:  add "test" parameter.
def send_request_with_body( sock, ver, path, body, test = FALSE )
  if block_given? then
    ac = Accumulator.new
    yield ac              # must be yield, DO NOT USE block.call
    data = ac.terminate
  else
    data = body
  end

  # Modification:  only calculate and set Content-Length if its

value hasn’t already been specified.
unless @header[‘content-length’]
@header[‘content-length’] = data.size.to_s
@header.delete 'transfer-encoding’
end

  # Modification:  only set Content-Type if its value hasn't already

been specified.
unless @header[‘content-type’]
$stderr.puts ‘Content-Type did not set; using
application/x-www-form-urlencoded’ if $VERBOSE
@header[‘content-type’] = 'application/x-www-form-urlencoded’
end

  # Modification:  add "test" argument.
  request sock, ver, path, test
  # Modification:  add if statement.
  if test then
    puts data
    exit
  else
    sock.write data
  end
end

alias :old_request :request
def request( sock, ver, path, test = FALSE )
  # Modification:  add "test" option.
  if test then
    sprintf('%s %s HTTP/%s', @method, path, ver)
    canonical_each do |k,v|
      puts k + ': ' + v
    end
    puts ''
  else
    sock.writeline sprintf('%s %s HTTP/%s', @method, path, ver)
    canonical_each do |k,v|
      sock.writeline k + ': ' + v
    end
    sock.writeline ''
  end
end

end
end

···


Albert Davidson Chou, QA Manager
TeaLeaf Technology, Inc.
(415) 932-5031
AChou@TeaLeaf.com | http://www.TeaLeaf.com/

Evaluate TeaLeaf Technology today:
http://www.TeaLeaf.com/demo/