Commit 71e098c8c41b2c0c2bcf7b00b5bfdfa50da40e59
Committed by
GitHub
Merge pull request #5304 from msqr/master
Add option for HTTP client rule node to not create any message body
Showing
3 changed files
with
227 additions
and
1 deletions
... | ... | @@ -182,7 +182,8 @@ public class TbHttpClient { |
182 | 182 | HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); |
183 | 183 | HttpEntity<String> entity; |
184 | 184 | if(HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) || |
185 | - HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method)) { | |
185 | + HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method) || | |
186 | + config.isIgnoreRequestBody()) { | |
186 | 187 | entity = new HttpEntity<>(headers); |
187 | 188 | } else { |
188 | 189 | entity = new HttpEntity<>(msg.getData(), headers); | ... | ... |
... | ... | @@ -47,6 +47,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA |
47 | 47 | private String proxyPassword; |
48 | 48 | private String proxyScheme; |
49 | 49 | private ClientCredentials credentials; |
50 | + private boolean ignoreRequestBody; | |
50 | 51 | |
51 | 52 | @Override |
52 | 53 | public TbRestApiCallNodeConfiguration defaultConfiguration() { |
... | ... | @@ -61,6 +62,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA |
61 | 62 | configuration.setTrimQueue(false); |
62 | 63 | configuration.setEnableProxy(false); |
63 | 64 | configuration.setCredentials(new AnonymousCredentials()); |
65 | + configuration.setIgnoreRequestBody(false); | |
64 | 66 | return configuration; |
65 | 67 | } |
66 | 68 | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.rest; | |
17 | + | |
18 | +import static org.junit.Assert.*; | |
19 | +import static org.mockito.Mockito.verify; | |
20 | + | |
21 | +import java.io.IOException; | |
22 | +import java.util.Collections; | |
23 | +import java.util.concurrent.CountDownLatch; | |
24 | +import java.util.concurrent.TimeUnit; | |
25 | + | |
26 | +import org.apache.http.Header; | |
27 | +import org.apache.http.HttpException; | |
28 | +import org.apache.http.HttpRequest; | |
29 | +import org.apache.http.HttpResponse; | |
30 | +import org.apache.http.config.SocketConfig; | |
31 | +import org.apache.http.impl.bootstrap.HttpServer; | |
32 | +import org.apache.http.impl.bootstrap.ServerBootstrap; | |
33 | +import org.apache.http.protocol.HttpContext; | |
34 | +import org.apache.http.protocol.HttpRequestHandler; | |
35 | +import org.junit.After; | |
36 | +import org.junit.Test; | |
37 | +import org.junit.runner.RunWith; | |
38 | +import org.mockito.ArgumentCaptor; | |
39 | +import org.mockito.Mock; | |
40 | +import org.mockito.junit.MockitoJUnitRunner; | |
41 | +import org.thingsboard.rule.engine.api.TbContext; | |
42 | +import org.thingsboard.rule.engine.api.TbEmail; | |
43 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
44 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
45 | +import org.thingsboard.server.common.data.id.DeviceId; | |
46 | +import org.thingsboard.server.common.data.id.EntityId; | |
47 | +import org.thingsboard.server.common.data.id.RuleChainId; | |
48 | +import org.thingsboard.server.common.data.id.RuleNodeId; | |
49 | +import org.thingsboard.server.common.msg.TbMsg; | |
50 | +import org.thingsboard.server.common.msg.TbMsgDataType; | |
51 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | |
52 | + | |
53 | +import com.datastax.oss.driver.api.core.uuid.Uuids; | |
54 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
55 | + | |
56 | +@RunWith(MockitoJUnitRunner.class) | |
57 | +public class TbRestApiCallNodeTest { | |
58 | + | |
59 | + private TbRestApiCallNode restNode; | |
60 | + | |
61 | + @Mock | |
62 | + private TbContext ctx; | |
63 | + | |
64 | + private EntityId originator = new DeviceId(Uuids.timeBased()); | |
65 | + private TbMsgMetaData metaData = new TbMsgMetaData(); | |
66 | + | |
67 | + private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); | |
68 | + private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); | |
69 | + | |
70 | + private HttpServer server; | |
71 | + | |
72 | + public void setupServer(String pattern, HttpRequestHandler handler) throws IOException { | |
73 | + SocketConfig config = SocketConfig.custom().setSoReuseAddress(true).setTcpNoDelay(true).build(); | |
74 | + server = ServerBootstrap.bootstrap() | |
75 | + .setSocketConfig(config) | |
76 | + .registerHandler(pattern, handler) | |
77 | + .create(); | |
78 | + server.start(); | |
79 | + } | |
80 | + | |
81 | + private void initWithConfig(TbRestApiCallNodeConfiguration config) { | |
82 | + try { | |
83 | + ObjectMapper mapper = new ObjectMapper(); | |
84 | + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); | |
85 | + restNode = new TbRestApiCallNode(); | |
86 | + restNode.init(ctx, nodeConfiguration); | |
87 | + } catch (TbNodeException ex) { | |
88 | + throw new IllegalStateException(ex); | |
89 | + } | |
90 | + } | |
91 | + | |
92 | + @After | |
93 | + public void teardown() { | |
94 | + server.stop(); | |
95 | + } | |
96 | + | |
97 | + @Test | |
98 | + public void deleteRequestWithoutBody() throws IOException, InterruptedException { | |
99 | + final CountDownLatch latch = new CountDownLatch(1); | |
100 | + final String path = "/path/to/delete"; | |
101 | + setupServer("*", new HttpRequestHandler() { | |
102 | + | |
103 | + @Override | |
104 | + public void handle(HttpRequest request, HttpResponse response, HttpContext context) | |
105 | + throws HttpException, IOException { | |
106 | + try { | |
107 | + assertEquals("Request path matches", request.getRequestLine().getUri(), path); | |
108 | + assertFalse("Content-Type not included", request.containsHeader("Content-Type")); | |
109 | + assertTrue("Custom header included", request.containsHeader("Foo")); | |
110 | + assertEquals("Custom header value", "Bar", request.getFirstHeader("Foo").getValue()); | |
111 | + response.setStatusCode(200); | |
112 | + new Thread(new Runnable() { | |
113 | + @Override | |
114 | + public void run() { | |
115 | + try { | |
116 | + Thread.sleep(1000L); | |
117 | + } catch (InterruptedException e) { | |
118 | + // ignore | |
119 | + } finally { | |
120 | + latch.countDown(); | |
121 | + } | |
122 | + } | |
123 | + }).start(); | |
124 | + } catch ( Exception e ) { | |
125 | + System.out.println("Exception handling request: " + e.toString()); | |
126 | + e.printStackTrace(); | |
127 | + latch.countDown(); | |
128 | + } | |
129 | + } | |
130 | + }); | |
131 | + | |
132 | + TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); | |
133 | + config.setRequestMethod("DELETE"); | |
134 | + config.setHeaders(Collections.singletonMap("Foo", "Bar")); | |
135 | + config.setIgnoreRequestBody(true); | |
136 | + config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); | |
137 | + initWithConfig(config); | |
138 | + | |
139 | + TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); | |
140 | + restNode.onMsg(ctx, msg); | |
141 | + | |
142 | + assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); | |
143 | + | |
144 | + ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); | |
145 | + ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | |
146 | + ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); | |
147 | + ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); | |
148 | + ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); | |
149 | + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); | |
150 | + | |
151 | + | |
152 | + assertEquals("USER", typeCaptor.getValue()); | |
153 | + assertEquals(originator, originatorCaptor.getValue()); | |
154 | + assertNotSame(metaData, metadataCaptor.getValue()); | |
155 | + assertEquals("{}", dataCaptor.getValue()); | |
156 | + } | |
157 | + | |
158 | + @Test | |
159 | + public void deleteRequestWithBody() throws IOException, InterruptedException { | |
160 | + final CountDownLatch latch = new CountDownLatch(1); | |
161 | + final String path = "/path/to/delete"; | |
162 | + setupServer("*", new HttpRequestHandler() { | |
163 | + | |
164 | + @Override | |
165 | + public void handle(HttpRequest request, HttpResponse response, HttpContext context) | |
166 | + throws HttpException, IOException { | |
167 | + try { | |
168 | + assertEquals("Request path matches", path, request.getRequestLine().getUri()); | |
169 | + assertTrue("Content-Type included", request.containsHeader("Content-Type")); | |
170 | + assertEquals("Content-Type value", "text/plain;charset=ISO-8859-1", | |
171 | + request.getFirstHeader("Content-Type").getValue()); | |
172 | + assertTrue("Content-Length included", request.containsHeader("Content-Length")); | |
173 | + assertEquals("Content-Length value", "2", | |
174 | + request.getFirstHeader("Content-Length").getValue()); | |
175 | + assertTrue("Custom header included", request.containsHeader("Foo")); | |
176 | + assertEquals("Custom header value", "Bar", request.getFirstHeader("Foo").getValue()); | |
177 | + response.setStatusCode(200); | |
178 | + new Thread(new Runnable() { | |
179 | + @Override | |
180 | + public void run() { | |
181 | + try { | |
182 | + Thread.sleep(1000L); | |
183 | + } catch (InterruptedException e) { | |
184 | + // ignore | |
185 | + } finally { | |
186 | + latch.countDown(); | |
187 | + } | |
188 | + } | |
189 | + }).start(); | |
190 | + } catch ( Exception e ) { | |
191 | + System.out.println("Exception handling request: " + e.toString()); | |
192 | + e.printStackTrace(); | |
193 | + latch.countDown(); | |
194 | + } | |
195 | + } | |
196 | + }); | |
197 | + | |
198 | + TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); | |
199 | + config.setRequestMethod("DELETE"); | |
200 | + config.setHeaders(Collections.singletonMap("Foo", "Bar")); | |
201 | + config.setIgnoreRequestBody(false); | |
202 | + config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); | |
203 | + initWithConfig(config); | |
204 | + | |
205 | + TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); | |
206 | + restNode.onMsg(ctx, msg); | |
207 | + | |
208 | + assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); | |
209 | + | |
210 | + ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); | |
211 | + ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | |
212 | + ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); | |
213 | + ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); | |
214 | + ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); | |
215 | + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); | |
216 | + | |
217 | + assertEquals("USER", typeCaptor.getValue()); | |
218 | + assertEquals(originator, originatorCaptor.getValue()); | |
219 | + assertNotSame(metaData, metadataCaptor.getValue()); | |
220 | + assertEquals("{}", dataCaptor.getValue()); | |
221 | + } | |
222 | + | |
223 | +} | ... | ... |