PostAgent: allow sending arbitrary string data

When `content_type` contains a MIME type and `payload` is not a Hash the string in `payload` will be send as the HTTP
body and `content_type` is send as the `Content-Type` header.

 #1361

Dominik Sander 8 years ago
parent
commit
eecd67c378
2 changed files with 56 additions and 4 deletions
  1. 25 4
      app/models/agents/post_agent.rb
  2. 31 0
      spec/models/agents/post_agent_spec.rb

+ 25 - 4
app/models/agents/post_agent.rb

@@ -2,6 +2,8 @@ module Agents
2 2
   class PostAgent < Agent
3 3
     include WebRequestConcern
4 4
 
5
+    MIME_RE = /\A\w+\/.+\z/
6
+
5 7
     can_dry_run!
6 8
     no_bulk_receive!
7 9
     default_schedule "never"
@@ -13,7 +15,13 @@ module Agents
13 15
 
14 16
       The `method` used can be any of `get`, `post`, `put`, `patch`, and `delete`.
15 17
 
16
-      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`.
18
+      By default, non-GETs will be sent with form encoding (`application/x-www-form-urlencoded`).
19
+
20
+      Change `content_type` to `json` to send JSON instead.
21
+
22
+      Change `content_type` to `xml` to send XML, where the name of the root element may be specified using `xml_root`, defaulting to `post`.
23
+
24
+      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`. When `payload` is a string `no_merge` has to be set to `true`.
17 25
 
18 26
       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
19 27
       will be attempted by this Agent, so the Event's "body" value will always be raw text.
@@ -49,7 +57,8 @@ module Agents
49 57
           'something' => 'the event contained {{ somekey }}'
50 58
         },
51 59
         'headers' => {},
52
-        'emit_events' => 'false'
60
+        'emit_events' => 'false',
61
+        'no_merge' => 'false'
53 62
       }
54 63
     end
55 64
 
@@ -66,10 +75,19 @@ module Agents
66 75
         errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
67 76
       end
68 77
 
69
-      if options['payload'].present? && !options['payload'].is_a?(Hash)
78
+      if options['payload'].present? && %w[get delete].include?(method) && !options['payload'].is_a?(Hash)
70 79
         errors.add(:base, "if provided, payload must be a hash")
71 80
       end
72 81
 
82
+      if options['payload'].present? && %w[post put patch].include?(method)
83
+        if !options['payload'].is_a?(Hash) && options['content_type'] !~ MIME_RE
84
+          errors.add(:base, "if provided, payload must be a hash")
85
+        end
86
+        if options['content_type'] =~ MIME_RE && options['payload'].is_a?(String) && boolify(options['no_merge']) != true
87
+          errors.add(:base, "when the payload is a string, `no_merge` has to be set to `true`")
88
+        end
89
+      end
90
+
73 91
       if options.has_key?('emit_events') && boolify(options['emit_events']).nil?
74 92
         errors.add(:base, "if provided, emit_events must be true or false")
75 93
       end
@@ -116,13 +134,16 @@ module Agents
116 134
       when 'post', 'put', 'patch'
117 135
         params = nil
118 136
 
119
-        case interpolated(payload)['content_type']
137
+        case (content_type = interpolated(payload)['content_type'])
120 138
         when 'json'
121 139
           headers['Content-Type'] = 'application/json; charset=utf-8'
122 140
           body = data.to_json
123 141
         when 'xml'
124 142
           headers['Content-Type'] = 'text/xml; charset=utf-8'
125 143
           body = data.to_xml(root: (interpolated(payload)[:xml_root] || 'post'))
144
+        when MIME_RE
145
+          headers['Content-Type'] = content_type
146
+          body = data.to_s
126 147
         else
127 148
           body = data
128 149
         end

+ 31 - 0
spec/models/agents/post_agent_spec.rb

@@ -46,6 +46,8 @@ describe Agents::PostAgent do
46 46
           req.data = ActiveSupport::JSON.decode(request.body)
47 47
         when 'text/xml'
48 48
           req.data = Hash.from_xml(request.body)
49
+        when Agents::PostAgent::MIME_RE
50
+          req.data = request.body
49 51
         else
50 52
           raise "unexpected Content-Type: #{content_type}"
51 53
         end
@@ -187,6 +189,16 @@ describe Agents::PostAgent do
187 189
       expect(@sent_requests[:get][0].data).to eq(@checker.options['payload'].to_query)
188 190
     end
189 191
 
192
+    it "sends options['payload'] as a string POST request when content-type continas a MIME type" do
193
+      @checker.options['payload'] = '<test>hello</test>'
194
+      @checker.options['content_type'] = 'application/xml'
195
+      expect {
196
+        @checker.check
197
+      }.to change { @sent_requests[:post].length }.by(1)
198
+
199
+      expect(@sent_requests[:post][0].data).to eq('<test>hello</test>')
200
+    end
201
+
190 202
     describe "emitting events" do
191 203
       context "when emit_events is not set to true" do
192 204
         it "does not emit events" do
@@ -304,6 +316,25 @@ describe Agents::PostAgent do
304 316
       expect(@checker).to be_valid
305 317
     end
306 318
 
319
+    it "should not validate payload as a hash if content_type includes a MIME type and method is not get or delete" do
320
+      @checker.options['no_merge'] = 'true'
321
+      @checker.options['content_type'] = 'text/xml'
322
+      @checker.options['payload'] = "test"
323
+      expect(@checker).to be_valid
324
+
325
+      @checker.options['method'] = 'get'
326
+      expect(@checker).not_to be_valid
327
+
328
+      @checker.options['method'] = 'delete'
329
+      expect(@checker).not_to be_valid
330
+    end
331
+
332
+    it "requires `no_merge` to be set to true when content_type contains a MIME type" do
333
+      @checker.options['content_type'] = 'text/xml'
334
+      @checker.options['payload'] = "test"
335
+      expect(@checker).not_to be_valid
336
+    end
337
+
307 338
     it "requires headers to be a hash, if present" do
308 339
       @checker.options['headers'] = [1,2,3]
309 340
       expect(@checker).not_to be_valid