segregate 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e0d78f2e3626e434f62af51a9bd25dc6f2f7608
4
- data.tar.gz: cb81f3d82418ea3d07cebf692418a901ca305969
3
+ metadata.gz: 13ff1b7cc5c95ae45abf27d4edaa5fd80365fa19
4
+ data.tar.gz: 59dd6532dd4a5be947c24c7ac0bb9378950f9b88
5
5
  SHA512:
6
- metadata.gz: 2586ce6b36f3f33d41923bea19d07b7227b1de6c5fa00e9283e7b5514285ad896457ff36bd57a8d81c590fce74cea1fd0f458e02dcaf0638abf0e47d1661df6e
7
- data.tar.gz: dc606c644b5d5221c870412a5afee531a1882782d56298a273fb1c8aa5f48124359a818e5a35f5b5707dcf6a97a478d887d5825e31f80c03081411d431108b35
6
+ metadata.gz: c0efb2a197c27af4005ee120feac38dfcec61d41628f5ffd05c9726ec4aade1fa5b9efcb7cf8690246b2a1462966b79e44616b908de2368242fbb12b459a817b
7
+ data.tar.gz: e470e38d363523ea15a042119b85d4835d98d784c15f8a19423c10f146562117f9ba8a051f10667d7345f48d9f189d636e389eeb92e4a66d06df165d0b8caddd
@@ -0,0 +1,64 @@
1
+ class Segregate
2
+ GENERAL_HEADERS = [
3
+ "cache-control",
4
+ "connection",
5
+ "date",
6
+ "pragma",
7
+ "trailer",
8
+ "transfer-encoding",
9
+ "upgrade",
10
+ "via",
11
+ "warning"
12
+ ]
13
+
14
+ REQUEST_HEADERS = [
15
+ "accept",
16
+ "accept-charset",
17
+ "accept-encoding",
18
+ "accept-language",
19
+ "authorization",
20
+ "expect",
21
+ "from",
22
+ "host",
23
+ "if-match",
24
+ "if-modified-Since",
25
+ "if-none-Match",
26
+ "if-range",
27
+ "if-unmodified-Since",
28
+ "max-forwards",
29
+ "proxy-authorization",
30
+ "range",
31
+ "referer",
32
+ "te",
33
+ "user-agent"
34
+ ]
35
+
36
+ RESPONSE_HEADERS = [
37
+ "accept-ranges",
38
+ "age",
39
+ "etag",
40
+ "location",
41
+ "proxy-authenticate",
42
+ "retry-after",
43
+ "server",
44
+ "vary",
45
+ "www-authenticate"
46
+ ]
47
+
48
+ ENTITY_HEADERS = [
49
+ "allow",
50
+ "content-encoding",
51
+ "content-language",
52
+ "content-length",
53
+ "content-location",
54
+ "content-md5",
55
+ "content-range",
56
+ "content-type",
57
+ "expires",
58
+ "last-modified"
59
+ ]
60
+
61
+ ALL_HEADERS = GENERAL_HEADERS + REQUEST_HEADERS + RESPONSE_HEADERS + ENTITY_HEADERS
62
+
63
+
64
+ end
@@ -1,4 +1,4 @@
1
1
  class Segregate
2
- VERSION = "0.4.0".freeze
2
+ VERSION = "0.5.0".freeze
3
3
  DATE = "2014-02-04".freeze
4
4
  end
data/lib/segregate.rb CHANGED
@@ -1,11 +1,14 @@
1
- require 'uri'
1
+ require 'juncture'
2
2
  require 'hashie'
3
+ require 'uri'
4
+ require 'segregate/version'
3
5
  require 'segregate/http_methods'
6
+ require 'segregate/http_headers'
4
7
  require 'segregate/http_regular_expressions'
5
8
 
6
9
  class Segregate
7
- attr_reader :uri
8
- attr_accessor :request_method, :status_code, :status_phrase, :http_version, :headers, :body
10
+ attr_reader :uri, :type, :state, :http_version, :headers
11
+ attr_accessor :request_method, :status_code, :status_phrase, :body
9
12
 
10
13
  def method_missing meth, *args, &block
11
14
  if @uri.respond_to? meth
@@ -23,32 +26,31 @@ class Segregate
23
26
  @callback = callback
24
27
  @http_version = [nil, nil]
25
28
 
29
+ # :request, :response
30
+ @type = Juncture.new :request, :response
31
+ @state = Juncture.new :waiting, :headers, :body, :done, default: :waiting
32
+
26
33
  @headers = Hashie::Mash.new
27
34
  @body = ""
28
35
 
29
- @request = false
30
- @response = false
31
-
32
- @first_line_complete = false
33
- @headers_complete = false
34
- @body_complete = false
35
- @header_orders = []
36
+ @stashed_data = ""
37
+ @stashed_body = ""
36
38
  end
37
39
 
38
40
  def request?
39
- @request
41
+ @type == :request
40
42
  end
41
43
 
42
44
  def response?
43
- @response
45
+ @type == :response
44
46
  end
45
47
 
46
48
  def headers_complete?
47
- @headers_complete
49
+ @state > :headers
48
50
  end
49
51
 
50
- def body_complete?
51
- @body_complete
52
+ def done?
53
+ @state >= :done
52
54
  end
53
55
 
54
56
  def request_line
@@ -67,24 +69,22 @@ class Segregate
67
69
  http_version[0]
68
70
  end
69
71
 
70
- def major_http_version= val
71
- http_version[0] = val
72
+ def major_http_version= value
73
+ http_version[0] = value
72
74
  end
73
75
 
74
76
  def minor_http_version
75
77
  http_version[1]
76
78
  end
77
79
 
78
- def minor_http_version= val
79
- http_version[1] = val
80
+ def minor_http_version= value
81
+ http_version[1] = value
80
82
  end
81
83
 
82
84
  def update_content_length
83
- if @body_complete
85
+ unless @body.empty?
84
86
  @headers['content-length'] = @body.size.to_s
85
- @header_orders.push 'content-length' unless @header_orders.include? 'content-length'
86
87
  @headers.delete 'transfer-encoding'
87
- @header_orders.delete 'transfer-encoding'
88
88
  end
89
89
  end
90
90
 
@@ -93,41 +93,46 @@ class Segregate
93
93
  update_content_length
94
94
 
95
95
  request? ? raw_message << request_line + "\r\n" : raw_message << status_line + "\r\n"
96
- @header_orders.each do |header|
97
- raw_message << "%s: %s\r\n" % [header, headers[header]]
96
+ ALL_HEADERS.each do |header|
97
+ raw_message << "%s: %s\r\n" % [header, headers[header]] if headers[header]
98
98
  end
99
99
 
100
- raw_message << "\r\n" + @body + "\r\n" if @body_complete
100
+ raw_message << "\r\n" + @body + "\r\n" unless @body.empty?
101
101
  raw_message << "\r\n"
102
102
  end
103
103
 
104
- def parse data
105
- data = StringIO.new data
104
+ def parse_data data
105
+ data = StringIO.new(@stashed_data + data)
106
+ @stashed_data = ""
106
107
 
107
- read_first_line data unless @first_line_complete
108
- read_headers data unless @headers_complete
109
- read_body data unless data.eof?
110
-
111
- @callback.on_message_complete self if @callback.respond_to?(:on_message_complete) && message_complete?
108
+ while !data.eof? && @state < :done
109
+ line, complete_line = get_next_line data
110
+ complete_line ? parse_line(line) : @stashed_data = line
111
+ end
112
112
  end
113
113
 
114
- private
114
+ def parse_line line
115
+ case @state.state
116
+ when :waiting
117
+ read_in_first_line line
118
+ when :headers
119
+ read_in_headers line
120
+ when :body
121
+ read_in_body line
122
+ end
115
123
 
116
- def message_complete?
117
- @body_complete || (@headers_complete && @headers['content-length'].nil? && @headers['transfer-encoding'].nil?)
124
+ @callback.on_message_complete self if @callback.respond_to?(:on_message_complete) && done?
118
125
  end
119
126
 
120
- def read data, size = nil
121
- if size
122
- data.read(size + 2).chomp("\r\n")
123
- else
124
- data.readline.chomp("\r\n")
125
- end
127
+ private
128
+
129
+ def get_next_line data
130
+ line = data.readline("\r\n")
131
+ [line.chomp("\r\n"), line.end_with?("\r\n") || line.length == headers['content-length'].to_i && @state >= :body]
126
132
  end
127
133
 
128
- def read_first_line data
134
+ def read_in_first_line line
129
135
  @callback.on_message_begin self if @callback.respond_to?(:on_message_begin)
130
- line = read data
131
136
 
132
137
  if line =~ REQUEST_LINE
133
138
  parse_request_line line
@@ -139,11 +144,11 @@ class Segregate
139
144
  raise "ERROR: Unknown first line: %s" % line
140
145
  end
141
146
 
142
- @first_line_complete = true
147
+ @state.next
143
148
  end
144
149
 
145
150
  def parse_request_line line
146
- @request = true
151
+ @type.set :request
147
152
  @request_method, url, @http_version[0], @http_version[1] = line.scan(REQUEST_LINE).flatten
148
153
  @http_version.map! {|v| v.to_i}
149
154
  @uri = URI.parse url
@@ -152,7 +157,7 @@ class Segregate
152
157
  end
153
158
 
154
159
  def parse_status_line line
155
- @response = true
160
+ @type.set :response
156
161
  @http_version[0], @http_version[1], code, @status_phrase = line.scan(STATUS_LINE).flatten
157
162
  @http_version.map! {|v| v.to_i}
158
163
  @status_code = code.to_i
@@ -160,45 +165,73 @@ class Segregate
160
165
  @callback.on_status_line self if @callback.respond_to?(:on_status_line)
161
166
  end
162
167
 
163
- def read_headers data
164
- while !data.eof? && !@headers_complete
165
- line = read data
166
- if line.empty?
167
- @headers_complete = true
168
- else
169
- key, value = line.split(":")
170
- @headers[key.downcase] = value.strip
171
- @header_orders << key.downcase
172
- end
168
+ def read_in_headers line
169
+ if line.empty?
170
+ @state.next
171
+ else
172
+ key, value = line.split(":")
173
+ @headers[key.downcase] = value.strip
173
174
  end
174
175
 
175
- @callback.on_headers_complete self if @callback.respond_to?(:on_headers_complete) && @headers_complete
176
+ if headers_complete?
177
+ @callback.on_headers_complete self if @callback.respond_to?(:on_headers_complete)
178
+ unless headers['content-length'] || headers['transfer-encoding']
179
+ @state.set :done
180
+ end
181
+ end
176
182
  end
177
183
 
178
- def read_body data
179
- if headers.key? 'content-length'
180
- parse_body data
184
+ def read_in_body line
185
+ if headers['content-length']
186
+ parse_body line
181
187
  elsif headers['transfer-encoding'] == 'chunked'
182
- parse_chunked_data data
188
+ parse_chunked_data line
183
189
  end
184
190
  end
185
191
 
186
- def parse_body data
187
- @body = read data, headers['content-length'].to_i
188
- @callback.on_body @body if @callback.respond_to?(:on_body)
189
- @body_complete = true
192
+ def parse_body line
193
+ line = @stashed_body + line
194
+ @stashed_body = ""
195
+
196
+ if line.length == headers['content-length'].to_i
197
+ @body = line
198
+ @callback.on_body @body if @callback.respond_to?(:on_body)
199
+ @state.next
200
+ else
201
+ @stashed_body = line
202
+ end
190
203
  end
191
204
 
192
- def parse_chunked_data data
193
- while !data.eof? && !@body_complete
194
- chunk_size = read(data).to_i(16)
195
- if chunk_size == 0
196
- @body_complete = true
197
- else
198
- chunk = read(data, chunk_size)
199
- @body << chunk
200
- @callback.on_body chunk if @callback.respond_to?(:on_body)
201
- end
205
+ def parse_chunked_data line
206
+ @chunked_body_state ||= Juncture.new :size, :chunk, default: :size
207
+
208
+ case @chunked_body_state.state
209
+ when :size
210
+ parse_chunked_data_size line.to_i(16)
211
+ when :chunk
212
+ parse_chunked_data_chunk line
213
+ end
214
+ end
215
+
216
+ def parse_chunked_data_size chunk_size
217
+ if chunk_size == 0
218
+ @state.next
219
+ else
220
+ @chunk_size = chunk_size
221
+ end
222
+ @chunked_body_state.next
223
+ end
224
+
225
+ def parse_chunked_data_chunk line
226
+ line = @stashed_body + line
227
+ @stashed_body = ""
228
+
229
+ if line.length == @chunk_size
230
+ @body << line
231
+ @callback.on_body line if @callback.respond_to?(:on_body)
232
+ @chunked_body_state.next
233
+ else
234
+ @stashed_body = line
202
235
  end
203
236
  end
204
237
  end