Merge pull request #5304 from msqr/master

Add option for HTTP client rule node to not create any message body
This commit is contained in:
Andrew Shvayka 2021-11-03 16:26:51 +02:00 committed by GitHub
commit 71e098c8c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 227 additions and 1 deletions

View File

@ -182,7 +182,8 @@ public class TbHttpClient {
HttpMethod method = HttpMethod.valueOf(config.getRequestMethod());
HttpEntity<String> entity;
if(HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) ||
HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method)) {
HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method) ||
config.isIgnoreRequestBody()) {
entity = new HttpEntity<>(headers);
} else {
entity = new HttpEntity<>(msg.getData(), headers);

View File

@ -47,6 +47,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
private String proxyPassword;
private String proxyScheme;
private ClientCredentials credentials;
private boolean ignoreRequestBody;
@Override
public TbRestApiCallNodeConfiguration defaultConfiguration() {
@ -61,6 +62,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
configuration.setTrimQueue(false);
configuration.setEnableProxy(false);
configuration.setCredentials(new AnonymousCredentials());
configuration.setIgnoreRequestBody(false);
return configuration;
}

View File

@ -0,0 +1,223 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.rest;
import static org.junit.Assert.*;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.config.SocketConfig;
import org.apache.http.impl.bootstrap.HttpServer;
import org.apache.http.impl.bootstrap.ServerBootstrap;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbEmail;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(MockitoJUnitRunner.class)
public class TbRestApiCallNodeTest {
private TbRestApiCallNode restNode;
@Mock
private TbContext ctx;
private EntityId originator = new DeviceId(Uuids.timeBased());
private TbMsgMetaData metaData = new TbMsgMetaData();
private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
private HttpServer server;
public void setupServer(String pattern, HttpRequestHandler handler) throws IOException {
SocketConfig config = SocketConfig.custom().setSoReuseAddress(true).setTcpNoDelay(true).build();
server = ServerBootstrap.bootstrap()
.setSocketConfig(config)
.registerHandler(pattern, handler)
.create();
server.start();
}
private void initWithConfig(TbRestApiCallNodeConfiguration config) {
try {
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
restNode = new TbRestApiCallNode();
restNode.init(ctx, nodeConfiguration);
} catch (TbNodeException ex) {
throw new IllegalStateException(ex);
}
}
@After
public void teardown() {
server.stop();
}
@Test
public void deleteRequestWithoutBody() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final String path = "/path/to/delete";
setupServer("*", new HttpRequestHandler() {
@Override
public void handle(HttpRequest request, HttpResponse response, HttpContext context)
throws HttpException, IOException {
try {
assertEquals("Request path matches", request.getRequestLine().getUri(), path);
assertFalse("Content-Type not included", request.containsHeader("Content-Type"));
assertTrue("Custom header included", request.containsHeader("Foo"));
assertEquals("Custom header value", "Bar", request.getFirstHeader("Foo").getValue());
response.setStatusCode(200);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// ignore
} finally {
latch.countDown();
}
}
}).start();
} catch ( Exception e ) {
System.out.println("Exception handling request: " + e.toString());
e.printStackTrace();
latch.countDown();
}
}
});
TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration();
config.setRequestMethod("DELETE");
config.setHeaders(Collections.singletonMap("Foo", "Bar"));
config.setIgnoreRequestBody(true);
config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path));
initWithConfig(config);
TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
restNode.onMsg(ctx, msg);
assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS));
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
assertEquals("USER", typeCaptor.getValue());
assertEquals(originator, originatorCaptor.getValue());
assertNotSame(metaData, metadataCaptor.getValue());
assertEquals("{}", dataCaptor.getValue());
}
@Test
public void deleteRequestWithBody() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final String path = "/path/to/delete";
setupServer("*", new HttpRequestHandler() {
@Override
public void handle(HttpRequest request, HttpResponse response, HttpContext context)
throws HttpException, IOException {
try {
assertEquals("Request path matches", path, request.getRequestLine().getUri());
assertTrue("Content-Type included", request.containsHeader("Content-Type"));
assertEquals("Content-Type value", "text/plain;charset=ISO-8859-1",
request.getFirstHeader("Content-Type").getValue());
assertTrue("Content-Length included", request.containsHeader("Content-Length"));
assertEquals("Content-Length value", "2",
request.getFirstHeader("Content-Length").getValue());
assertTrue("Custom header included", request.containsHeader("Foo"));
assertEquals("Custom header value", "Bar", request.getFirstHeader("Foo").getValue());
response.setStatusCode(200);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// ignore
} finally {
latch.countDown();
}
}
}).start();
} catch ( Exception e ) {
System.out.println("Exception handling request: " + e.toString());
e.printStackTrace();
latch.countDown();
}
}
});
TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration();
config.setRequestMethod("DELETE");
config.setHeaders(Collections.singletonMap("Foo", "Bar"));
config.setIgnoreRequestBody(false);
config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path));
initWithConfig(config);
TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
restNode.onMsg(ctx, msg);
assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS));
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
assertEquals("USER", typeCaptor.getValue());
assertEquals(originator, originatorCaptor.getValue());
assertNotSame(metaData, metadataCaptor.getValue());
assertEquals("{}", dataCaptor.getValue());
}
}