-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ben Slaughter
committed
Nov 27, 2014
1 parent
e66de23
commit de00b8e
Showing
18 changed files
with
32,677 additions
and
492 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.