jls-lumberjack 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/lumberjack/server.rb +163 -85
  2. metadata +11 -13
@@ -44,110 +44,188 @@ module Lumberjack
44
44
 
45
45
  def run(&block)
46
46
  while true
47
- begin
48
- Thread.new(@ssl_server.accept) do |fd|
49
- Connection.new(fd).run(&block)
50
- end
51
- rescue => e
52
- p :accept_error => e
47
+ Thread.new(@ssl_server.accept) do |fd|
48
+ Connection.new(fd).run(&block)
53
49
  end
54
50
  end
55
51
  end # def run
56
52
  end # class Server
57
53
 
58
- class Connection
59
- def initialize(fd)
60
- @fd = fd
54
+ class Parser
55
+ def initialize
56
+ @buffer_offset = 0
57
+ @buffer = ""
58
+ @buffer.force_encoding("BINARY")
59
+ transition(:header, 2)
61
60
  end # def initialize
62
61
 
63
- def run(&block)
64
- begin
65
- each_event(&block)
66
- rescue IOError => e
67
- p :io_error => e
62
+ def transition(state, next_length)
63
+ @state = state
64
+ #puts :transition => state
65
+ # TODO(sissel): Assert this self.respond_to?(state)
66
+ # TODO(sissel): Assert state is in STATES
67
+ # TODO(sissel): Assert next_length is a number
68
+ need(next_length)
69
+ end # def transition
70
+
71
+ # Feed data to this parser.
72
+ #
73
+ # Currently, it will return the raw payload of websocket messages.
74
+ # Otherwise, it returns nil if no complete message has yet been consumed.
75
+ #
76
+ # @param [String] the string data to feed into the parser.
77
+ # @return [String, nil] the websocket message payload, if any, nil otherwise.
78
+ def feed(data, &block)
79
+ @buffer << data
80
+ #p :need => @need
81
+ while have?(@need)
82
+ send(@state, &block)
83
+ #case @state
84
+ #when :header; header(&block)
85
+ #when :window_size; window_size(&block)
86
+ #when :data_lead; data_lead(&block)
87
+ #when :data_field_key_len; data_field_key_len(&block)
88
+ #when :data_field_key; data_field_key(&block)
89
+ #when :data_field_value_len; data_field_value_len(&block)
90
+ #when :data_field_value; data_field_value(&block)
91
+ #when :data_field_value; data_field_value(&block)
92
+ #when :compressed_lead; compressed_lead(&block)
93
+ #when :compressed_payload; compressed_payload(&block)
94
+ #end # case @state
68
95
  end
69
- end # def run
96
+ return nil
97
+ end # def <<
98
+
99
+ # Do we have at least 'length' bytes in the buffer?
100
+ def have?(length)
101
+ return length <= (@buffer.size - @buffer_offset)
102
+ end # def have?
103
+
104
+ # Get 'length' string from the buffer.
105
+ def get(length=nil)
106
+ length = @need if length.nil?
107
+ data = @buffer[@buffer_offset ... @buffer_offset + length]
108
+ @buffer_offset += length
109
+ if @buffer_offset > 16384
110
+ @buffer = @buffer[@buffer_offset .. -1]
111
+ @buffer_offset = 0
112
+ end
113
+ return data
114
+ end # def get
115
+
116
+ # Set the minimum number of bytes we need in the buffer for the next read.
117
+ def need(length)
118
+ @need = length
119
+ end # def need
120
+
121
+ FRAME_WINDOW = "W".ord
122
+ FRAME_DATA = "D".ord
123
+ FRAME_COMPRESSED = "C".ord
124
+ def header(&block)
125
+ version, frame_type = get.bytes.to_a[0..1]
126
+
127
+ case frame_type
128
+ when FRAME_WINDOW; transition(:window_size, 4)
129
+ when FRAME_DATA; transition(:data_lead, 8)
130
+ when FRAME_COMPRESSED; transition(:compressed_lead, 4)
131
+ else; raise "Unknown frame type: #{frame_type}"
132
+ end
133
+ end
70
134
 
71
- def each_event(&block)
72
- last_ack = 0
73
- window_size = 0
74
- io = IOWrap.new(@fd)
75
- while true
76
- version = io.read(1)
77
- frame = io.read(1)
78
-
79
- if frame == "W" # window size
80
- window_size = io.read(4).unpack("N").first / 2
81
- #puts "Window size: #{window_size}"
82
- next
83
- elsif frame == "C" # compressed data
84
- length = io.read(4).unpack("N").first
85
- #puts "Compressed frame length #{length}"
86
- compressed = io.read(length)
87
- original = Zlib::Inflate.inflate(compressed)
88
- #original = LZ4::uncompress(compressed, length)
89
- io.pushback(original)
90
- next
91
- elsif frame != "D"
92
- #puts "Unexpected frame type: #{version.inspect} / #{frame.inspect}"
93
- io.close
94
- return
95
- end
96
- #
97
- # data frame
98
- sequence = io.read(4).unpack("N").first
99
- count = io.read(4).unpack("N").first
100
-
101
- map = {}
102
- count.times do
103
- key_len = io.read(4).unpack("N").first
104
- key = io.read(key_len)
105
- value_len = io.read(4).unpack("N").first
106
- value = io.read(value_len)
107
- map[key] = value
108
- end
135
+ def window_size(&block)
136
+ @window_size = get.unpack("N").first
137
+ transition(:header, 2)
138
+ yield :window_size, @window_size
139
+ end # def window_size
109
140
 
110
- block.call(map)
141
+ def data_lead(&block)
142
+ @sequence, @data_count = get.unpack("NN")
143
+ @data = {}
144
+ transition(:data_field_key_len, 4)
145
+ end
111
146
 
112
- if sequence - last_ack >= window_size
113
- # ack this.
114
- io.syswrite(["1", "A", sequence].pack("AAN"))
115
- last_ack = sequence
116
- end
117
- end
118
- end # def each_event
119
- end # class Connection
147
+ def data_field_key_len(&block)
148
+ key_len = get.unpack("N").first
149
+ transition(:data_field_key, key_len)
150
+ end
120
151
 
121
- # Wrap an io-like object but support pushback.
122
- class IOWrap
123
- def initialize(io)
124
- @io = io
125
- @buffer = ""
152
+ def data_field_key(&block)
153
+ @key = get
154
+ transition(:data_field_value_len, 4)
155
+ end
156
+
157
+ def data_field_value_len(&block)
158
+ transition(:data_field_value, get.unpack("N").first)
126
159
  end
127
160
 
128
- def read(bytes)
129
- if @buffer.empty?
130
- #puts "reading direct from @io"
131
- return @io.read(bytes)
132
- elsif @buffer.length > bytes
133
- #puts "reading buffered"
134
- data = @buffer.slice!(0...bytes)
135
- return data
161
+ def data_field_value(&block)
162
+ @value = get
163
+
164
+ @data_count -= 1
165
+ @data[@key] = @value
166
+
167
+ if @data_count > 0
168
+ transition(:data_field_key_len, 4)
136
169
  else
137
- data = @buffer.clone
138
- @buffer.clear
139
- return data + @io.read(bytes - data.length)
170
+ transition(:header, 2)
140
171
  end
172
+
173
+ yield :data, @sequence, @data
174
+ end # def data_field_value
175
+
176
+ def compressed_lead(&block)
177
+ length = get.unpack("N").first
178
+ transition(:compressed_payload, length)
141
179
  end
180
+
181
+ def compressed_payload(&block)
182
+ original = Zlib::Inflate.inflate(get)
183
+ transition(:header, 2)
142
184
 
143
- def pushback(data)
144
- #puts "Pushback: #{data[0..30].inspect}..."
145
- @buffer += data
185
+ # Parse the uncompressed payload.
186
+ feed(original, &block)
146
187
  end
188
+ end # class Parser
147
189
 
148
- def method_missing(method, *args)
149
- @io.send(method, *args)
190
+ class Connection
191
+ def initialize(fd)
192
+ super()
193
+ @parser = Parser.new
194
+ @fd = fd
195
+ @last_ack = 0
196
+
197
+ # a safe default until we are told by the client what window size to use
198
+ @window_size = 1
150
199
  end
151
- end # class IOWrap
152
- end # module Lumberjack
153
200
 
201
+ def run(&block)
202
+ while true
203
+ @parser.feed(@fd.sysread(16384)) do |event, *args|
204
+ case event
205
+ when :window_size; window_size(*args, &block)
206
+ when :data; data(*args, &block)
207
+ end
208
+ #send(event, *args)
209
+ end # feed
210
+ end # while true
211
+ rescue EOFError, OpenSSL::SSL::SSLError
212
+ # EOF or other read errors, only action is to shutdown which we'll do in
213
+ # 'ensure'
214
+ ensure
215
+ @fd.close
216
+ end # def run
217
+
218
+ def window_size(size)
219
+ @window_size = size
220
+ end
221
+
222
+ def data(sequence, map, &block)
223
+ block.call(map)
224
+ if (sequence - @last_ack) >= @window_size
225
+ @fd.syswrite(["1A", sequence].pack("A*N"))
226
+ @last_ack = sequence
227
+ end
228
+ end
229
+ end # class Connection
230
+
231
+ end # module Lumberjack
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jls-lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
5
- prerelease:
4
+ version: 0.0.8
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jordan Sissel
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-04 00:00:00.000000000 Z
12
+ date: 2013-01-26 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: lumberjack log transport library
15
15
  email:
@@ -22,28 +22,26 @@ files:
22
22
  - lib/lumberjack/client.rb
23
23
  homepage: https://github.com/jordansissel/lumberjack
24
24
  licenses: []
25
- post_install_message:
25
+ post_install_message:
26
26
  rdoc_options: []
27
27
  require_paths:
28
28
  - lib
29
29
  required_ruby_version: !ruby/object:Gem::Requirement
30
+ none: false
30
31
  requirements:
31
32
  - - ! '>='
32
33
  - !ruby/object:Gem::Version
33
- version: !binary |-
34
- MA==
35
- none: false
34
+ version: '0'
36
35
  required_rubygems_version: !ruby/object:Gem::Requirement
36
+ none: false
37
37
  requirements:
38
38
  - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
- version: !binary |-
41
- MA==
42
- none: false
40
+ version: '0'
43
41
  requirements: []
44
- rubyforge_project:
42
+ rubyforge_project:
45
43
  rubygems_version: 1.8.24
46
- signing_key:
44
+ signing_key:
47
45
  specification_version: 3
48
46
  summary: lumberjack log transport library
49
47
  test_files: []