Skip to content

Commit

Permalink
SAVEPOINT
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Slaughter committed Nov 27, 2014
1 parent e66de23 commit de00b8e
Show file tree
Hide file tree
Showing 18 changed files with 32,677 additions and 492 deletions.
11 changes: 11 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# See full list of defaults here: https://github.com/bbatsov/rubocop/blob/master/config/default.yml
# To see all cops used see here: https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml

MethodLength:
Max: 20

LineLength:
Max: 100

CyclomaticComplexity:
Max: 20
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
source 'http://rubygems.org'

gem 'coveralls', '~> 0.6.7', require: false
gem 'rubocop', require: false
gemspec
10 changes: 9 additions & 1 deletion HISTORY
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# History
## Released Versions
### Version 0.6.0
#### Minor: parse_data
##### Added

##### Updated

##### Removed

### Version 0.5.0
#### Minor: parse_data
##### Added
Expand Down Expand Up @@ -42,7 +50,7 @@
#### Minor: Object callback
##### Added
* Object callback

### Version 0.1.0
#### Minor: Inital code
##### Added
Expand Down
323 changes: 74 additions & 249 deletions lib/segregate.rb
Original file line number Diff line number Diff line change
@@ -1,272 +1,97 @@
require 'lumberjack'
require 'juncture'
require 'hashie'
require 'uri'
require 'segregate/version'
require 'segregate/http_methods'
require 'segregate/http_headers'
require 'segregate/http_regular_expressions'
require 'pry'

require 'segregate/first_line_parser'
require 'segregate/header_parser'
require 'segregate/body_length_parser'
require 'segregate/body_chunked_parser'

# Segregate
class Segregate
attr_reader :uri, :type, :state, :http_version, :headers
attr_accessor :request_method, :status_code, :status_phrase, :body
extend Forwardable

attr_reader :first_line, :headers, :state

def method_missing meth, *args, &block
if @uri.respond_to? meth
@uri.send meth, *args, &block
def method_missing(meth, *args, &block)
if @first_line.respond_to? meth
@first_line.send meth, *args, &block
elsif @headers.respond_to? meth
@headers.send meth, *args, &block
elsif @body.respond_to? meth
@body.send meth, *args, &block
else
super
end
end

def respond_to?(meth, include_private = false)
@uri.respond_to?(meth, include_private) || super
end

def debug message
if @debug
puts "DEBUG: " + message.to_s
end
end

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

def request?
@type == :request
end

def response?
@type == :response
end

def headers_complete?
@state > :headers
end

def done?
@state >= :done
end

def request_line
request? ? "%s %s HTTP/%d.%d" % [request_method, request_url.to_s, *http_version] : nil
end

def status_line
response? ? "HTTP/%d.%d %d %s" % [*http_version, status_code, status_phrase] : nil
end

def request_url
uri ? uri.to_s : nil
end

def major_http_version
http_version[0]
end

def major_http_version= value
http_version[0] = value
end

def minor_http_version
http_version[1]
end

def minor_http_version= value
http_version[1] = value
end

def update_content_length
unless @body.empty?
if @headers['content-length']
@headers['content-length'] = @body.length.to_s
@first_line.respond_to?(meth, include_private) ||
@headers.respond_to?(meth, include_private) ||
@body.respond_to?(meth, include_private) ||
super
end

def initialize(callback = nil)
@log = Lumberjack::Logger.new('./segregate.log', level: 0)
@first_line = FirstLineParser.new @log
@headers = HeaderParser.new @log
@callback = callback
@body = nil
@state = :first_line
p 'segregate initalised'
end

def body
@body ? @body.body_data : nil
end

def body=(value)
@body.body_data = value
end

def parse_data(data)
data = StringIO.new(data)
@log.debug "---parse_data---\n" + data.string
while !data.eof? && @state != :done
if @first_line.state != :done
@first_line.parse_data(data)
@state = :headers if @first_line.state == :done
elsif @headers.state != :done
@headers.parse_data(data)
if @headers.state == :done
@state = :body
set_body
end
else
@body.parse_data(data)
@state = :done if @body.state == :done
if @callback.respond_to?(:on_message_complete) && @body.state == :done
@callback.on_message_complete self
end
end
end
end

def raw_data
raw_message = ""
update_content_length

build_headers raw_message
build_body raw_message

return raw_message
end

def build_headers raw_message
request? ? raw_message << request_line + "\r\n" : raw_message << status_line + "\r\n"
@header_order.each do |header|
raw_message << "%s: %s\r\n" % [header, headers[header.downcase]]
end
raw_message << "\r\n"
end

def build_body raw_message
if @headers['content-length']
raw_message << @body
elsif @headers['transfer-encoding'] == 'chunked'
raw_message << "%s\r\n" % (@body.size.to_s(16))
raw_message << @body + "\r\n"
raw_message << "0\r\n\r\n"
end
end

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

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?
def to_s
@headers['content-length'] = @body.body_data.length.to_s if @headers.body_type == :length
@first_line.to_s + @headers.to_s + (@body ? @body.to_s : '') + "\r\n"
end

private

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

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
debug "Unknown first line: %s" % line
end

@state.next
end

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

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

def read_in_headers line
if line.empty?
@state.next
else
key, value = line.split(": ",2)
@header_order << key
@headers[key.downcase] = 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

def read_in_body line
if headers['content-length']
parse_body line
elsif headers['transfer-encoding'] == 'chunked'
parse_chunked_data line
end
end

def parse_body line
line = @stashed_body + line
@stashed_body = ""

if line.length >= headers['content-length'].to_i
@body = line
@callback.on_body @body if @callback.respond_to?(:on_body)
@state.next
else
@stashed_body = line
end
end

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

def parse_chunked_data_size chunk_size
if chunk_size == 0
@state.next
else
@chunk_size = chunk_size
end
@chunked_body_state.next
end

def parse_chunked_data_chunk line
line = @stashed_body + line
@stashed_body = ""

if line.length >= @chunk_size
@body << line
@callback.on_body line if @callback.respond_to?(:on_body)
@chunked_body_state.next
def set_body
case @headers.body_type
when :length
@body = BodyLengthParser.new @headers['content-length'], @log
when :chunked
@body = BodyChunkedParser.new @log
else
@stashed_body = @inital_line.end_with?("\r\n") ? (line + "\r\n") : line
@log.error "Unknown body Type: #{@headers.body_type}"
@state = :done
@callback.on_message_complete self if @callback.respond_to?(:on_message_complete)
end
end
end
end

0 comments on commit de00b8e

Please sign in to comment.