fluent-plugin-out-http 1.2.0 → 1.3.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 +4 -4
- data/.gitignore +8 -8
- data/.travis.yml +25 -25
- data/CHANGELOG.md +72 -67
- data/Gemfile +4 -4
- data/ISSUE_TEMPLATE.md +21 -21
- data/LICENSE.txt +11 -11
- data/README.md +45 -38
- data/Rakefile +11 -11
- data/fluent-plugin-out-http.gemspec +25 -25
- data/lib/fluent/plugin/out_http.rb +297 -246
- data/lib/fluent/test/http_output_test.rb +42 -42
- data/test/plugin/script/plugin/formatter_test.rb +21 -21
- data/test/plugin/test_out_http.rb +771 -732
- metadata +4 -3
@@ -1,246 +1,297 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
require 'uri'
|
3
|
-
require 'yajl'
|
4
|
-
require 'fluent/plugin/output'
|
5
|
-
require 'openssl'
|
6
|
-
|
7
|
-
class Fluent::Plugin::HTTPOutput < Fluent::Plugin::Output
|
8
|
-
Fluent::Plugin.register_output('http', self)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
config_param :
|
57
|
-
|
58
|
-
#
|
59
|
-
config_param :
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
req
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
req
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'yajl'
|
4
|
+
require 'fluent/plugin/output'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
class Fluent::Plugin::HTTPOutput < Fluent::Plugin::Output
|
8
|
+
Fluent::Plugin.register_output('http', self)
|
9
|
+
|
10
|
+
class RecoverableResponse < StandardError; end
|
11
|
+
|
12
|
+
helpers :compat_parameters, :formatter
|
13
|
+
|
14
|
+
DEFAULT_BUFFER_TYPE = "memory"
|
15
|
+
DEFAULT_FORMATTER = "json"
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
# Endpoint URL ex. http://localhost.local/api/
|
22
|
+
config_param :endpoint_url, :string
|
23
|
+
|
24
|
+
# Set Net::HTTP.verify_mode to `OpenSSL::SSL::VERIFY_NONE`
|
25
|
+
config_param :ssl_no_verify, :bool, :default => false
|
26
|
+
|
27
|
+
# HTTP method
|
28
|
+
config_param :http_method, :enum, list: [:get, :put, :post, :delete], :default => :post
|
29
|
+
|
30
|
+
# form | json | text | raw
|
31
|
+
config_param :serializer, :enum, list: [:json, :form, :text, :raw], :default => :form
|
32
|
+
|
33
|
+
# Simple rate limiting: ignore any records within `rate_limit_msec`
|
34
|
+
# since the last one.
|
35
|
+
config_param :rate_limit_msec, :integer, :default => 0
|
36
|
+
|
37
|
+
# Raise errors that were rescued during HTTP requests?
|
38
|
+
config_param :raise_on_error, :bool, :default => true
|
39
|
+
|
40
|
+
# Specify recoverable error codes
|
41
|
+
config_param :recoverable_status_codes, :array, value_type: :integer, default: [503]
|
42
|
+
|
43
|
+
# ca file to use for https request
|
44
|
+
config_param :cacert_file, :string, :default => ''
|
45
|
+
|
46
|
+
# specify client sertificate
|
47
|
+
config_param :client_cert_path, :string, :default => ''
|
48
|
+
|
49
|
+
# specify private key path
|
50
|
+
config_param :private_key_path, :string, :default => ''
|
51
|
+
|
52
|
+
# specify private key passphrase
|
53
|
+
config_param :private_key_passphrase, :string, :default => '', :secret => true
|
54
|
+
|
55
|
+
# custom headers
|
56
|
+
config_param :custom_headers, :hash, :default => nil
|
57
|
+
|
58
|
+
# 'none' | 'basic' | 'jwt' | 'bearer'
|
59
|
+
config_param :authentication, :enum, list: [:none, :basic, :jwt, :bearer], :default => :none
|
60
|
+
config_param :username, :string, :default => ''
|
61
|
+
config_param :password, :string, :default => '', :secret => true
|
62
|
+
config_param :token, :string, :default => ''
|
63
|
+
# Switch non-buffered/buffered plugin
|
64
|
+
config_param :buffered, :bool, :default => false
|
65
|
+
config_param :bulk_request, :bool, :default => false
|
66
|
+
|
67
|
+
config_section :buffer do
|
68
|
+
config_set_default :@type, DEFAULT_BUFFER_TYPE
|
69
|
+
config_set_default :chunk_keys, ['tag']
|
70
|
+
end
|
71
|
+
|
72
|
+
config_section :format do
|
73
|
+
config_set_default :@type, DEFAULT_FORMATTER
|
74
|
+
end
|
75
|
+
|
76
|
+
def configure(conf)
|
77
|
+
compat_parameters_convert(conf, :buffer, :formatter)
|
78
|
+
super
|
79
|
+
|
80
|
+
@ssl_verify_mode = if @ssl_no_verify
|
81
|
+
OpenSSL::SSL::VERIFY_NONE
|
82
|
+
else
|
83
|
+
OpenSSL::SSL::VERIFY_PEER
|
84
|
+
end
|
85
|
+
|
86
|
+
@ca_file = @cacert_file
|
87
|
+
@last_request_time = nil
|
88
|
+
raise Fluent::ConfigError, "'tag' in chunk_keys is required." if !@chunk_key_tag && @buffered
|
89
|
+
|
90
|
+
if @formatter_config = conf.elements('format').first
|
91
|
+
@formatter = formatter_create
|
92
|
+
end
|
93
|
+
|
94
|
+
if @bulk_request
|
95
|
+
class << self
|
96
|
+
alias_method :format, :bulk_request_format
|
97
|
+
end
|
98
|
+
@formatter = formatter_create(type: :json)
|
99
|
+
@serializer = :x_ndjson # secret settings for bulk_request
|
100
|
+
else
|
101
|
+
class << self
|
102
|
+
alias_method :format, :split_request_format
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def start
|
108
|
+
super
|
109
|
+
end
|
110
|
+
|
111
|
+
def shutdown
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
def format_url(tag, time, record)
|
116
|
+
@endpoint_url
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_body(req, tag, time, record)
|
120
|
+
if @serializer == :json
|
121
|
+
set_json_body(req, record)
|
122
|
+
elsif @serializer == :text
|
123
|
+
set_text_body(req, record)
|
124
|
+
elsif @serializer == :raw
|
125
|
+
set_raw_body(req, record)
|
126
|
+
elsif @serializer == :x_ndjson
|
127
|
+
set_bulk_body(req, record)
|
128
|
+
else
|
129
|
+
req.set_form_data(record)
|
130
|
+
end
|
131
|
+
req
|
132
|
+
end
|
133
|
+
|
134
|
+
def set_header(req, tag, time, record)
|
135
|
+
if @custom_headers
|
136
|
+
@custom_headers.each do |k,v|
|
137
|
+
req[k] = v
|
138
|
+
end
|
139
|
+
req
|
140
|
+
else
|
141
|
+
req
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def set_json_body(req, data)
|
146
|
+
req.body = Yajl.dump(data)
|
147
|
+
req['Content-Type'] = 'application/json'
|
148
|
+
end
|
149
|
+
|
150
|
+
def set_text_body(req, data)
|
151
|
+
req.body = data["message"]
|
152
|
+
req['Content-Type'] = 'text/plain'
|
153
|
+
end
|
154
|
+
|
155
|
+
def set_raw_body(req, data)
|
156
|
+
req.body = data.to_s
|
157
|
+
req['Content-Type'] = 'application/octet-stream'
|
158
|
+
end
|
159
|
+
|
160
|
+
def set_bulk_body(req, data)
|
161
|
+
req.body = data.to_s
|
162
|
+
req['Content-Type'] = 'application/x-ndjson'
|
163
|
+
end
|
164
|
+
|
165
|
+
def create_request(tag, time, record)
|
166
|
+
url = format_url(tag, time, record)
|
167
|
+
uri = URI.parse(url)
|
168
|
+
req = Net::HTTP.const_get(@http_method.to_s.capitalize).new(uri.request_uri)
|
169
|
+
set_body(req, tag, time, record)
|
170
|
+
set_header(req, tag, time, record)
|
171
|
+
return req, uri
|
172
|
+
end
|
173
|
+
|
174
|
+
def http_opts(uri)
|
175
|
+
opts = {
|
176
|
+
:use_ssl => uri.scheme == 'https'
|
177
|
+
}
|
178
|
+
opts[:verify_mode] = @ssl_verify_mode if opts[:use_ssl]
|
179
|
+
opts[:ca_file] = File.join(@ca_file) if File.file?(@ca_file)
|
180
|
+
opts[:cert] = OpenSSL::X509::Certificate.new(File.read(@client_cert_path)) if File.file?(@client_cert_path)
|
181
|
+
opts[:key] = OpenSSL::PKey.read(File.read(@private_key_path), @private_key_passphrase) if File.file?(@private_key_path)
|
182
|
+
opts
|
183
|
+
end
|
184
|
+
|
185
|
+
def proxies
|
186
|
+
ENV['HTTPS_PROXY'] || ENV['HTTP_PROXY'] || ENV['http_proxy'] || ENV['https_proxy']
|
187
|
+
end
|
188
|
+
|
189
|
+
def send_request(req, uri)
|
190
|
+
is_rate_limited = (@rate_limit_msec != 0 and not @last_request_time.nil?)
|
191
|
+
if is_rate_limited and ((Time.now.to_f - @last_request_time) * 1000.0 < @rate_limit_msec)
|
192
|
+
log.info('Dropped request due to rate limiting')
|
193
|
+
return
|
194
|
+
end
|
195
|
+
|
196
|
+
res = nil
|
197
|
+
|
198
|
+
begin
|
199
|
+
if @authentication == :basic
|
200
|
+
req.basic_auth(@username, @password)
|
201
|
+
elsif @authentication == :bearer
|
202
|
+
req['authorization'] = "bearer #{@token}"
|
203
|
+
elsif @authentication == :jwt
|
204
|
+
req['authorization'] = "jwt #{@token}"
|
205
|
+
end
|
206
|
+
@last_request_time = Time.now.to_f
|
207
|
+
|
208
|
+
if proxy = proxies
|
209
|
+
proxy_uri = URI.parse(proxy)
|
210
|
+
|
211
|
+
res = Net::HTTP.start(uri.host, uri.port,
|
212
|
+
proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password,
|
213
|
+
**http_opts(uri)) {|http| http.request(req) }
|
214
|
+
else
|
215
|
+
res = Net::HTTP.start(uri.host, uri.port, **http_opts(uri)) {|http| http.request(req) }
|
216
|
+
end
|
217
|
+
|
218
|
+
rescue => e # rescue all StandardErrors
|
219
|
+
# server didn't respond
|
220
|
+
log.warn "Net::HTTP.#{req.method.capitalize} raises exception: #{e.class}, '#{e.message}'"
|
221
|
+
raise e if @raise_on_error
|
222
|
+
else
|
223
|
+
unless res and res.is_a?(Net::HTTPSuccess)
|
224
|
+
res_summary = if res
|
225
|
+
"#{res.code} #{res.message} #{res.body}"
|
226
|
+
else
|
227
|
+
"res=nil"
|
228
|
+
end
|
229
|
+
if @recoverable_status_codes.include?(res.code.to_i)
|
230
|
+
raise RecoverableResponse, res_summary
|
231
|
+
else
|
232
|
+
log.warn "failed to #{req.method} #{uri} (#{res_summary})"
|
233
|
+
end
|
234
|
+
end #end unless
|
235
|
+
end # end begin
|
236
|
+
end # end send_request
|
237
|
+
|
238
|
+
def handle_record(tag, time, record)
|
239
|
+
if @formatter_config
|
240
|
+
record = @formatter.format(tag, time, record)
|
241
|
+
end
|
242
|
+
req, uri = create_request(tag, time, record)
|
243
|
+
send_request(req, uri)
|
244
|
+
end
|
245
|
+
|
246
|
+
def handle_records(tag, time, chunk)
|
247
|
+
req, uri = create_request(tag, time, chunk.read)
|
248
|
+
send_request(req, uri)
|
249
|
+
end
|
250
|
+
|
251
|
+
def prefer_buffered_processing
|
252
|
+
@buffered
|
253
|
+
end
|
254
|
+
|
255
|
+
def format(tag, time, record)
|
256
|
+
# For safety.
|
257
|
+
end
|
258
|
+
|
259
|
+
def split_request_format(tag, time, record)
|
260
|
+
[time, record].to_msgpack
|
261
|
+
end
|
262
|
+
|
263
|
+
def bulk_request_format(tag, time, record)
|
264
|
+
@formatter.format(tag, time, record)
|
265
|
+
end
|
266
|
+
|
267
|
+
def formatted_to_msgpack_binary?
|
268
|
+
if @bulk_request
|
269
|
+
false
|
270
|
+
else
|
271
|
+
true
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def multi_workers_ready?
|
276
|
+
true
|
277
|
+
end
|
278
|
+
|
279
|
+
def process(tag, es)
|
280
|
+
es.each do |time, record|
|
281
|
+
handle_record(tag, time, record)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def write(chunk)
|
286
|
+
tag = chunk.metadata.tag
|
287
|
+
@endpoint_url = extract_placeholders(@endpoint_url, chunk)
|
288
|
+
if @bulk_request
|
289
|
+
time = Fluent::Engine.now
|
290
|
+
handle_records(tag, time, chunk)
|
291
|
+
else
|
292
|
+
chunk.msgpack_each do |time, record|
|
293
|
+
handle_record(tag, time, record)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|