1 # -*- encoding: binary -*-
2 # internal implementation details here, do not rely on them in your code
4 # This class is needed because Net::HTTP streaming is still inefficient
5 # for reading huge response bodies over fast LANs.
6 class MogileFS::HTTPReader < MogileFS::Socket
7 attr_accessor :content_length, :uri
9 # backwards compat, if anybody cares
10 alias mogilefs_size content_length # :nodoc:
12 # this may OOM your system on large files
15 read(@content_length, buf)
16 return buf if buf.size == @content_length
18 raise MogileFS::SizeMismatchError,
19 "read=#{buf.size} bytes, expected=#@content_length from #@uri", []
22 def self.first(paths, http_method, timeout)
26 sock = new(path, http_method, timeout) and return sock
29 errors << "#{path} failed with #{e.message} (#{e.class})"
32 raise MogileFS::Error,
33 "all paths failed with #{http_method}: #{errors.join(', ')}", []
36 # given a path, this returns a readable socket with ready data from the
37 # body of the response.
38 def self.new(path, http_method, timeout)
40 sock = tcp(uri.host, uri.port, timeout)
41 buf = "#{http_method} #{uri.request_uri} HTTP/1.0\r\n\r\n" # no chunking
42 sock.timed_write(buf, timeout)
44 sock.timed_peek(2048, buf, timeout) or
45 raise MogileFS::InvalidResponseError, "EOF on #{http_method} #{uri}", []
47 head, _ = buf.split(/\r\n\r\n/, 2)
49 # we're dealing with a seriously slow/stupid HTTP server if we can't
50 # get the header in a single recv(2) syscall.
51 if head =~ %r{\AHTTP/\d+\.\d+\s+200\s*} &&
52 head =~ %r{^Content-Length:\s*(\d+)}i
53 sock.content_length = $1.to_i
60 # slice off the top of the socket buffer to allow IO.copy_stream
62 sock.timed_read(head.bytesize + 4, buf, 0)
66 raise MogileFS::InvalidResponseError,
67 "#{http_method} on #{uri} returned: #{head.inspect}", []
69 sock.close unless sock.closed?