huginn_http_request_agent 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,4 @@
1
+ require 'huginn_agent'
2
+
3
+ #HuginnAgent.load 'huginn_http_request_agent/concerns/my_agent_concern'
4
+ HuginnAgent.register 'huginn_http_request_agent/http_request_agent'
@@ -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