jls-lumberjack 0.0.24 → 0.0.25.beta1
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 +4 -4
- data/lib/lumberjack.rb +19 -0
- data/lib/lumberjack/client.rb +41 -17
- data/lib/lumberjack/server.rb +190 -44
- data/spec/integration_spec.rb +122 -45
- data/spec/lumberjack/acking_protocol_v1_spec.rb +16 -0
- data/spec/lumberjack/acking_protocol_v2_spec.rb +31 -0
- data/spec/lumberjack/client_spec.rb +50 -8
- data/spec/lumberjack/connection_spec.rb +47 -0
- data/spec/lumberjack/server_spec.rb +29 -20
- data/spec/spec_helper.rb +2 -0
- metadata +38 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2b2959f02e7f417cf30eb4d840508950edf0808
|
4
|
+
data.tar.gz: a1f37209be258dbcc97617b8eed7e5c61ca66db5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a87220c6b45a3c53f4bf685c2309c37cdd953c06d489f56cb442eb9ebc482357067b3fcad526b7e44c28455117be4c189226815184d65049bb6b429326ef5d42
|
7
|
+
data.tar.gz: 8d326476bf7fe3d76873fc998dae45fbaf94cbe776de1a9e276fe8d4fadcfe6243a3ac2db7c90568056998c8296dabe44424fc3b03dfb1a7f91f7d87a5df41ae
|
data/lib/lumberjack.rb
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
module Lumberjack
|
2
4
|
SEQUENCE_MAX = (2**32-1).freeze
|
5
|
+
|
6
|
+
@@json = Class.new do
|
7
|
+
def self.load(blob)
|
8
|
+
JSON.parse(blob)
|
9
|
+
end
|
10
|
+
def self.dump(v)
|
11
|
+
v.to_json
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.json
|
16
|
+
@@json
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.json=(j)
|
20
|
+
@@json = j
|
21
|
+
end
|
3
22
|
end
|
data/lib/lumberjack/client.rb
CHANGED
@@ -12,15 +12,16 @@ module Lumberjack
|
|
12
12
|
:port => 0,
|
13
13
|
:addresses => [],
|
14
14
|
:ssl_certificate => nil,
|
15
|
+
:ssl => true,
|
16
|
+
:json => false,
|
15
17
|
}.merge(opts)
|
16
18
|
|
17
19
|
@opts[:addresses] = [@opts[:addresses]] if @opts[:addresses].class == String
|
18
20
|
raise "Must set a port." if @opts[:port] == 0
|
19
21
|
raise "Must set atleast one address" if @opts[:addresses].empty? == 0
|
20
|
-
raise "Must set a ssl certificate or path" if @opts[:ssl_certificate].nil?
|
22
|
+
raise "Must set a ssl certificate or path" if @opts[:ssl_certificate].nil? && @opts[:ssl]
|
21
23
|
|
22
24
|
@socket = connect
|
23
|
-
|
24
25
|
end
|
25
26
|
|
26
27
|
private
|
@@ -37,8 +38,8 @@ module Lumberjack
|
|
37
38
|
end
|
38
39
|
|
39
40
|
public
|
40
|
-
def write(elements)
|
41
|
-
@socket.write_sync(elements)
|
41
|
+
def write(elements, opts={})
|
42
|
+
@socket.write_sync(elements, opts)
|
42
43
|
end
|
43
44
|
|
44
45
|
public
|
@@ -54,7 +55,10 @@ module Lumberjack
|
|
54
55
|
#
|
55
56
|
# * :port - the port to listen on
|
56
57
|
# * :address - the host/address to bind to
|
57
|
-
# * :
|
58
|
+
# * :ssl - enable/disable ssl support
|
59
|
+
# * :ssl_certificate - the path to the ssl cert to use.
|
60
|
+
# If ssl_certificate is not set, a plain tcp connection
|
61
|
+
# will be used.
|
58
62
|
attr_reader :sequence
|
59
63
|
attr_reader :host
|
60
64
|
def initialize(opts={})
|
@@ -64,6 +68,8 @@ module Lumberjack
|
|
64
68
|
:port => 0,
|
65
69
|
:address => "127.0.0.1",
|
66
70
|
:ssl_certificate => nil,
|
71
|
+
:ssl => true,
|
72
|
+
:json => false,
|
67
73
|
}.merge(opts)
|
68
74
|
@host = @opts[:address]
|
69
75
|
|
@@ -73,18 +79,21 @@ module Lumberjack
|
|
73
79
|
private
|
74
80
|
def connection_start(opts)
|
75
81
|
tcp_socket = TCPSocket.new(opts[:address], opts[:port])
|
82
|
+
if !opts[:ssl]
|
83
|
+
@socket = tcp_socket
|
84
|
+
else
|
85
|
+
certificate = OpenSSL::X509::Certificate.new(File.read(opts[:ssl_certificate]))
|
76
86
|
|
77
|
-
|
78
|
-
|
79
|
-
certificate_store = OpenSSL::X509::Store.new
|
80
|
-
certificate_store.add_cert(certificate)
|
87
|
+
certificate_store = OpenSSL::X509::Store.new
|
88
|
+
certificate_store.add_cert(certificate)
|
81
89
|
|
82
|
-
|
83
|
-
|
84
|
-
|
90
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
91
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
92
|
+
ssl_context.cert_store = certificate_store
|
85
93
|
|
86
|
-
|
87
|
-
|
94
|
+
@socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
95
|
+
@socket.connect
|
96
|
+
end
|
88
97
|
end
|
89
98
|
|
90
99
|
private
|
@@ -109,11 +118,16 @@ module Lumberjack
|
|
109
118
|
end
|
110
119
|
|
111
120
|
public
|
112
|
-
def write_sync(elements)
|
121
|
+
def write_sync(elements, opts={})
|
122
|
+
options = {
|
123
|
+
:json => @opts[:json],
|
124
|
+
}.merge(opts)
|
125
|
+
|
113
126
|
elements = [elements] if elements.is_a?(Hash)
|
114
127
|
send_window_size(elements.size)
|
115
128
|
|
116
|
-
|
129
|
+
encoder = options[:json] ? JsonEncoder : FrameEncoder
|
130
|
+
payload = elements.map { |element| encoder.to_frame(element, inc) }.join
|
117
131
|
compress = compress_payload(payload)
|
118
132
|
send_payload(compress)
|
119
133
|
|
@@ -151,7 +165,17 @@ module Lumberjack
|
|
151
165
|
end
|
152
166
|
end
|
153
167
|
|
154
|
-
module
|
168
|
+
module JsonEncoder
|
169
|
+
def self.to_frame(hash, sequence)
|
170
|
+
json = Lumberjack::json.dump(hash)
|
171
|
+
json_length = json.bytesize
|
172
|
+
pack = "AANNA#{json_length}"
|
173
|
+
frame = ["1", "J", sequence, json_length, json]
|
174
|
+
frame.pack(pack)
|
175
|
+
end
|
176
|
+
end # JsonEncoder
|
177
|
+
|
178
|
+
module FrameEncoder
|
155
179
|
def self.to_frame(hash, sequence)
|
156
180
|
frame = ["1", "D", sequence]
|
157
181
|
pack = "AAN"
|
data/lib/lumberjack/server.rb
CHANGED
@@ -4,9 +4,13 @@ require "socket"
|
|
4
4
|
require "thread"
|
5
5
|
require "openssl"
|
6
6
|
require "zlib"
|
7
|
+
require "json"
|
8
|
+
require "concurrent"
|
7
9
|
|
8
10
|
module Lumberjack
|
9
11
|
class Server
|
12
|
+
SOCKET_TIMEOUT = 1 # seconds
|
13
|
+
|
10
14
|
attr_reader :port
|
11
15
|
|
12
16
|
# Create a new Lumberjack server.
|
@@ -22,55 +26,113 @@ module Lumberjack
|
|
22
26
|
@options = {
|
23
27
|
:port => 0,
|
24
28
|
:address => "0.0.0.0",
|
29
|
+
:ssl => true,
|
25
30
|
:ssl_certificate => nil,
|
26
31
|
:ssl_key => nil,
|
27
32
|
:ssl_key_passphrase => nil
|
28
33
|
}.merge(options)
|
29
34
|
|
30
|
-
[:
|
31
|
-
|
32
|
-
|
35
|
+
if @options[:ssl]
|
36
|
+
[:ssl_certificate, :ssl_key].each do |k|
|
37
|
+
if @options[k].nil?
|
38
|
+
raise "You must specify #{k} in Lumberjack::Server.new(...)"
|
39
|
+
end
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
36
|
-
@
|
43
|
+
@server = TCPServer.new(@options[:address], @options[:port])
|
44
|
+
|
45
|
+
@close = Concurrent::AtomicBoolean.new
|
37
46
|
|
38
47
|
# Query the port in case the port number is '0'
|
39
48
|
# TCPServer#addr == [ address_family, port, address, address ]
|
40
|
-
@port = @
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
@port = @server.addr[1]
|
50
|
+
|
51
|
+
if @options[:ssl]
|
52
|
+
# load SSL certificate
|
53
|
+
@ssl = OpenSSL::SSL::SSLContext.new
|
54
|
+
@ssl.cert = OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
|
55
|
+
@ssl.key = OpenSSL::PKey::RSA.new(File.read(@options[:ssl_key]),
|
56
|
+
@options[:ssl_key_passphrase])
|
57
|
+
end
|
46
58
|
end # def initialize
|
47
59
|
|
48
60
|
def run(&block)
|
49
|
-
while
|
61
|
+
while !closed?
|
50
62
|
connection = accept
|
63
|
+
|
64
|
+
# Some exception may occur in the accept loop
|
65
|
+
# we will try again in the next iteration
|
66
|
+
# unless the server is closing
|
67
|
+
next unless connection
|
68
|
+
|
51
69
|
Thread.new(connection) do |connection|
|
52
70
|
connection.run(&block)
|
53
71
|
end
|
54
72
|
end
|
55
73
|
end # def run
|
56
74
|
|
75
|
+
def ssl?
|
76
|
+
@ssl
|
77
|
+
end
|
78
|
+
|
57
79
|
def accept(&block)
|
58
80
|
begin
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
81
|
+
socket = @server.accept_nonblock
|
82
|
+
# update the socket with a SSL layer
|
83
|
+
socket = accept_ssl(socket) if ssl?
|
84
|
+
|
85
|
+
if block_given?
|
86
|
+
block.call(socket, self)
|
87
|
+
else
|
88
|
+
return Connection.new(socket, self)
|
89
|
+
end
|
90
|
+
rescue OpenSSL::SSL::SSLError, IOError, EOFError, Errno::EBADF
|
91
|
+
socket.close rescue nil
|
92
|
+
retry unless closed?
|
93
|
+
rescue IO::WaitReadable, Errno::EAGAIN # Resource not ready yet, so lets try again
|
94
|
+
begin
|
95
|
+
IO.select([@server], nil, nil, SOCKET_TIMEOUT)
|
96
|
+
retry unless closed?
|
97
|
+
rescue IOError, Errno::EBADF => e # we currently closing
|
98
|
+
raise e unless closed?
|
99
|
+
end
|
64
100
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
101
|
+
end
|
102
|
+
|
103
|
+
def accept_ssl(tcp_socket)
|
104
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl)
|
105
|
+
ssl_socket.sync_close
|
106
|
+
|
107
|
+
begin
|
108
|
+
ssl_socket.accept_nonblock
|
109
|
+
|
110
|
+
return ssl_socket
|
111
|
+
rescue IO::WaitReadable # handshake
|
112
|
+
IO.select([ssl_socket], nil, nil, SOCKET_TIMEOUT)
|
113
|
+
retry unless closed?
|
114
|
+
rescue IO::WaitWritable # handshake
|
115
|
+
IO.select(nil, [ssl_socket], nil, SOCKET_TIMEOUT)
|
116
|
+
retry unless closed?
|
69
117
|
end
|
70
118
|
end
|
119
|
+
|
120
|
+
def closed?
|
121
|
+
@close.value
|
122
|
+
end
|
123
|
+
|
124
|
+
def close
|
125
|
+
@close.make_true
|
126
|
+
@server.close unless @server.closed?
|
127
|
+
end
|
71
128
|
end # class Server
|
72
129
|
|
73
130
|
class Parser
|
131
|
+
PROTOCOL_VERSION_1 = "1".ord
|
132
|
+
PROTOCOL_VERSION_2 = "2".ord
|
133
|
+
|
134
|
+
SUPPORTED_PROTOCOLS = [PROTOCOL_VERSION_1, PROTOCOL_VERSION_2]
|
135
|
+
|
74
136
|
def initialize
|
75
137
|
@buffer_offset = 0
|
76
138
|
@buffer = ""
|
@@ -139,24 +201,52 @@ module Lumberjack
|
|
139
201
|
|
140
202
|
FRAME_WINDOW = "W".ord
|
141
203
|
FRAME_DATA = "D".ord
|
204
|
+
FRAME_JSON_DATA = "J".ord
|
142
205
|
FRAME_COMPRESSED = "C".ord
|
143
206
|
def header(&block)
|
144
207
|
version, frame_type = get.bytes.to_a[0..1]
|
208
|
+
version ||= PROTOCOL_VERSION_1
|
209
|
+
|
210
|
+
handle_version(version, &block)
|
145
211
|
|
146
212
|
case frame_type
|
147
213
|
when FRAME_WINDOW; transition(:window_size, 4)
|
148
214
|
when FRAME_DATA; transition(:data_lead, 8)
|
215
|
+
when FRAME_JSON_DATA; transition(:json_data_lead, 8)
|
149
216
|
when FRAME_COMPRESSED; transition(:compressed_lead, 4)
|
150
|
-
else; raise "Unknown frame type:
|
217
|
+
else; raise "Unknown frame type: `#{frame_type}`"
|
151
218
|
end
|
152
219
|
end
|
153
220
|
|
221
|
+
def handle_version(version, &block)
|
222
|
+
if supported_protocol?(version)
|
223
|
+
yield :version, version
|
224
|
+
else
|
225
|
+
raise "unsupported protocol #{version}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def supported_protocol?(version)
|
230
|
+
SUPPORTED_PROTOCOLS.include?(version)
|
231
|
+
end
|
232
|
+
|
154
233
|
def window_size(&block)
|
155
234
|
@window_size = get.unpack("N").first
|
156
235
|
transition(:header, 2)
|
157
236
|
yield :window_size, @window_size
|
158
237
|
end # def window_size
|
159
238
|
|
239
|
+
def json_data_lead(&block)
|
240
|
+
@sequence, payload_size = get.unpack("NN")
|
241
|
+
transition(:json_data_payload, payload_size)
|
242
|
+
end
|
243
|
+
|
244
|
+
def json_data_payload(&block)
|
245
|
+
payload = get
|
246
|
+
yield :json, @sequence, Lumberjack::json.load(payload)
|
247
|
+
transition(:header, 2)
|
248
|
+
end
|
249
|
+
|
160
250
|
def data_lead(&block)
|
161
251
|
@sequence, @data_count = get.unpack("NN")
|
162
252
|
@data = {}
|
@@ -210,17 +300,18 @@ module Lumberjack
|
|
210
300
|
class Connection
|
211
301
|
READ_SIZE = 16384
|
212
302
|
|
213
|
-
|
214
|
-
|
303
|
+
attr_accessor :server
|
304
|
+
|
305
|
+
def initialize(fd, server)
|
215
306
|
@parser = Parser.new
|
216
307
|
@fd = fd
|
217
308
|
|
218
|
-
|
219
|
-
@
|
309
|
+
@server = server
|
310
|
+
@ack_handler = nil
|
220
311
|
end
|
221
312
|
|
222
313
|
def run(&block)
|
223
|
-
while
|
314
|
+
while !server.closed?
|
224
315
|
read_socket(&block)
|
225
316
|
end
|
226
317
|
rescue EOFError, OpenSSL::SSL::SSLError, IOError, Errno::ECONNRESET
|
@@ -238,42 +329,97 @@ module Lumberjack
|
|
238
329
|
# X: too many events after errors.
|
239
330
|
@parser.feed(@fd.sysread(READ_SIZE)) do |event, *args|
|
240
331
|
case event
|
332
|
+
when :version
|
333
|
+
version(*args)
|
241
334
|
when :window_size
|
242
|
-
|
243
|
-
window_size(*args)
|
244
|
-
reset_next_ack
|
335
|
+
reset_next_ack(*args)
|
245
336
|
when :data
|
246
|
-
|
337
|
+
sequence, map = args
|
338
|
+
ack_if_needed(sequence) { data(map, &block) }
|
339
|
+
when :json
|
340
|
+
# If the payload is an array of items we will emit multiple events
|
341
|
+
# this behavior was moved from the plugin to the library.
|
342
|
+
# see this commit: https://github.com/logstash-plugins/logstash-input-lumberjack/pull/57/files#diff-1b9590423b15f04f215635164e7376ecR158
|
343
|
+
sequence, map = args
|
344
|
+
|
345
|
+
ack_if_needed(sequence) do
|
346
|
+
if map.is_a?(Array)
|
347
|
+
map.each { |e| data(e, &block) }
|
348
|
+
else
|
349
|
+
data(map, &block)
|
350
|
+
end
|
351
|
+
end
|
247
352
|
end
|
248
353
|
end
|
249
354
|
end
|
250
355
|
|
251
|
-
def
|
252
|
-
@
|
356
|
+
def version(version)
|
357
|
+
@version = version
|
253
358
|
end
|
254
359
|
|
255
|
-
def
|
256
|
-
|
360
|
+
def ack_if_needed(sequence, &block)
|
361
|
+
block.call
|
362
|
+
send_ack(sequence) if @ack_handler.ack?(sequence)
|
257
363
|
end
|
258
364
|
|
259
|
-
def
|
260
|
-
@
|
365
|
+
def close
|
366
|
+
@fd.close unless @fd.closed?
|
261
367
|
end
|
262
368
|
|
263
|
-
def data(
|
369
|
+
def data(map, &block)
|
264
370
|
block.call(map) if block_given?
|
265
|
-
ack_if_needed(sequence)
|
266
371
|
end
|
267
|
-
|
268
|
-
def
|
269
|
-
|
372
|
+
|
373
|
+
def reset_next_ack(window_size)
|
374
|
+
klass = (@version == Parser::PROTOCOL_VERSION_1) ? AckingProtocolV1 : AckingProtocolV2
|
375
|
+
@ack_handler = klass.new(window_size)
|
376
|
+
end
|
377
|
+
|
378
|
+
def send_ack(sequence)
|
379
|
+
@fd.syswrite(["1A", sequence].pack("A*N"))
|
380
|
+
end
|
381
|
+
end # class Connection
|
382
|
+
|
383
|
+
class AckingProtocolV1
|
384
|
+
def initialize(window_size)
|
385
|
+
@next_ack = nil
|
386
|
+
@window_size = window_size
|
270
387
|
end
|
271
388
|
|
272
|
-
def
|
389
|
+
def ack?(sequence)
|
273
390
|
# The first encoded event will contain the sequence number
|
274
391
|
# this is needed to know when we should ack.
|
275
392
|
@next_ack = compute_next_ack(sequence) if @next_ack.nil?
|
276
|
-
|
393
|
+
sequence == @next_ack
|
277
394
|
end
|
278
|
-
|
395
|
+
|
396
|
+
private
|
397
|
+
def compute_next_ack(sequence)
|
398
|
+
(sequence + @window_size - 1) % SEQUENCE_MAX
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Allow lumberjack to send partial ack back to the producer
|
403
|
+
# only V2 client support partial Acks
|
404
|
+
#
|
405
|
+
# Send Ack on every 20% of the data, so with default settings every 200 events
|
406
|
+
# This should reduce the congestion on retransmit.
|
407
|
+
class AckingProtocolV2
|
408
|
+
ACK_RATIO = 5
|
409
|
+
|
410
|
+
def initialize(window_size)
|
411
|
+
@window_size = window_size
|
412
|
+
@every = (window_size / ACK_RATIO).round
|
413
|
+
end
|
414
|
+
|
415
|
+
def ack?(sequence)
|
416
|
+
if @window_size == sequence
|
417
|
+
true
|
418
|
+
elsif sequence % @every == 0
|
419
|
+
true
|
420
|
+
else
|
421
|
+
false
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
279
425
|
end # module Lumberjack
|
data/spec/integration_spec.rb
CHANGED
@@ -8,60 +8,46 @@ require "thread"
|
|
8
8
|
require "spec_helper"
|
9
9
|
|
10
10
|
Thread.abort_on_exception = true
|
11
|
-
|
12
11
|
describe "A client" do
|
13
12
|
let(:certificate) { Flores::PKI.generate }
|
14
13
|
let(:certificate_file_crt) { "certificate.crt" }
|
15
14
|
let(:certificate_file_key) { "certificate.key" }
|
16
15
|
let(:port) { Flores::Random.integer(1024..65335) }
|
16
|
+
let(:tcp_port) { port + 1 }
|
17
17
|
let(:host) { "127.0.0.1" }
|
18
18
|
let(:queue) { [] }
|
19
19
|
|
20
20
|
before do
|
21
21
|
expect(File).to receive(:read).at_least(1).with(certificate_file_crt) { certificate.first.to_s }
|
22
22
|
expect(File).to receive(:read).at_least(1).with(certificate_file_key) { certificate.last.to_s }
|
23
|
-
|
24
|
-
server = Lumberjack::Server.new(:port => port,
|
25
|
-
:address => host,
|
26
|
-
:ssl_certificate => certificate_file_crt,
|
27
|
-
:ssl_key => certificate_file_key)
|
28
|
-
|
29
|
-
@server = Thread.new do
|
30
|
-
server.run { |data| queue << data }
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
context "with a valid certificate" do
|
35
|
-
it "successfully connect to the server" do
|
36
|
-
expect {
|
37
|
-
Lumberjack::Client.new(:port => port,
|
38
|
-
:host => host,
|
39
|
-
:addresses => host,
|
40
|
-
:ssl_certificate => certificate_file_crt)
|
41
23
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
24
|
+
tcp_server = Lumberjack::Server.new(:port => tcp_port, :address => host, :ssl => false)
|
25
|
+
|
26
|
+
ssl_server = Lumberjack::Server.new(:port => port,
|
27
|
+
:address => host,
|
28
|
+
:ssl_certificate => certificate_file_crt,
|
29
|
+
:ssl_key => certificate_file_key)
|
30
|
+
|
31
|
+
@tcp_server = Thread.new do
|
32
|
+
while true
|
33
|
+
tcp_server.accept do |socket|
|
34
|
+
con = Lumberjack::Connection.new(socket, tcp_server)
|
35
|
+
begin
|
36
|
+
con.run { |data| queue << data }
|
37
|
+
rescue
|
38
|
+
# Close connection on failure. For example SSL client will make
|
39
|
+
# parser for TCP based server trip.
|
40
|
+
# Connection is closed by Server connection object
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
52
44
|
end
|
53
45
|
|
54
|
-
|
55
|
-
|
56
|
-
Lumberjack::Client.new(:port => port,
|
57
|
-
:host => host,
|
58
|
-
:addresses => host,
|
59
|
-
:ssl_certificate => invalid_certificate_file)
|
60
|
-
|
61
|
-
}.to raise_error(OpenSSL::SSL::SSLError, /certificate verify failed/)
|
46
|
+
@ssl_server = Thread.new do
|
47
|
+
ssl_server.run { |data| queue << data }
|
62
48
|
end
|
63
49
|
end
|
64
|
-
|
50
|
+
|
65
51
|
shared_examples "send payload" do
|
66
52
|
it "supports single element" do
|
67
53
|
(1..random_number_of_events).each do |n|
|
@@ -80,15 +66,9 @@ describe "A client" do
|
|
80
66
|
end
|
81
67
|
end
|
82
68
|
|
83
|
-
|
69
|
+
shared_examples "transmit payloads" do
|
84
70
|
let(:random_number_of_events) { Flores::Random.integer(2..10) }
|
85
71
|
let(:payload) { { "line" => "foobar" } }
|
86
|
-
let(:client) do
|
87
|
-
Lumberjack::Client.new(:port => port,
|
88
|
-
:host => host,
|
89
|
-
:addresses => host,
|
90
|
-
:ssl_certificate => certificate_file_crt)
|
91
|
-
end
|
92
72
|
let(:batch_size) { Flores::Random.integer(1..1024) }
|
93
73
|
let(:batch_payload) do
|
94
74
|
batch = []
|
@@ -130,4 +110,101 @@ describe "A client" do
|
|
130
110
|
end
|
131
111
|
end
|
132
112
|
end
|
113
|
+
|
114
|
+
context "using plain tcp connection" do
|
115
|
+
it "should successfully connect to tcp server if ssl explicitely disabled" do
|
116
|
+
expect {
|
117
|
+
Lumberjack::Client.new(:port => tcp_port, :host => host, :addresses => host, :ssl => false)
|
118
|
+
}.not_to raise_error
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should fail to connect to tcp server if ssl not explicitely disabled" do
|
122
|
+
expect {
|
123
|
+
Lumberjack::Client.new(:port => tcp_port, :host => host, :addresses => host)
|
124
|
+
}.to raise_error(RuntimeError, /Must set a ssl certificate/)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should fail to communicate to ssl based server" do
|
128
|
+
expect {
|
129
|
+
client = Lumberjack::Client.new(:port => port,
|
130
|
+
:host => host,
|
131
|
+
:addresses => host,
|
132
|
+
:ssl => false)
|
133
|
+
client.write({ "line" => "foobar" })
|
134
|
+
}.to raise_error(RuntimeError)
|
135
|
+
end
|
136
|
+
|
137
|
+
context "When transmitting a payload" do
|
138
|
+
let(:options) { {:port => tcp_port, :host => host, :addresses => host, :ssl => false } }
|
139
|
+
let(:client) { Lumberjack::Client.new(options) }
|
140
|
+
|
141
|
+
context "json" do
|
142
|
+
let(:options) { super.merge({ :json => true }) }
|
143
|
+
include_examples "transmit payloads"
|
144
|
+
end
|
145
|
+
|
146
|
+
context "v1 frame" do
|
147
|
+
include_examples "transmit payloads"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "using ssl encrypted connection" do
|
153
|
+
context "with a valid certificate" do
|
154
|
+
it "successfully connect to the server" do
|
155
|
+
expect {
|
156
|
+
Lumberjack::Client.new(:port => port,
|
157
|
+
:host => host,
|
158
|
+
:addresses => host,
|
159
|
+
:ssl_certificate => certificate_file_crt)
|
160
|
+
}.not_to raise_error
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should fail connecting to plain tcp server" do
|
164
|
+
expect {
|
165
|
+
Lumberjack::Client.new(:port => tcp_port,
|
166
|
+
:host => host,
|
167
|
+
:addresses => host,
|
168
|
+
:ssl_certificate => certificate_file_crt)
|
169
|
+
}.to raise_error(OpenSSL::SSL::SSLError)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "with an invalid certificate" do
|
174
|
+
let(:invalid_certificate) { Flores::PKI.generate }
|
175
|
+
let(:invalid_certificate_file) { "invalid.crt" }
|
176
|
+
|
177
|
+
before do
|
178
|
+
expect(File).to receive(:read).with(invalid_certificate_file) { invalid_certificate.first.to_s }
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should refuse to connect" do
|
182
|
+
expect {
|
183
|
+
Lumberjack::Client.new(:port => port,
|
184
|
+
:host => host,
|
185
|
+
:addresses => host,
|
186
|
+
:ssl_certificate => invalid_certificate_file)
|
187
|
+
|
188
|
+
}.to raise_error(OpenSSL::SSL::SSLError, /certificate verify failed/)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "When transmitting a payload" do
|
193
|
+
let(:client) do
|
194
|
+
Lumberjack::Client.new(:port => port,
|
195
|
+
:host => host,
|
196
|
+
:addresses => host,
|
197
|
+
:ssl_certificate => certificate_file_crt)
|
198
|
+
end
|
199
|
+
|
200
|
+
context "json" do
|
201
|
+
let(:options) { super.merge({ :json => true }) }
|
202
|
+
include_examples "transmit payloads"
|
203
|
+
end
|
204
|
+
|
205
|
+
context "v1 frame" do
|
206
|
+
include_examples "transmit payloads"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
133
210
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "lumberjack/server"
|
3
|
+
require "flores/random"
|
4
|
+
|
5
|
+
describe Lumberjack::AckingProtocolV1 do
|
6
|
+
let(:number_of_events) { Flores::Random.integer(100..1024) }
|
7
|
+
|
8
|
+
subject { Lumberjack::AckingProtocolV1.new(number_of_events) }
|
9
|
+
|
10
|
+
it "should return true only once" do
|
11
|
+
results = []
|
12
|
+
number_of_events.times { |n| results << subject.ack?(n) }
|
13
|
+
expect(results.size).to eq(number_of_events)
|
14
|
+
expect(results.count(true)).to eq(1)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "lumberjack/server"
|
3
|
+
require "flores/random"
|
4
|
+
|
5
|
+
describe Lumberjack::AckingProtocolV2 do
|
6
|
+
let(:results) { [] }
|
7
|
+
subject { Lumberjack::AckingProtocolV2.new(number_of_events) }
|
8
|
+
before { 1.upto(number_of_events) { |n| results << subject.ack?(n) } }
|
9
|
+
|
10
|
+
context "with multiples events" do
|
11
|
+
let(:number_of_events) { Flores::Random.integer(100..1024) }
|
12
|
+
|
13
|
+
it "should return multiples partial acks" do
|
14
|
+
expect(results.size).to eq(number_of_events)
|
15
|
+
expect(results.count(true)).to eq((number_of_events / number_of_events * Lumberjack::AckingProtocolV2::ACK_RATIO).ceil + 1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "last ack should be true" do
|
19
|
+
expect(results.last).to be_truthy
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with only one event" do
|
24
|
+
let(:number_of_events) { 1 }
|
25
|
+
|
26
|
+
it "should return true only once" do
|
27
|
+
expect(results.size).to eq(number_of_events)
|
28
|
+
expect(results.count(true)).to eq(1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -8,9 +8,7 @@ require "openssl"
|
|
8
8
|
require "zlib"
|
9
9
|
|
10
10
|
describe "Lumberjack::Client" do
|
11
|
-
|
12
11
|
describe "Lumberjack::Socket" do
|
13
|
-
|
14
12
|
let(:port) { 5000 }
|
15
13
|
|
16
14
|
subject(:socket) { Lumberjack::Socket.new(:port => port, :ssl_certificate => "" ) }
|
@@ -38,16 +36,60 @@ describe "Lumberjack::Client" do
|
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
describe Lumberjack::
|
39
|
+
describe Lumberjack::FrameEncoder do
|
40
|
+
it 'should creates frames without truncating accentued characters' do
|
41
|
+
content = {
|
42
|
+
"message" => "Le Canadien de Montréal est la meilleure équipe au monde!",
|
43
|
+
"other" => "éléphant"
|
44
|
+
}
|
45
|
+
parser = Lumberjack::Parser.new
|
46
|
+
parser.feed(Lumberjack::FrameEncoder.to_frame(content, 0)) do |code, sequence, data|
|
47
|
+
if code == :data
|
48
|
+
expect(data["message"].force_encoding('UTF-8')).to eq(content["message"])
|
49
|
+
expect(data["other"].force_encoding('UTF-8')).to eq(content["other"])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should creates frames without dropping multibytes characters' do
|
55
|
+
content = {
|
56
|
+
"message" => "国際ホッケー連盟" # International Hockey Federation
|
57
|
+
}
|
58
|
+
parser = Lumberjack::Parser.new
|
59
|
+
parser.feed(Lumberjack::FrameEncoder.to_frame(content, 0)) do |code, sequence, data|
|
60
|
+
expect(data["message"].force_encoding('UTF-8')).to eq(content["message"]) if code == :data
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Lumberjack::JsonEncoder do
|
66
|
+
it 'should create frames from nested hash' do
|
67
|
+
content = {
|
68
|
+
"number" => 1,
|
69
|
+
"string" => "hello world",
|
70
|
+
"array" => [1,2,3],
|
71
|
+
"sub" => {
|
72
|
+
"a" => 1
|
73
|
+
}
|
74
|
+
}
|
75
|
+
parser = Lumberjack::Parser.new
|
76
|
+
frame = Lumberjack::JsonEncoder.to_frame(content, 0)
|
77
|
+
parser.feed(frame) do |code, sequence, data|
|
78
|
+
expect(data).to eq(content) if code == :json
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
42
82
|
it 'should creates frames without truncating accentued characters' do
|
43
83
|
content = {
|
44
84
|
"message" => "Le Canadien de Montréal est la meilleure équipe au monde!",
|
45
85
|
"other" => "éléphant"
|
46
86
|
}
|
47
87
|
parser = Lumberjack::Parser.new
|
48
|
-
parser.feed(Lumberjack::
|
49
|
-
|
50
|
-
|
88
|
+
parser.feed(Lumberjack::JsonEncoder.to_frame(content, 0)) do |code, sequence, data|
|
89
|
+
if code == :json
|
90
|
+
expect(data["message"]).to eq(content["message"])
|
91
|
+
expect(data["other"]).to eq(content["other"])
|
92
|
+
end
|
51
93
|
end
|
52
94
|
end
|
53
95
|
|
@@ -56,8 +98,8 @@ describe "Lumberjack::Client" do
|
|
56
98
|
"message" => "国際ホッケー連盟" # International Hockey Federation
|
57
99
|
}
|
58
100
|
parser = Lumberjack::Parser.new
|
59
|
-
parser.feed(Lumberjack::
|
60
|
-
expect(data["message"]
|
101
|
+
parser.feed(Lumberjack::JsonEncoder.to_frame(content, 0)) do |code, sequence, data|
|
102
|
+
expect(data["message"]).to eq(content["message"]) if code == :json
|
61
103
|
end
|
62
104
|
end
|
63
105
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "lumberjack/server"
|
3
|
+
require "spec_helper"
|
4
|
+
require "flores/random"
|
5
|
+
|
6
|
+
describe "Connnection" do
|
7
|
+
let(:server) { double("server", :closed? => false) }
|
8
|
+
let(:socket) { double("socket", :closed? => false) }
|
9
|
+
let(:connection) { Lumberjack::Connection.new(socket, server) }
|
10
|
+
let(:payload) { {"line" => "foobar" } }
|
11
|
+
let(:start_sequence) { Flores::Random.integer(0..2000) }
|
12
|
+
let(:random_number_of_events) { Flores::Random.integer(2..200) }
|
13
|
+
|
14
|
+
context "when the server is running" do
|
15
|
+
before do
|
16
|
+
expect(socket).to receive(:sysread).at_least(:once).with(Lumberjack::Connection::READ_SIZE).and_return("")
|
17
|
+
allow(socket).to receive(:syswrite).with(anything).and_return(true)
|
18
|
+
allow(socket).to receive(:close)
|
19
|
+
|
20
|
+
|
21
|
+
expectation = receive(:feed)
|
22
|
+
.with("")
|
23
|
+
.and_yield(:version, Lumberjack::Parser::PROTOCOL_VERSION_1)
|
24
|
+
.and_yield(:window_size, random_number_of_events)
|
25
|
+
|
26
|
+
random_number_of_events.times { |n| expectation.and_yield(:data, start_sequence + n + 1, payload) }
|
27
|
+
|
28
|
+
expect_any_instance_of(Lumberjack::Parser).to expectation
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should ack the end of a sequence" do
|
32
|
+
expect(socket).to receive(:syswrite).with(["1A", random_number_of_events + start_sequence].pack("A*N"))
|
33
|
+
connection.read_socket
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the server stop" do
|
38
|
+
let(:server) { double("server", :closed? => true) }
|
39
|
+
before do
|
40
|
+
expect(socket).to receive(:close).and_return(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "stop reading from the socket" do
|
44
|
+
expect { |b| connection.run(&b) }.not_to yield_control
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,33 +1,42 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require "lumberjack/client"
|
2
3
|
require "lumberjack/server"
|
3
|
-
require "spec_helper"
|
4
4
|
require "flores/random"
|
5
|
+
require "flores/pki"
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
Thread.abort_on_exception = true
|
5
9
|
|
6
10
|
describe "Server" do
|
7
|
-
let(:
|
8
|
-
let(:
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
11
|
+
let(:certificate) { Flores::PKI.generate }
|
12
|
+
let(:certificate_file_crt) { "certificate.crt" }
|
13
|
+
let(:certificate_file_key) { "certificate.key" }
|
14
|
+
let(:port) { Flores::Random.integer(1024..65335) }
|
15
|
+
let(:tcp_port) { port + 1 }
|
16
|
+
let(:host) { "127.0.0.1" }
|
17
|
+
let(:queue) { [] }
|
12
18
|
|
13
19
|
before do
|
14
|
-
expect(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
expectation = receive(:feed)
|
19
|
-
.with("")
|
20
|
-
.and_yield(:window_size, random_number_of_events)
|
21
|
-
|
22
|
-
random_number_of_events.times { |n| expectation.and_yield(:data, start_sequence + n + 1, payload) }
|
20
|
+
expect(File).to receive(:read).at_least(1).with(certificate_file_crt) { certificate.first.to_s }
|
21
|
+
expect(File).to receive(:read).at_least(1).with(certificate_file_key) { certificate.last.to_s }
|
22
|
+
end
|
23
23
|
|
24
|
-
|
24
|
+
subject do
|
25
|
+
Lumberjack::Server.new(:port => port,
|
26
|
+
:address => host,
|
27
|
+
:ssl_certificate => certificate_file_crt,
|
28
|
+
:ssl_key => certificate_file_key)
|
25
29
|
end
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
it "should not block when closing the server" do
|
32
|
+
thread = Thread.new do
|
33
|
+
subject.run do |event|
|
34
|
+
queue << event
|
35
|
+
end
|
31
36
|
end
|
37
|
+
|
38
|
+
sleep(1) while thread.status != "run"
|
39
|
+
subject.close
|
40
|
+
wait_for { thread.status }.to be_falsey
|
32
41
|
end
|
33
42
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jls-lumberjack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.25.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Sissel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - '>='
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
name: concurrent-ruby
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
15
29
|
requirements:
|
@@ -66,6 +80,20 @@ dependencies:
|
|
66
80
|
- - '>='
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
name: rspec-wait
|
90
|
+
prerelease: false
|
91
|
+
type: :development
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
description: lumberjack log transport library
|
70
98
|
email:
|
71
99
|
- jls@semicomplete.com
|
@@ -77,7 +105,10 @@ files:
|
|
77
105
|
- lib/lumberjack/client.rb
|
78
106
|
- lib/lumberjack/server.rb
|
79
107
|
- spec/integration_spec.rb
|
108
|
+
- spec/lumberjack/acking_protocol_v1_spec.rb
|
109
|
+
- spec/lumberjack/acking_protocol_v2_spec.rb
|
80
110
|
- spec/lumberjack/client_spec.rb
|
111
|
+
- spec/lumberjack/connection_spec.rb
|
81
112
|
- spec/lumberjack/server_spec.rb
|
82
113
|
- spec/spec_helper.rb
|
83
114
|
homepage: https://github.com/jordansissel/lumberjack
|
@@ -94,9 +125,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
125
|
version: '0'
|
95
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
127
|
requirements:
|
97
|
-
- - '
|
128
|
+
- - '>'
|
98
129
|
- !ruby/object:Gem::Version
|
99
|
-
version:
|
130
|
+
version: 1.3.1
|
100
131
|
requirements: []
|
101
132
|
rubyforge_project:
|
102
133
|
rubygems_version: 2.4.8
|
@@ -106,5 +137,8 @@ summary: lumberjack log transport library
|
|
106
137
|
test_files:
|
107
138
|
- spec/integration_spec.rb
|
108
139
|
- spec/spec_helper.rb
|
140
|
+
- spec/lumberjack/acking_protocol_v1_spec.rb
|
141
|
+
- spec/lumberjack/acking_protocol_v2_spec.rb
|
109
142
|
- spec/lumberjack/client_spec.rb
|
143
|
+
- spec/lumberjack/connection_spec.rb
|
110
144
|
- spec/lumberjack/server_spec.rb
|