logstash-core 1.5.0.beta2-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of logstash-core might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/lib/logstash-event.rb +2 -0
- data/lib/logstash.rb +4 -0
- data/lib/logstash/JRUBY-PR1448.rb +32 -0
- data/lib/logstash/agent.rb +355 -0
- data/lib/logstash/bundler.rb +124 -0
- data/lib/logstash/codecs/base.rb +50 -0
- data/lib/logstash/config/config_ast.rb +508 -0
- data/lib/logstash/config/file.rb +39 -0
- data/lib/logstash/config/grammar.rb +3503 -0
- data/lib/logstash/config/mixin.rb +495 -0
- data/lib/logstash/config/registry.rb +13 -0
- data/lib/logstash/environment.rb +168 -0
- data/lib/logstash/errors.rb +12 -0
- data/lib/logstash/event.rb +310 -0
- data/lib/logstash/filters/base.rb +239 -0
- data/lib/logstash/gemfile.rb +175 -0
- data/lib/logstash/inputs/base.rb +137 -0
- data/lib/logstash/inputs/threadable.rb +18 -0
- data/lib/logstash/java_integration.rb +41 -0
- data/lib/logstash/json.rb +53 -0
- data/lib/logstash/logging.rb +91 -0
- data/lib/logstash/multiqueue.rb +53 -0
- data/lib/logstash/namespace.rb +17 -0
- data/lib/logstash/outputs/base.rb +124 -0
- data/lib/logstash/patches.rb +3 -0
- data/lib/logstash/patches/bugfix_jruby_2558.rb +50 -0
- data/lib/logstash/patches/cabin.rb +34 -0
- data/lib/logstash/patches/profile_require_calls.rb +47 -0
- data/lib/logstash/pipeline.rb +305 -0
- data/lib/logstash/plugin.rb +177 -0
- data/lib/logstash/pluginmanager.rb +17 -0
- data/lib/logstash/pluginmanager/install.rb +112 -0
- data/lib/logstash/pluginmanager/list.rb +38 -0
- data/lib/logstash/pluginmanager/main.rb +22 -0
- data/lib/logstash/pluginmanager/maven_tools_patch.rb +12 -0
- data/lib/logstash/pluginmanager/uninstall.rb +49 -0
- data/lib/logstash/pluginmanager/update.rb +50 -0
- data/lib/logstash/pluginmanager/util.rb +88 -0
- data/lib/logstash/program.rb +15 -0
- data/lib/logstash/runner.rb +167 -0
- data/lib/logstash/sized_queue.rb +8 -0
- data/lib/logstash/threadwatchdog.rb +37 -0
- data/lib/logstash/timestamp.rb +97 -0
- data/lib/logstash/util.rb +152 -0
- data/lib/logstash/util/accessors.rb +88 -0
- data/lib/logstash/util/buftok.rb +139 -0
- data/lib/logstash/util/charset.rb +35 -0
- data/lib/logstash/util/fieldreference.rb +68 -0
- data/lib/logstash/util/filetools.rb +185 -0
- data/lib/logstash/util/password.rb +25 -0
- data/lib/logstash/util/plugin_version.rb +43 -0
- data/lib/logstash/util/prctl.rb +11 -0
- data/lib/logstash/util/require-helper.rb +18 -0
- data/lib/logstash/util/retryable.rb +39 -0
- data/lib/logstash/util/socket_peer.rb +7 -0
- data/lib/logstash/version.rb +6 -0
- data/locales/en.yml +176 -0
- metadata +427 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/namespace"
|
3
|
+
require "logstash/logging"
|
4
|
+
|
5
|
+
class LogStash::ThreadWatchdog
|
6
|
+
attr_accessor :logger
|
7
|
+
attr_accessor :threads
|
8
|
+
|
9
|
+
class TimeoutError < StandardError; end
|
10
|
+
|
11
|
+
public
|
12
|
+
def initialize(threads, watchdog_timeout=10)
|
13
|
+
@threads = threads
|
14
|
+
@watchdog_timeout = watchdog_timeout
|
15
|
+
end # def initialize
|
16
|
+
|
17
|
+
public
|
18
|
+
def watch
|
19
|
+
while sleep(1)
|
20
|
+
cutoff = Time.now - @watchdog_timeout
|
21
|
+
@threads.each do |t|
|
22
|
+
watchdog = t[:watchdog]
|
23
|
+
if watchdog and watchdog <= cutoff
|
24
|
+
age = Time.now - watchdog
|
25
|
+
@logger.fatal("thread watchdog timeout",
|
26
|
+
:thread => t,
|
27
|
+
:backtrace => t.backtrace,
|
28
|
+
:thread_watchdog => watchdog,
|
29
|
+
:age => age,
|
30
|
+
:cutoff => @watchdog_timeout,
|
31
|
+
:state => t[:watchdog_state])
|
32
|
+
raise TimeoutError, "watchdog timeout"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end # def watch
|
37
|
+
end # class LogStash::ThreadWatchdog
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/environment"
|
3
|
+
require "logstash/json"
|
4
|
+
require "forwardable"
|
5
|
+
require "date"
|
6
|
+
require "time"
|
7
|
+
|
8
|
+
module LogStash
|
9
|
+
class TimestampParserError < StandardError; end
|
10
|
+
|
11
|
+
class Timestamp
|
12
|
+
extend Forwardable
|
13
|
+
include Comparable
|
14
|
+
|
15
|
+
def_delegators :@time, :tv_usec, :usec, :year, :iso8601, :to_i, :tv_sec, :to_f, :to_edn, :<=>, :+
|
16
|
+
|
17
|
+
attr_reader :time
|
18
|
+
|
19
|
+
ISO8601_STRFTIME = "%04d-%02d-%02dT%02d:%02d:%02d.%06d%+03d:00".freeze
|
20
|
+
ISO8601_PRECISION = 3
|
21
|
+
|
22
|
+
def initialize(time = Time.new)
|
23
|
+
@time = time.utc
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.at(*args)
|
27
|
+
Timestamp.new(::Time.at(*args))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse(*args)
|
31
|
+
Timestamp.new(::Time.parse(*args))
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.now
|
35
|
+
Timestamp.new(::Time.now)
|
36
|
+
end
|
37
|
+
|
38
|
+
# coerce tries different strategies based on the time object class to convert into a Timestamp.
|
39
|
+
# @param [String, Time, Timestamp] time the time object to try coerce
|
40
|
+
# @return [Timestamp, nil] Timestamp will be returned if successful otherwise nil
|
41
|
+
# @raise [TimestampParserError] on String with invalid format
|
42
|
+
def self.coerce(time)
|
43
|
+
case time
|
44
|
+
when String
|
45
|
+
LogStash::Timestamp.parse_iso8601(time)
|
46
|
+
when LogStash::Timestamp
|
47
|
+
time
|
48
|
+
when Time
|
49
|
+
LogStash::Timestamp.new(time)
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if LogStash::Environment.jruby?
|
56
|
+
JODA_ISO8601_PARSER = org.joda.time.format.ISODateTimeFormat.dateTimeParser
|
57
|
+
UTC = org.joda.time.DateTimeZone.forID("UTC")
|
58
|
+
|
59
|
+
def self.parse_iso8601(t)
|
60
|
+
millis = JODA_ISO8601_PARSER.parseMillis(t)
|
61
|
+
LogStash::Timestamp.at(millis / 1000, (millis % 1000) * 1000)
|
62
|
+
rescue => e
|
63
|
+
raise(TimestampParserError, "invalid timestamp string #{t.inspect}, error=#{e.inspect}")
|
64
|
+
end
|
65
|
+
|
66
|
+
else
|
67
|
+
|
68
|
+
def self.parse_iso8601(t)
|
69
|
+
# warning, ruby's Time.parse is *really* terrible and slow.
|
70
|
+
LogStash::Timestamp.new(::Time.parse(t))
|
71
|
+
rescue => e
|
72
|
+
raise(TimestampParserError, "invalid timestamp string #{t.inspect}, error=#{e.inspect}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def utc
|
77
|
+
@time.utc # modifies the receiver
|
78
|
+
self
|
79
|
+
end
|
80
|
+
alias_method :gmtime, :utc
|
81
|
+
|
82
|
+
def to_json(*args)
|
83
|
+
# ignore arguments to respect accepted to_json method signature
|
84
|
+
"\"" + to_iso8601 + "\""
|
85
|
+
end
|
86
|
+
alias_method :inspect, :to_json
|
87
|
+
|
88
|
+
def to_iso8601
|
89
|
+
@iso8601 ||= @time.iso8601(ISO8601_PRECISION)
|
90
|
+
end
|
91
|
+
alias_method :to_s, :to_iso8601
|
92
|
+
|
93
|
+
def -(value)
|
94
|
+
@time - (value.is_a?(Timestamp) ? value.time : value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/namespace"
|
3
|
+
require "logstash/environment"
|
4
|
+
|
5
|
+
module LogStash::Util
|
6
|
+
UNAME = case RbConfig::CONFIG["host_os"]
|
7
|
+
when /^linux/; "linux"
|
8
|
+
else; RbConfig::CONFIG["host_os"]
|
9
|
+
end
|
10
|
+
|
11
|
+
PR_SET_NAME = 15
|
12
|
+
def self.set_thread_name(name)
|
13
|
+
if RUBY_ENGINE == "jruby"
|
14
|
+
# Keep java and ruby thread names in sync.
|
15
|
+
Java::java.lang.Thread.currentThread.setName(name)
|
16
|
+
end
|
17
|
+
Thread.current[:name] = name
|
18
|
+
|
19
|
+
if UNAME == "linux"
|
20
|
+
require "logstash/util/prctl"
|
21
|
+
# prctl PR_SET_NAME allows up to 16 bytes for a process name
|
22
|
+
# since MRI 1.9, JRuby, and Rubinius use system threads for this.
|
23
|
+
LibC.prctl(PR_SET_NAME, name[0..16], 0, 0, 0)
|
24
|
+
end
|
25
|
+
end # def set_thread_name
|
26
|
+
|
27
|
+
# Merge hash 'src' into 'dst' nondestructively
|
28
|
+
#
|
29
|
+
# Duplicate keys will become array values
|
30
|
+
#
|
31
|
+
# [ src["foo"], dst["foo"] ]
|
32
|
+
def self.hash_merge(dst, src)
|
33
|
+
src.each do |name, svalue|
|
34
|
+
if dst.include?(name)
|
35
|
+
dvalue = dst[name]
|
36
|
+
if dvalue.is_a?(Hash) && svalue.is_a?(Hash)
|
37
|
+
dvalue = hash_merge(dvalue, svalue)
|
38
|
+
elsif svalue.is_a?(Array)
|
39
|
+
if dvalue.is_a?(Array)
|
40
|
+
# merge arrays without duplicates.
|
41
|
+
dvalue |= svalue
|
42
|
+
else
|
43
|
+
dvalue = [dvalue] | svalue
|
44
|
+
end
|
45
|
+
else
|
46
|
+
if dvalue.is_a?(Array)
|
47
|
+
dvalue << svalue unless dvalue.include?(svalue)
|
48
|
+
else
|
49
|
+
dvalue = [dvalue, svalue] unless dvalue == svalue
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
dst[name] = dvalue
|
54
|
+
else
|
55
|
+
# dst doesn't have this key, just set it.
|
56
|
+
dst[name] = svalue
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
return dst
|
61
|
+
end # def self.hash_merge
|
62
|
+
|
63
|
+
# Merge hash 'src' into 'dst' nondestructively
|
64
|
+
#
|
65
|
+
# Duplicate keys will become array values
|
66
|
+
# Arrays merged will simply be appended.
|
67
|
+
#
|
68
|
+
# [ src["foo"], dst["foo"] ]
|
69
|
+
def self.hash_merge_with_dups(dst, src)
|
70
|
+
src.each do |name, svalue|
|
71
|
+
if dst.include?(name)
|
72
|
+
dvalue = dst[name]
|
73
|
+
if dvalue.is_a?(Hash) && svalue.is_a?(Hash)
|
74
|
+
dvalue = hash_merge(dvalue, svalue)
|
75
|
+
elsif svalue.is_a?(Array)
|
76
|
+
if dvalue.is_a?(Array)
|
77
|
+
# merge arrays without duplicates.
|
78
|
+
dvalue += svalue
|
79
|
+
else
|
80
|
+
dvalue = [dvalue] + svalue
|
81
|
+
end
|
82
|
+
else
|
83
|
+
if dvalue.is_a?(Array)
|
84
|
+
dvalue << svalue unless dvalue.include?(svalue)
|
85
|
+
else
|
86
|
+
dvalue = [dvalue, svalue] unless dvalue == svalue
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
dst[name] = dvalue
|
91
|
+
else
|
92
|
+
# dst doesn't have this key, just set it.
|
93
|
+
dst[name] = svalue
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
return dst
|
98
|
+
end # def self.hash_merge
|
99
|
+
|
100
|
+
def self.hash_merge_many(*hashes)
|
101
|
+
dst = {}
|
102
|
+
hashes.each do |hash|
|
103
|
+
hash_merge_with_dups(dst, hash)
|
104
|
+
end
|
105
|
+
return dst
|
106
|
+
end # def hash_merge_many
|
107
|
+
|
108
|
+
|
109
|
+
# nomalize method definition based on platform.
|
110
|
+
# normalize is used to convert an object create through
|
111
|
+
# json deserialization from JrJackson in :raw mode to pure Ruby
|
112
|
+
# to support these pure Ruby object monkey patches.
|
113
|
+
# see logstash/json.rb and logstash/java_integration.rb
|
114
|
+
|
115
|
+
if LogStash::Environment.jruby?
|
116
|
+
require "java"
|
117
|
+
|
118
|
+
# recursively convert any Java LinkedHashMap and ArrayList to pure Ruby.
|
119
|
+
# will not recurse into pure Ruby objects. Pure Ruby object should never
|
120
|
+
# contain LinkedHashMap and ArrayList since these are only created at
|
121
|
+
# initial deserialization, anything after (deeper) will be pure Ruby.
|
122
|
+
def self.normalize(o)
|
123
|
+
case o
|
124
|
+
when Java::JavaUtil::LinkedHashMap
|
125
|
+
o.inject({}){|r, (k, v)| r[k] = normalize(v); r}
|
126
|
+
when Java::JavaUtil::ArrayList
|
127
|
+
o.map{|i| normalize(i)}
|
128
|
+
else
|
129
|
+
o
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
else
|
134
|
+
|
135
|
+
# identity function, pure Ruby object don't need normalization.
|
136
|
+
def self.normalize(o); o; end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.stringify_symbols(o)
|
140
|
+
case o
|
141
|
+
when Hash
|
142
|
+
o.inject({}){|r, (k, v)| r[k.is_a?(Symbol) ? k.to_s : k] = stringify_symbols(v); r}
|
143
|
+
when Array
|
144
|
+
o.map{|i| stringify_symbols(i)}
|
145
|
+
when Symbol
|
146
|
+
o.to_s
|
147
|
+
else
|
148
|
+
o
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end # module LogStash::Util
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "logstash/namespace"
|
4
|
+
require "logstash/util"
|
5
|
+
|
6
|
+
module LogStash::Util
|
7
|
+
|
8
|
+
# PathCache is a singleton which globally caches a parsed fields path for the path to the
|
9
|
+
# container hash and key in that hash.
|
10
|
+
module PathCache
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def get(accessor)
|
14
|
+
@cache ||= {}
|
15
|
+
@cache[accessor] ||= parse(accessor)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(accessor)
|
19
|
+
path = accessor.split(/[\[\]]/).select{|s| !s.empty?}
|
20
|
+
[path.pop, path]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Accessors uses a lookup table to speedup access of an accessor field of the type
|
25
|
+
# "[hello][world]" to the underlying store hash into {"hello" => {"world" => "foo"}}
|
26
|
+
class Accessors
|
27
|
+
|
28
|
+
def initialize(store)
|
29
|
+
@store = store
|
30
|
+
@lut = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(accessor)
|
34
|
+
target, key = lookup(accessor)
|
35
|
+
unless target.nil?
|
36
|
+
target.is_a?(Array) ? target[key.to_i] : target[key]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def set(accessor, value)
|
41
|
+
target, key = store_and_lookup(accessor)
|
42
|
+
target[target.is_a?(Array) ? key.to_i : key] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def strict_set(accessor, value)
|
46
|
+
set(accessor, LogStash::Event.validate_value(value))
|
47
|
+
end
|
48
|
+
|
49
|
+
def del(accessor)
|
50
|
+
target, key = lookup(accessor)
|
51
|
+
unless target.nil?
|
52
|
+
target.is_a?(Array) ? target.delete_at(key.to_i) : target.delete(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def lookup(accessor)
|
59
|
+
target, key = lookup_path(accessor)
|
60
|
+
if target.nil?
|
61
|
+
[target, key]
|
62
|
+
else
|
63
|
+
@lut[accessor] = [target, key]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def store_and_lookup(accessor)
|
68
|
+
@lut[accessor] ||= store_path(accessor)
|
69
|
+
end
|
70
|
+
|
71
|
+
def lookup_path(accessor)
|
72
|
+
key, path = PathCache.get(accessor)
|
73
|
+
target = path.inject(@store) do |r, k|
|
74
|
+
if r.nil?
|
75
|
+
return nil
|
76
|
+
end
|
77
|
+
r[r.is_a?(Array) ? k.to_i : k]
|
78
|
+
end
|
79
|
+
[target, key]
|
80
|
+
end
|
81
|
+
|
82
|
+
def store_path(accessor)
|
83
|
+
key, path = PathCache.get(accessor)
|
84
|
+
target = path.inject(@store) {|r, k| r[r.is_a?(Array) ? k.to_i : k] ||= {}}
|
85
|
+
[target, key]
|
86
|
+
end
|
87
|
+
end # class Accessors
|
88
|
+
end # module LogStash::Util
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# BufferedTokenizer - Statefully split input data by a specifiable token
|
3
|
+
#
|
4
|
+
# Authors:: Tony Arcieri, Martin Emde
|
5
|
+
#
|
6
|
+
#----------------------------------------------------------------------------
|
7
|
+
#
|
8
|
+
# Copyright (C) 2006-07 by Tony Arcieri and Martin Emde
|
9
|
+
#
|
10
|
+
# Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
|
11
|
+
#
|
12
|
+
#---------------------------------------------------------------------------
|
13
|
+
#
|
14
|
+
|
15
|
+
# (C)2006 Tony Arcieri, Martin Emde
|
16
|
+
# Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
|
17
|
+
|
18
|
+
# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
|
19
|
+
# by default. It allows input to be spoon-fed from some outside source which
|
20
|
+
# receives arbitrary length datagrams which may-or-may-not contain the token
|
21
|
+
# by which entities are delimited.
|
22
|
+
#
|
23
|
+
# Commonly used to parse lines out of incoming data:
|
24
|
+
#
|
25
|
+
# module LineBufferedConnection
|
26
|
+
# def receive_data(data)
|
27
|
+
# (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
|
28
|
+
# receive_line(line)
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
|
33
|
+
module FileWatch; class BufferedTokenizer
|
34
|
+
# New BufferedTokenizers will operate on lines delimited by "\n" by default
|
35
|
+
# or allow you to specify any delimiter token you so choose, which will then
|
36
|
+
# be used by String#split to tokenize the input data
|
37
|
+
def initialize(delimiter = "\n", size_limit = nil)
|
38
|
+
# Store the specified delimiter
|
39
|
+
@delimiter = delimiter
|
40
|
+
|
41
|
+
# Store the specified size limitation
|
42
|
+
@size_limit = size_limit
|
43
|
+
|
44
|
+
# The input buffer is stored as an array. This is by far the most efficient
|
45
|
+
# approach given language constraints (in C a linked list would be a more
|
46
|
+
# appropriate data structure). Segments of input data are stored in a list
|
47
|
+
# which is only joined when a token is reached, substantially reducing the
|
48
|
+
# number of objects required for the operation.
|
49
|
+
@input = []
|
50
|
+
|
51
|
+
# Size of the input buffer
|
52
|
+
@input_size = 0
|
53
|
+
end
|
54
|
+
|
55
|
+
# Extract takes an arbitrary string of input data and returns an array of
|
56
|
+
# tokenized entities, provided there were any available to extract. This
|
57
|
+
# makes for easy processing of datagrams using a pattern like:
|
58
|
+
#
|
59
|
+
# tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
|
60
|
+
def extract(data)
|
61
|
+
# Extract token-delimited entities from the input string with the split command.
|
62
|
+
# There's a bit of craftiness here with the -1 parameter. Normally split would
|
63
|
+
# behave no differently regardless of if the token lies at the very end of the
|
64
|
+
# input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
|
65
|
+
# return "" in this case, meaning that the last entry in the list represents a
|
66
|
+
# new segment of data where the token has not been encountered
|
67
|
+
entities = data.split @delimiter, -1
|
68
|
+
|
69
|
+
# Check to see if the buffer has exceeded capacity, if we're imposing a limit
|
70
|
+
if @size_limit
|
71
|
+
raise 'input buffer full' if @input_size + entities.first.size > @size_limit
|
72
|
+
@input_size += entities.first.size
|
73
|
+
end
|
74
|
+
|
75
|
+
# Move the first entry in the resulting array into the input buffer. It represents
|
76
|
+
# the last segment of a token-delimited entity unless it's the only entry in the list.
|
77
|
+
@input << entities.shift
|
78
|
+
|
79
|
+
# If the resulting array from the split is empty, the token was not encountered
|
80
|
+
# (not even at the end of the buffer). Since we've encountered no token-delimited
|
81
|
+
# entities this go-around, return an empty array.
|
82
|
+
return [] if entities.empty?
|
83
|
+
|
84
|
+
# At this point, we've hit a token, or potentially multiple tokens. Now we can bring
|
85
|
+
# together all the data we've buffered from earlier calls without hitting a token,
|
86
|
+
# and add it to our list of discovered entities.
|
87
|
+
entities.unshift @input.join
|
88
|
+
|
89
|
+
=begin
|
90
|
+
# Note added by FC, 10Jul07. This paragraph contains a regression. It breaks
|
91
|
+
# empty tokens. Think of the empty line that delimits an HTTP header. It will have
|
92
|
+
# two "\n" delimiters in a row, and this code mishandles the resulting empty token.
|
93
|
+
# It someone figures out how to fix the problem, we can re-enable this code branch.
|
94
|
+
# Multi-character token support.
|
95
|
+
# Split any tokens that were incomplete on the last iteration buf complete now.
|
96
|
+
entities.map! do |e|
|
97
|
+
e.split @delimiter, -1
|
98
|
+
end
|
99
|
+
# Flatten the resulting array. This has the side effect of removing the empty
|
100
|
+
# entry at the end that was produced by passing -1 to split. Add it again if
|
101
|
+
# necessary.
|
102
|
+
if (entities[-1] == [])
|
103
|
+
entities.flatten! << []
|
104
|
+
else
|
105
|
+
entities.flatten!
|
106
|
+
end
|
107
|
+
=end
|
108
|
+
|
109
|
+
# Now that we've hit a token, joined the input buffer and added it to the entities
|
110
|
+
# list, we can go ahead and clear the input buffer. All of the segments that were
|
111
|
+
# stored before the join can now be garbage collected.
|
112
|
+
@input.clear
|
113
|
+
|
114
|
+
# The last entity in the list is not token delimited, however, thanks to the -1
|
115
|
+
# passed to split. It represents the beginning of a new list of as-yet-untokenized
|
116
|
+
# data, so we add it to the start of the list.
|
117
|
+
@input << entities.pop
|
118
|
+
|
119
|
+
# Set the new input buffer size, provided we're keeping track
|
120
|
+
@input_size = @input.first.size if @size_limit
|
121
|
+
|
122
|
+
# Now we're left with the list of extracted token-delimited entities we wanted
|
123
|
+
# in the first place. Hooray!
|
124
|
+
entities
|
125
|
+
end
|
126
|
+
|
127
|
+
# Flush the contents of the input buffer, i.e. return the input buffer even though
|
128
|
+
# a token has not yet been encountered
|
129
|
+
def flush
|
130
|
+
buffer = @input.join
|
131
|
+
@input.clear
|
132
|
+
buffer
|
133
|
+
end
|
134
|
+
|
135
|
+
# Is the buffer empty?
|
136
|
+
def empty?
|
137
|
+
@input.empty?
|
138
|
+
end
|
139
|
+
end; end
|