huginn_http_request_agent 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +7 -0
- data/lib/huginn_http_request_agent.rb +4 -0
- data/lib/huginn_http_request_agent/http_request_agent.rb +257 -0
- data/spec/http_request_agent_spec.rb +13 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aa4b89cbaf5fef82663f2845a176ff324843eb74
|
4
|
+
data.tar.gz: 45bf7be76d8549e519571e0a4da22101e2167868
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c8cbcb0ef880d72ac64ac14ce67a1b2f611976b9ee9eef7a09f0ad94a13667806cf4cd6a21692d0b551d1286ac777018d155300dc01e60f53400c5e0daec184
|
7
|
+
data.tar.gz: bec6693f64867873839b1b22be72faf25dfe754f810b79958fd294bb494dfadd0bf008666e67387353eb22a31f24c17ae620e9a86ddb880903bf2e5e1f8158f1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2019 Jacob Spizziri
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,257 @@
|
|
1
|
+
module Agents
|
2
|
+
class HttpRequestAgent < Agent
|
3
|
+
include EventHeadersConcern
|
4
|
+
include WebRequestConcern
|
5
|
+
include FileHandling
|
6
|
+
|
7
|
+
consumes_file_pointer!
|
8
|
+
|
9
|
+
MIME_RE = /\A\w+\/.+\z/
|
10
|
+
|
11
|
+
can_dry_run!
|
12
|
+
no_bulk_receive!
|
13
|
+
default_schedule "never"
|
14
|
+
|
15
|
+
description do
|
16
|
+
<<-MD
|
17
|
+
A HTTP Request Agent receives events from other agents (or runs periodically), merges those events with the [Liquid-interpolated](https://github.com/huginn/huginn/wiki/Formatting-Events-using-Liquid) contents of `payload`, and sends the request to a specified url.
|
18
|
+
|
19
|
+
### Options
|
20
|
+
|
21
|
+
**Primary:**
|
22
|
+
|
23
|
+
- `endpoint` - The URL you would like to send request. Please include the URI scheme (`http` or `https`).
|
24
|
+
- `method` - The lowercase HTTP verb you would like to use (i.e. `get`, `post`, `put`, `patch`, and `delete`).
|
25
|
+
- `content_type` - The content type of the request (see below for more detail).
|
26
|
+
- `payload` - For `get` requests this will be converted to URL params. For `post`, `put`, and `patch` this will be the request `body`. When `payload` is a string `no_merge` has to be set to `true`.
|
27
|
+
|
28
|
+
**Other:**
|
29
|
+
|
30
|
+
- `headers` - When present, it should be a hash of headers to send with the request (e.g. an `Authorization` header).
|
31
|
+
- `basic_auth` - Specify HTTP basic auth parameters: `"username:password"`, or `["username", "password"]`.
|
32
|
+
- `disable_ssl_verification` - Set to `true` to disable ssl verification.
|
33
|
+
- `user_agent` - A custom User-Agent name (default: "Faraday v#{Faraday::VERSION}").
|
34
|
+
- `no_merge` - Setting this value to `true` will result in the incoming event, but still send the interpolated payload
|
35
|
+
- `output_mode` - Setting this value to `merge` will result in the emitted Event being merged into the original contents of the received Event. Setting it to `clean` will result in no merge.
|
36
|
+
- `emit_events` - Setting this to `true` will result in the server response being emitted as an Event which can be subsequently consumed by another agent (ex. to a WebsiteAgent for parsing of the response body)
|
37
|
+
|
38
|
+
### Content Type's:
|
39
|
+
|
40
|
+
**Supported `content_type`'s:**
|
41
|
+
|
42
|
+
- `json` to send JSON instead
|
43
|
+
- `xml` to send XML, where the name of the root element may be specified using `xml_root`
|
44
|
+
|
45
|
+
By default, non-GETs will be sent with form encoding (`application/json`).
|
46
|
+
|
47
|
+
When `content_type` contains a [MIME](https://en.wikipedia.org/wiki/Media_type) type, and `payload` is a string, its interpolated value will be sent as a string in the HTTP request's body and the request's `Content-Type` HTTP header will be set to `content_type`.
|
48
|
+
|
49
|
+
|
50
|
+
### Response
|
51
|
+
|
52
|
+
`emit_events` must be set to `true` to use the response. The emitted Event will have a "headers" hash and a "status" integer value. No data processing will be attempted by this Agent, so the resopnse Event's "body" value will always be raw text.
|
53
|
+
|
54
|
+
### Misc
|
55
|
+
|
56
|
+
Set `event_headers` to a list of header names, either in an array of string or in a comma-separated string, to include only some of the header values.
|
57
|
+
Set `event_headers_style` to one of the following values to normalize the keys of "headers" for downstream agents' convenience:
|
58
|
+
|
59
|
+
- `capitalized` (default) - Header names are capitalized; e.g. "Content-Type"
|
60
|
+
- `downcased` - Header names are downcased; e.g. "content-type"
|
61
|
+
- `snakecased` - Header names are snakecased; e.g. "content_type"
|
62
|
+
- `raw` - Backward compatibility option to leave them unmodified from what the underlying HTTP library returns.
|
63
|
+
|
64
|
+
#{receiving_file_handling_agent_description}
|
65
|
+
When receiving a `file_pointer` the request will be sent with multipart encoding (`multipart/form-data`) and `content_type` is ignored. `upload_key` can be used to specify the parameter in which the file will be sent, it defaults to `file`.
|
66
|
+
When a `payload` is passed with a `get` method then the payload is converted into URL query params.
|
67
|
+
MD
|
68
|
+
end
|
69
|
+
|
70
|
+
event_description <<-MD
|
71
|
+
Events look like this:
|
72
|
+
{
|
73
|
+
"status": 200,
|
74
|
+
"headers": {
|
75
|
+
"Content-Type": "text/html",
|
76
|
+
...
|
77
|
+
},
|
78
|
+
"body": "<html>Some data...</html>"
|
79
|
+
}
|
80
|
+
Original event contents will be merged when `output_mode` is set to `merge`.
|
81
|
+
MD
|
82
|
+
|
83
|
+
def default_options
|
84
|
+
{
|
85
|
+
'endpoint' => "http://www.example.com",
|
86
|
+
'expected_receive_period_in_days' => '1',
|
87
|
+
'content_type' => 'json',
|
88
|
+
'method' => 'get',
|
89
|
+
'payload' => {
|
90
|
+
'key' => 'value',
|
91
|
+
'something' => 'the event contained {{ somekey }}'
|
92
|
+
},
|
93
|
+
'headers' => {},
|
94
|
+
'emit_events' => 'true',
|
95
|
+
'no_merge' => 'false',
|
96
|
+
'output_mode' => 'clean'
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def working?
|
101
|
+
return false if recent_error_logs?
|
102
|
+
|
103
|
+
if interpolated['expected_receive_period_in_days'].present?
|
104
|
+
return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
|
105
|
+
end
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
def method
|
111
|
+
(interpolated['method'].presence || 'post').to_s.downcase
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_options
|
115
|
+
unless options['endpoint'].present?
|
116
|
+
errors.add(:base, "endpoint is a required field")
|
117
|
+
end
|
118
|
+
|
119
|
+
if options['payload'].present? && %w[get delete].include?(method) && !(options['payload'].is_a?(Hash) || options['payload'].is_a?(Array))
|
120
|
+
errors.add(:base, "if provided, payload must be a hash or an array")
|
121
|
+
end
|
122
|
+
|
123
|
+
if options['payload'].present? && %w[post put patch].include?(method)
|
124
|
+
if !(options['payload'].is_a?(Hash) || options['payload'].is_a?(Array)) && options['content_type'] !~ MIME_RE
|
125
|
+
errors.add(:base, "if provided, payload must be a hash or an array")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if options['content_type'] =~ MIME_RE && options['payload'].is_a?(String) && boolify(options['no_merge']) != true
|
130
|
+
errors.add(:base, "when the payload is a string, `no_merge` has to be set to `true`")
|
131
|
+
end
|
132
|
+
|
133
|
+
if options['content_type'] == 'form' && options['payload'].present? && options['payload'].is_a?(Array)
|
134
|
+
errors.add(:base, "when content_type is a form, if provided, payload must be a hash")
|
135
|
+
end
|
136
|
+
|
137
|
+
if options.has_key?('emit_events') && boolify(options['emit_events']).nil?
|
138
|
+
errors.add(:base, "if provided, emit_events must be true or false")
|
139
|
+
end
|
140
|
+
|
141
|
+
validate_event_headers_options!
|
142
|
+
|
143
|
+
unless %w[post get put delete patch].include?(method)
|
144
|
+
errors.add(:base, "method must be 'post', 'get', 'put', 'delete', or 'patch'")
|
145
|
+
end
|
146
|
+
|
147
|
+
if options['no_merge'].present? && !%[true false].include?(options['no_merge'].to_s)
|
148
|
+
errors.add(:base, "if provided, no_merge must be 'true' or 'false'")
|
149
|
+
end
|
150
|
+
|
151
|
+
if options['output_mode'].present? && !options['output_mode'].to_s.include?('{') && !%[clean merge].include?(options['output_mode'].to_s)
|
152
|
+
errors.add(:base, "if provided, output_mode must be 'clean' or 'merge'")
|
153
|
+
end
|
154
|
+
|
155
|
+
unless headers.is_a?(Hash)
|
156
|
+
errors.add(:base, "if provided, headers must be a hash")
|
157
|
+
end
|
158
|
+
|
159
|
+
validate_web_request_options!
|
160
|
+
end
|
161
|
+
|
162
|
+
def receive(incoming_events)
|
163
|
+
incoming_events.each do |event|
|
164
|
+
interpolate_with(event) do
|
165
|
+
outgoing = interpolated['payload'].presence || {}
|
166
|
+
if boolify(interpolated['no_merge'])
|
167
|
+
handle outgoing, event, headers(interpolated[:headers])
|
168
|
+
else
|
169
|
+
handle outgoing.merge(event.payload), event, headers(interpolated[:headers])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def check
|
176
|
+
handle interpolated['payload'].presence || {}, headers
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def handle(data, event = Event.new, headers)
|
182
|
+
url = interpolated(event.payload)[:endpoint]
|
183
|
+
|
184
|
+
case method
|
185
|
+
when 'get'
|
186
|
+
content_type = interpolated(event.payload)['content_type']
|
187
|
+
|
188
|
+
case content_type
|
189
|
+
when 'json'
|
190
|
+
headers['Content-Type'] = 'application/json; charset=utf-8'
|
191
|
+
url = faraday.build_url(url, data.compact)
|
192
|
+
else
|
193
|
+
params = data
|
194
|
+
body = nil
|
195
|
+
end
|
196
|
+
|
197
|
+
when 'delete'
|
198
|
+
params = data
|
199
|
+
body = nil
|
200
|
+
when 'post', 'put', 'patch'
|
201
|
+
params = nil
|
202
|
+
content_type = nil
|
203
|
+
|
204
|
+
if has_file_pointer?(event)
|
205
|
+
data[interpolated(event.payload)['upload_key'].presence || 'file'] = get_upload_io(event)
|
206
|
+
else
|
207
|
+
content_type = interpolated(event.payload)['content_type']
|
208
|
+
end
|
209
|
+
|
210
|
+
case content_type
|
211
|
+
when 'json'
|
212
|
+
headers['Content-Type'] = 'application/json; charset=utf-8'
|
213
|
+
body = data.to_json
|
214
|
+
when 'xml'
|
215
|
+
headers['Content-Type'] = 'text/xml; charset=utf-8'
|
216
|
+
body = data.to_xml(root: (interpolated(event.payload)[:xml_root] || 'post'))
|
217
|
+
when MIME_RE
|
218
|
+
headers['Content-Type'] = content_type
|
219
|
+
body = data.to_s
|
220
|
+
else
|
221
|
+
body = data
|
222
|
+
end
|
223
|
+
else
|
224
|
+
error "Invalid method '#{method}'"
|
225
|
+
end
|
226
|
+
|
227
|
+
response = faraday.run_request(method.to_sym, url, body, headers) { |request|
|
228
|
+
|
229
|
+
# open/read timeout in seconds
|
230
|
+
if interpolated['timeout'].to_i
|
231
|
+
request.options.timeout = interpolated['timeout'].to_i
|
232
|
+
end
|
233
|
+
|
234
|
+
# connection open timeout in seconds
|
235
|
+
if interpolated['open_timeout'].to_i
|
236
|
+
request.options.open_timeout = interpolated['open_timeout'].to_i
|
237
|
+
end
|
238
|
+
|
239
|
+
request.params.update(params) if params
|
240
|
+
}
|
241
|
+
|
242
|
+
if boolify(interpolated['emit_events'])
|
243
|
+
new_event = interpolated['output_mode'].to_s == 'merge' ? event.payload.dup : {}
|
244
|
+
create_event payload: new_event.merge(
|
245
|
+
body: response.body,
|
246
|
+
status: response.status
|
247
|
+
).merge(
|
248
|
+
event_headers_payload(response.headers)
|
249
|
+
)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def event_headers_key
|
254
|
+
super || 'headers'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'huginn_agent/spec_helper'
|
3
|
+
|
4
|
+
describe Agents::HttpRequestAgent do
|
5
|
+
before(:each) do
|
6
|
+
@valid_options = Agents::HttpRequestAgent.new.default_options
|
7
|
+
@checker = Agents::HttpRequestAgent.new(:name => "HttpRequestAgent", :options => @valid_options)
|
8
|
+
@checker.user = users(:bob)
|
9
|
+
@checker.save!
|
10
|
+
end
|
11
|
+
|
12
|
+
pending "add specs here"
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: huginn_http_request_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacob Spizziri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-10-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: huginn_agent
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Write a longer description or delete this line.
|
56
|
+
email:
|
57
|
+
- jspizziri@weare5stones.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- LICENSE.txt
|
63
|
+
- lib/huginn_http_request_agent.rb
|
64
|
+
- lib/huginn_http_request_agent/http_request_agent.rb
|
65
|
+
- spec/http_request_agent_spec.rb
|
66
|
+
homepage: https://github.com/[my-github-username]/huginn_http_request_agent
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.6.10
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Write a short summary, because Rubygems requires one.
|
90
|
+
test_files:
|
91
|
+
- spec/http_request_agent_spec.rb
|