jls-lumberjack 0.0.7 → 0.0.8

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.
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: []