class Segregate

Constants

ALL_HEADERS
DATE
ENTITY_HEADERS
GENERAL_HEADERS
HTTP_METHODS
REQUEST_HEADERS
REQUEST_LINE
RESPONSE_HEADERS
STATUS_LINE
UNKNOWN_REQUEST_LINE
VERSION

Attributes

body[RW]
headers[R]
http_version[R]
request_method[RW]
state[R]
status_code[RW]
status_phrase[RW]
type[R]
uri[R]

Public Class Methods

new(callback = nil, *args, **kwargs) click to toggle source
# File lib/segregate.rb, line 31
def initialize callback = nil, *args, **kwargs
  @debug = kwargs[:debug] ? true : false
  @callback = callback
  @http_version = [nil, nil]

  # :request, :response
  @type = Juncture.new :request, :response
  @state = Juncture.new :waiting, :headers, :body, :done, default: :waiting

  @headers = Hashie::Mash.new
  @body = ""

  @stashed_data = ""
  @stashed_body = ""

  @header_order = []
end

Public Instance Methods

build_body(raw_message) click to toggle source
# File lib/segregate.rb, line 126
def build_body raw_message
  new_body, size = deflate_if_needed(@body)
  if @headers['content-length']
    raw_message << new_body
    raw_message << "\r\n\r\n"
  elsif @headers['transfer-encoding'] == 'chunked'
    raw_message << "%s\r\n" % (size.to_s(16))
    raw_message << new_body + "\r\n"
    raw_message << "0\r\n\r\n"
  end
end
build_headers(raw_message) click to toggle source
# File lib/segregate.rb, line 111
def build_headers raw_message
  request? ? raw_message << request_line + "\r\n" : raw_message << status_line + "\r\n"
  @header_order.each do |header|
    values = @headers[header.downcase]
    if values.kind_of?(Array)
      values.each do |value|
        raw_message << "%s: %s\r\n" % [header, value]
      end
    else
      raw_message << "%s: %s\r\n" % [header, values]
    end
  end
  raw_message << "\r\n"
end
debug(message) click to toggle source
# File lib/segregate.rb, line 25
def debug message
  if @debug
    puts "DEBUG: " + message.to_s
  end
end
deflate_if_needed(body) click to toggle source
# File lib/segregate.rb, line 138
def deflate_if_needed(body)
  return [body, body.size] unless gzip_encoded_body?

  str_io = StringIO.new('w')
  w_gz = Zlib::GzipWriter.new(str_io)
  w_gz.write(body)
  w_gz.close

  # To specify the number of bytes for a gzip string
  # we should check for the buffer size instead of the string size
  [str_io.string, str_io.size]
end
done?() click to toggle source
# File lib/segregate.rb, line 61
def done?
  @state >= :done
end
headers_complete?() click to toggle source
# File lib/segregate.rb, line 57
def headers_complete?
  @state > :headers
end
major_http_version() click to toggle source
# File lib/segregate.rb, line 77
def major_http_version
  http_version[0]
end
major_http_version=(value) click to toggle source
# File lib/segregate.rb, line 81
def major_http_version= value
  http_version[0] = value
end
method_missing(meth, *args, &block) click to toggle source
Calls superclass method
# File lib/segregate.rb, line 13
def method_missing meth, *args, &block
  if @uri.respond_to? meth
    @uri.send meth, *args, &block
  else
    super
  end
end
minor_http_version() click to toggle source
# File lib/segregate.rb, line 85
def minor_http_version
  http_version[1]
end
minor_http_version=(value) click to toggle source
# File lib/segregate.rb, line 89
def minor_http_version= value
  http_version[1] = value
end
parse_data(data) click to toggle source
# File lib/segregate.rb, line 151
def parse_data data
  data = StringIO.new(@stashed_data + data)
  @stashed_data = ""

  while !data.eof? && @state < :done
    line, complete_line = get_next_line data
    complete_line ? parse_line(line) : @stashed_data = line
  end

  data.close
end
parse_line(line) click to toggle source
# File lib/segregate.rb, line 163
def parse_line line
  case @state.state
  when :waiting
    read_in_first_line line
  when :headers
    read_in_headers line
  when :body
    read_in_body line
  end

  @callback.on_message_complete self if @callback.respond_to?(:on_message_complete) && done?
end
raw_data() click to toggle source
# File lib/segregate.rb, line 101
def raw_data
  raw_message = ""
  update_content_length

  build_headers raw_message
  build_body raw_message

  return raw_message
end
request?() click to toggle source
# File lib/segregate.rb, line 49
def request?
  @type == :request
end
request_line() click to toggle source
# File lib/segregate.rb, line 65
def request_line
  request? ? "%s %s HTTP/%d.%d" % [request_method, request_url.to_s, *http_version] : nil
end
request_url() click to toggle source
# File lib/segregate.rb, line 73
def request_url
  uri ? uri.to_s : nil
end
respond_to?(meth, include_private = false) click to toggle source
Calls superclass method
# File lib/segregate.rb, line 21
def respond_to?(meth, include_private = false)
  @uri.respond_to?(meth, include_private) || super
end
response?() click to toggle source
# File lib/segregate.rb, line 53
def response?
  @type == :response
end
status_line() click to toggle source
# File lib/segregate.rb, line 69
def status_line
  response? ? "HTTP/%d.%d %d %s" % [*http_version, status_code, status_phrase] : nil
end
update_content_length() click to toggle source
# File lib/segregate.rb, line 93
def update_content_length
  unless @body.empty?
    if @headers['content-length']
      @headers['content-length'] = @body.length.to_s
    end
  end
end

Private Instance Methods

get_next_line(data) click to toggle source
# File lib/segregate.rb, line 178
def get_next_line data
  if @headers['content-length'] && @state >= :body
    line = data.readline("\r\n")
    @inital_line = line
    [line, true]
  else
    line = data.readline("\r\n")
    @inital_line = line
    result = line.end_with?("\r\n")
    line = line[0..-3] if result
    [line, result]
  end
end
gzip_encoded_body?() click to toggle source
# File lib/segregate.rb, line 272
def gzip_encoded_body?
  headers.key?('content-encoding') && headers['content-encoding'].eql?('gzip')
end
inflate_body_if_needed(body) click to toggle source
# File lib/segregate.rb, line 264
def inflate_body_if_needed(body)
  return body unless gzip_encoded_body?

  gzip_str  = StringIO.new(body)
  gzip_body = Zlib::GzipReader.new( gzip_str )
  gzip_body.read()
end
parse_body(line) click to toggle source
# File lib/segregate.rb, line 249
def parse_body line
  line = @stashed_body + line
  @stashed_body = ""

  if line.length >= headers['content-length'].to_i
    # Removing delimiters characters from the HTTP protocol
    line = line[0..-3]
    @body = inflate_body_if_needed(line)
    @callback.on_body @body if @callback.respond_to?(:on_body)
    @state.next
  else
    @stashed_body = line
  end
end
parse_chunked_data(line) click to toggle source
# File lib/segregate.rb, line 276
def parse_chunked_data line
  @chunked_body_state ||= Juncture.new :size, :chunk, default: :size

  case @chunked_body_state.state
  when :size
    parse_chunked_data_size line.to_i(16)
  when :chunk
    parse_chunked_data_chunk line
  end
end
parse_chunked_data_chunk(line) click to toggle source
# File lib/segregate.rb, line 296
def parse_chunked_data_chunk line
  line = @stashed_body + line
  @stashed_body = ""

  if line.length >= @chunk_size
    @body << line
    @original_body = @body
    @body = inflate_body_if_needed(@body)
    @callback.on_body line if @callback.respond_to?(:on_body)
    @chunked_body_state.next
  else
    @stashed_body = @inital_line.end_with?("\r\n") ? (line + "\r\n") : line
  end
end
parse_chunked_data_size(chunk_size) click to toggle source
# File lib/segregate.rb, line 287
def parse_chunked_data_size chunk_size
  if chunk_size == 0
    @state.next
  else
    @chunk_size = chunk_size
  end
  @chunked_body_state.next
end
parse_request_line(line) click to toggle source
# File lib/segregate.rb, line 206
def parse_request_line line
  @type.set :request
  @request_method, url, @http_version[0], @http_version[1] = line.scan(REQUEST_LINE).flatten
  @http_version.map! {|v| v.to_i}
  @uri = URI.parse url

  @callback.on_request_line self if @callback.respond_to?(:on_request_line)
end
parse_status_line(line) click to toggle source
# File lib/segregate.rb, line 215
def parse_status_line line
  @type.set :response
  @http_version[0], @http_version[1], code, @status_phrase = line.scan(STATUS_LINE).flatten
  @http_version.map! {|v| v.to_i}
  @status_code = code.to_i

  @callback.on_status_line self if @callback.respond_to?(:on_status_line)
end
read_in_body(line) click to toggle source
# File lib/segregate.rb, line 241
def read_in_body line
  if headers['content-length']
    parse_body line
  elsif headers['transfer-encoding'] == 'chunked'
    parse_chunked_data line
  end
end
read_in_first_line(line) click to toggle source
# File lib/segregate.rb, line 192
def read_in_first_line line
  @callback.on_message_begin self if @callback.respond_to?(:on_message_begin)

  if line =~ REQUEST_LINE
    parse_request_line line
  elsif line =~ STATUS_LINE
    parse_status_line line
  else
    raise "ERROR: Unknown first line: %s" % line
  end

  @state.next
end
read_in_headers(line) click to toggle source
# File lib/segregate.rb, line 224
def read_in_headers line
  if line.empty?
    @state.next
  else
    key, value = line.split(": ",2)
    @header_order << key unless @header_order.include?(key)
    save_value(@headers, key, value)
  end

  if headers_complete?
    @callback.on_headers_complete self if @callback.respond_to?(:on_headers_complete)
    if headers['transfer-encoding'].nil? && (headers['content-length'] == "0" || headers['content-length'].nil?)
      @state.set :done
    end
  end
end
save_value(store_hash, key, value) click to toggle source
# File lib/segregate.rb, line 311
def save_value(store_hash, key, value)
  key = key.downcase
  if store_hash.key?(key)
    store_hash[key] = [] << store_hash[key] unless store_hash[key].kind_of?(Array)
    store_hash[key] << value
  else
    store_hash[key] = value
  end
end