[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 80 deletions

View File

@ -19,7 +19,8 @@ It can be extended with different channels.
| `username` | yes | - | Username for authentication (advanced parameter). |
| `password` | yes | - | Password for authentication (advanced parameter). |
| `authMode` | no | BASIC | Authentication mode, `BASIC`, `BASIC_PREEMPTIVE` or `DIGEST` (advanced parameter). |
| `commandMethod` | no | GET | Method used for sending commands `GET`, `PUT`, `POST`. |
| `stateMethod` | no | GET | Method used for requesting the state: `GET`, `PUT`, `POST`. |
| `commandMethod` | no | GET | Method used for sending commands: `GET`, `PUT`, `POST`. |
| `contentType` | yes | - | MIME content-type of the command requests. Only used for `PUT` and `POST`. |
| `encoding` | yes | - | Encoding to be used if no encoding is found in responses (advanced parameter). |
| `headers` | yes | - | Additional headers that are sent along with the request. Format is "header=value".|
@ -51,6 +52,7 @@ The `image` channel-type supports `stateExtension` only.
| `commandExtension` | yes | - | Appended to the `baseURL` for sending commands. If empty, same as `stateExtension`. |
| `stateTransformation ` | yes | - | One or more transformation applied to received values before updating channel. |
| `commandTransformation` | yes | - | One or more transformation applied to channel value before sending to a remote. |
| `stateContent` | yes | - | Content for state requests (if method is `PUT` or `POST`) |
| `mode` | no | `READWRITE` | Mode this channel is allowed to operate. `READONLY` means receive state, `WRITEONLY` means send commands. |
Transformations need to be specified in the same format as
@ -155,7 +157,7 @@ The URL is used as format string and two parameters are added:
- the transformed command (referenced as `%2$`)
After the parameter reference the format needs to be appended.
See the link above for more information about the available format parameters (e.g. to use the string representation, you need to append `s` to the reference).
See the link above for more information about the available format parameters (e.g. to use the string representation, you need to append `s` to the reference, for a timestamp `t`).
When sending an OFF command on 2020-07-06, the URL
```

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>