[http] add POST/PUT support for state requests (#10022)

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
This commit is contained in:
J-N-K
2021-02-03 11:28:44 +01:00
committed by GitHub
parent 4088241033
commit 899d8d2e9f
8 changed files with 169 additions and 80 deletions

View File

@@ -95,9 +95,9 @@ public class HttpThingHandler extends BaseThingHandler {
}
if (command instanceof RefreshType) {
String stateUrl = channelUrls.get(channelUID);
if (stateUrl != null) {
RefreshingUrlCache refreshingUrlCache = urlHandlers.get(stateUrl);
String key = channelUrls.get(channelUID);
if (key != null) {
RefreshingUrlCache refreshingUrlCache = urlHandlers.get(key);
if (refreshingUrlCache != null) {
try {
refreshingUrlCache.get().ifPresent(itemValueConverter::process);
@@ -272,17 +272,17 @@ public class HttpThingHandler extends BaseThingHandler {
channels.put(channelUID, itemValueConverter);
if (channelConfig.mode != HttpChannelMode.WRITEONLY) {
channelUrls.put(channelUID, stateUrl);
urlHandlers
.computeIfAbsent(stateUrl,
url -> new RefreshingUrlCache(scheduler, rateLimitedHttpClient, url, config))
.addConsumer(itemValueConverter::process);
// we need a key consisting of stateContent and URL, only if both are equal, we can use the same cache
String key = channelConfig.stateContent + "$" + stateUrl;
channelUrls.put(channelUID, key);
urlHandlers.computeIfAbsent(key, k -> new RefreshingUrlCache(scheduler, rateLimitedHttpClient, stateUrl,
config, channelConfig.stateContent)).addConsumer(itemValueConverter::process);
}
StateDescription stateDescription = StateDescriptionFragmentBuilder.create()
.withReadOnly(channelConfig.mode == HttpChannelMode.READONLY).build().toStateDescription();
if (stateDescription != null) {
// if the state description is not available, we don'tneed to add it
// if the state description is not available, we don't need to add it
httpDynamicStateDescriptionProvider.setDescription(channelUID, stateDescription);
}
}

View File

@@ -45,6 +45,7 @@ public class HttpChannelConfig {
public @Nullable String commandExtension;
public @Nullable String stateTransformation;
public @Nullable String commandTransformation;
public String stateContent = "";
public HttpChannelMode mode = HttpChannelMode.READWRITE;

View File

@@ -34,6 +34,8 @@ public class HttpThingConfig {
public String password = "";
public HttpAuthMode authMode = HttpAuthMode.BASIC;
public HttpMethod stateMethod = HttpMethod.GET;
public HttpMethod commandMethod = HttpMethod.GET;
public int bufferSize = 2048;

View File

@@ -20,6 +20,8 @@ import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
/**
* The {@link RateLimitedHttpClient} is a wrapper for a Jetty HTTP client that limits the number of requests by delaying
@@ -79,16 +81,20 @@ public class RateLimitedHttpClient {
* Create a new request to the given URL respecting rate-limits
*
* @param finalUrl the request URL
* @param method http request method GET/PUT/POST
* @param content the content (if method PUT/POST)
* @return a CompletableFuture that completes with the request
*/
public CompletableFuture<Request> newRequest(URI finalUrl) {
public CompletableFuture<Request> newRequest(URI finalUrl, HttpMethod method, String content) {
// if no delay is set, return a completed CompletableFuture
if (delay == 0) {
return CompletableFuture.completedFuture(httpClient.newRequest(finalUrl));
}
CompletableFuture<Request> future = new CompletableFuture<>();
if (!requestQueue.offer(new RequestQueueEntry(finalUrl, future))) {
future.completeExceptionally(new RejectedExecutionException("Maximum queue size exceeded."));
RequestQueueEntry queueEntry = new RequestQueueEntry(finalUrl, method, content, future);
if (delay == 0) {
queueEntry.completeFuture(httpClient);
} else {
if (!requestQueue.offer(queueEntry)) {
future.completeExceptionally(new RejectedExecutionException("Maximum queue size exceeded."));
}
}
return future;
}
@@ -113,17 +119,34 @@ public class RateLimitedHttpClient {
private void processQueue() {
RequestQueueEntry queueEntry = requestQueue.poll();
if (queueEntry != null) {
queueEntry.future.complete(httpClient.newRequest(queueEntry.finalUrl));
queueEntry.completeFuture(httpClient);
}
}
private static class RequestQueueEntry {
public URI finalUrl;
public CompletableFuture<Request> future;
private URI finalUrl;
private HttpMethod method;
private String content;
private CompletableFuture<Request> future;
public RequestQueueEntry(URI finalUrl, CompletableFuture<Request> future) {
public RequestQueueEntry(URI finalUrl, HttpMethod method, String content, CompletableFuture<Request> future) {
this.finalUrl = finalUrl;
this.method = method;
this.content = content;
this.future = future;
}
/**
* complete the future with a request
*
* @param httpClient the client to create the request
*/
public void completeFuture(HttpClient httpClient) {
Request request = httpClient.newRequest(finalUrl).method(method);
if (method != HttpMethod.GET && !content.isEmpty()) {
request.content(new StringContentProvider(content));
}
future.complete(request);
}
}
}

View File

@@ -24,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.http.internal.Util;
import org.openhab.binding.http.internal.config.HttpThingConfig;
import org.slf4j.Logger;
@@ -46,17 +47,21 @@ public class RefreshingUrlCache {
private final @Nullable String fallbackEncoding;
private final Set<Consumer<Content>> consumers = ConcurrentHashMap.newKeySet();
private final List<String> headers;
private final HttpMethod httpMethod;
private final String httpContent;
private final ScheduledFuture<?> future;
private @Nullable Content lastContent;
public RefreshingUrlCache(ScheduledExecutorService executor, RateLimitedHttpClient httpClient, String url,
HttpThingConfig thingConfig) {
HttpThingConfig thingConfig, String httpContent) {
this.httpClient = httpClient;
this.url = url;
this.timeout = thingConfig.timeout;
this.bufferSize = thingConfig.bufferSize;
this.headers = thingConfig.headers;
this.httpMethod = thingConfig.stateMethod;
this.httpContent = httpContent;
fallbackEncoding = thingConfig.encoding;
future = executor.scheduleWithFixedDelay(this::refresh, 1, thingConfig.refresh, TimeUnit.SECONDS);
@@ -78,7 +83,7 @@ public class RefreshingUrlCache {
URI uri = Util.uriFromString(String.format(this.url, new Date()));
logger.trace("Requesting refresh (retry={}) from '{}' with timeout {}ms", isRetry, uri, timeout);
httpClient.newRequest(uri).thenAccept(request -> {
httpClient.newRequest(uri, httpMethod, httpContent).thenAccept(request -> {
request.timeout(timeout, TimeUnit.MILLISECONDS);
headers.forEach(header -> {

View File

@@ -5,6 +5,14 @@
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="channel-type:http:channel-config">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -15,13 +23,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="mode" type="text">
<label>Read/Write Mode</label>
@@ -37,6 +42,14 @@
</config-description>
<config-description uri="channel-type:http:channel-config-color">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -47,13 +60,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="onValue" type="text">
<label>On Value</label>
@@ -101,6 +111,14 @@
</config-description>
<config-description uri="channel-type:http:channel-config-contact">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -111,13 +129,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="openValue" type="text" required="true">
<label>Open Value</label>
@@ -141,6 +156,14 @@
</config-description>
<config-description uri="channel-type:http:channel-config-dimmer">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -151,13 +174,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="onValue" type="text">
<label>On Value</label>
@@ -200,9 +220,22 @@
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
</config-description>
<config-description uri="channel-type:http:channel-config-number">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -213,13 +246,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="mode" type="text">
<label>Read/Write Mode</label>
@@ -240,6 +270,14 @@
</config-description>
<config-description uri="channel-type:http:channel-config-player">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -250,15 +288,11 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="playValue" type="text">
<label>Play Value</label>
<description>The value that represents PLAY</description>
@@ -297,6 +331,14 @@
</config-description>
<config-description uri="channel-type:http:channel-config-rollershutter">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -307,13 +349,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="upValue" type="text">
<label>Up Value</label>
@@ -345,6 +384,14 @@
</config-description>
<config-description uri="channel-type:http:channel-config-switch">
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
</parameter>
<parameter name="stateExtension" type="text">
<label>State URL Extension</label>
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
@@ -355,13 +402,10 @@
<description>This value is added to the base URL configured in the thing for sending values.</description>
<advanced>true</advanced>
</parameter>
<parameter name="stateTransformation" type="text">
<label>State Transformation</label>
<description>Transformation pattern used when receiving values.</description>
</parameter>
<parameter name="commandTransformation" type="text">
<label>Command Transformation</label>
<description>Transformation pattern used when sending values.</description>
<parameter name="stateContent" type="text">
<label>State Content</label>
<description>Content for state request (only used if method is POST/PUT)</description>
<advanced>true</advanced>
</parameter>
<parameter name="onValue" type="text" required="true">
<label>On Value</label>

View File

@@ -59,6 +59,18 @@
<limitToOptions>true</limitToOptions>
<advanced>true</advanced>
</parameter>
<parameter name="stateMethod" type="text">
<label>State Method</label>
<description>HTTP method (GET,POST, PUT) for retrieving a status.</description>
<options>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
</options>
<limitToOptions>true</limitToOptions>
<default>GET</default>
<advanced>true</advanced>
</parameter>
<parameter name="commandMethod" type="text">
<label>Command Method</label>
<description>HTTP method (GET,POST, PUT) for sending commands.</description>