post_agent.rb 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. module Agents
  2. class PostAgent < Agent
  3. include WebRequestConcern
  4. can_dry_run!
  5. no_bulk_receive!
  6. default_schedule "never"
  7. description <<-MD
  8. A Post Agent receives events from other agents (or runs periodically), merges those events with the [Liquid-interpolated](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) contents of `payload`, and sends the results as POST (or GET) requests to a specified url. To skip merging in the incoming event, but still send the interpolated payload, set `no_merge` to `true`.
  9. The `post_url` field must specify where you would like to send requests. Please include the URI scheme (`http` or `https`).
  10. The `method` used can be any of `get`, `post`, `put`, `patch`, and `delete`.
  11. By default, non-GETs will be sent with form encoding (`application/x-www-form-urlencoded`). Change `content_type` to `json` to send JSON instead. Change `content_type` to `xml` to send XML, where the name of the root element may be specified using `xml_root`, defaulting to `post`.
  12. If `emit_events` is set to `true`, the server response will be emitted as an Event and can be fed to a WebsiteAgent for parsing (using its `data_from_event` and `type` options). No data processing
  13. will be attempted by this Agent, so the Event's "body" value will always be raw text.
  14. Other Options:
  15. * `headers` - When present, it should be a hash of headers to send with the request.
  16. * `basic_auth` - Specify HTTP basic auth parameters: `"username:password"`, or `["username", "password"]`.
  17. * `disable_ssl_verification` - Set to `true` to disable ssl verification.
  18. * `user_agent` - A custom User-Agent name (default: "Faraday v#{Faraday::VERSION}").
  19. MD
  20. event_description <<-MD
  21. Events look like this:
  22. {
  23. "status": 200,
  24. "headers": {
  25. "Content-Type": "text/html",
  26. ...
  27. },
  28. "body": "<html>Some data...</html>"
  29. }
  30. MD
  31. def default_options
  32. {
  33. 'post_url' => "http://www.example.com",
  34. 'expected_receive_period_in_days' => '1',
  35. 'content_type' => 'form',
  36. 'method' => 'post',
  37. 'payload' => {
  38. 'key' => 'value',
  39. 'something' => 'the event contained {{ somekey }}'
  40. },
  41. 'headers' => {},
  42. 'emit_events' => 'false'
  43. }
  44. end
  45. def working?
  46. last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
  47. end
  48. def method
  49. (interpolated['method'].presence || 'post').to_s.downcase
  50. end
  51. def validate_options
  52. unless options['post_url'].present? && options['expected_receive_period_in_days'].present?
  53. errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
  54. end
  55. if options['payload'].present? && !options['payload'].is_a?(Hash)
  56. errors.add(:base, "if provided, payload must be a hash")
  57. end
  58. if options.has_key?('emit_events') && boolify(options['emit_events']).nil?
  59. errors.add(:base, "if provided, emit_events must be true or false")
  60. end
  61. unless %w[post get put delete patch].include?(method)
  62. errors.add(:base, "method must be 'post', 'get', 'put', 'delete', or 'patch'")
  63. end
  64. if options['no_merge'].present? && !%[true false].include?(options['no_merge'].to_s)
  65. errors.add(:base, "if provided, no_merge must be 'true' or 'false'")
  66. end
  67. unless headers.is_a?(Hash)
  68. errors.add(:base, "if provided, headers must be a hash")
  69. end
  70. validate_web_request_options!
  71. end
  72. def receive(incoming_events)
  73. incoming_events.each do |event|
  74. outgoing = interpolated(event)['payload'].presence || {}
  75. if boolify(interpolated['no_merge'])
  76. handle outgoing, event.payload
  77. else
  78. handle outgoing.merge(event.payload), event.payload
  79. end
  80. end
  81. end
  82. def check
  83. handle interpolated['payload'].presence || {}
  84. end
  85. private
  86. def handle(data, payload = {})
  87. url = interpolated(payload)[:post_url]
  88. headers = headers()
  89. case method
  90. when 'get', 'delete'
  91. params, body = data, nil
  92. when 'post', 'put', 'patch'
  93. params = nil
  94. case interpolated(payload)['content_type']
  95. when 'json'
  96. headers['Content-Type'] = 'application/json; charset=utf-8'
  97. body = data.to_json
  98. when 'xml'
  99. headers['Content-Type'] = 'text/xml; charset=utf-8'
  100. body = data.to_xml(root: (interpolated(payload)[:xml_root] || 'post'))
  101. else
  102. body = data
  103. end
  104. else
  105. error "Invalid method '#{method}'"
  106. end
  107. response = faraday.run_request(method.to_sym, url, body, headers) { |request|
  108. request.params.update(params) if params
  109. }
  110. if boolify(interpolated['emit_events'])
  111. create_event payload: { body: response.body, headers: response.headers, status: response.status }
  112. end
  113. end
  114. end
  115. end