diff --git a/bundles/org.openhab.binding.http/README.md b/bundles/org.openhab.binding.http/README.md index 59dd0c520..998e394d4 100644 --- a/bundles/org.openhab.binding.http/README.md +++ b/bundles/org.openhab.binding.http/README.md @@ -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 ``` diff --git a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java index b18e3c119..ba5c3c069 100644 --- a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java +++ b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java @@ -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); } } diff --git a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpChannelConfig.java b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpChannelConfig.java index 2effb1a0b..8803e986f 100644 --- a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpChannelConfig.java +++ b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpChannelConfig.java @@ -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; diff --git a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpThingConfig.java b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpThingConfig.java index 5de92016c..84b9b5c62 100644 --- a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpThingConfig.java +++ b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpThingConfig.java @@ -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; diff --git a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RateLimitedHttpClient.java b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RateLimitedHttpClient.java index 6e81888e4..000703b30 100644 --- a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RateLimitedHttpClient.java +++ b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RateLimitedHttpClient.java @@ -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 newRequest(URI finalUrl) { + public CompletableFuture 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 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 future; + private URI finalUrl; + private HttpMethod method; + private String content; + private CompletableFuture future; - public RequestQueueEntry(URI finalUrl, CompletableFuture future) { + public RequestQueueEntry(URI finalUrl, HttpMethod method, String content, CompletableFuture 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); + } } } diff --git a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RefreshingUrlCache.java b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RefreshingUrlCache.java index ceb7d3423..4ef2cb504 100644 --- a/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RefreshingUrlCache.java +++ b/bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RefreshingUrlCache.java @@ -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> consumers = ConcurrentHashMap.newKeySet(); private final List 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 -> { diff --git a/bundles/org.openhab.binding.http/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.http/src/main/resources/OH-INF/config/config.xml index 0ee64f42e..456cebfd7 100644 --- a/bundles/org.openhab.binding.http/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.http/src/main/resources/OH-INF/config/config.xml @@ -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"> + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -15,13 +23,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true @@ -37,6 +42,14 @@ + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -47,13 +60,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true @@ -101,6 +111,14 @@ + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -111,13 +129,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true @@ -141,6 +156,14 @@ + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -151,13 +174,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true @@ -200,9 +220,22 @@ This value is added to the base URL configured in the thing for retrieving values. true + + + Content for state request (only used if method is POST/PUT) + true + + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -213,13 +246,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true @@ -240,6 +270,14 @@ + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -250,15 +288,11 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. + + + Content for state request (only used if method is POST/PUT) + true - - - Transformation pattern used when sending values. - - The value that represents PLAY @@ -297,6 +331,14 @@ + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -307,13 +349,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true @@ -345,6 +384,14 @@ + + + Transformation pattern used when receiving values. + + + + Transformation pattern used when sending values. + This value is added to the base URL configured in the thing for retrieving values. @@ -355,13 +402,10 @@ This value is added to the base URL configured in the thing for sending values. true - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending values. + + + Content for state request (only used if method is POST/PUT) + true diff --git a/bundles/org.openhab.binding.http/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.http/src/main/resources/OH-INF/thing/thing-types.xml index cf40b92a4..ff7917a97 100644 --- a/bundles/org.openhab.binding.http/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.http/src/main/resources/OH-INF/thing/thing-types.xml @@ -59,6 +59,18 @@ true true + + + HTTP method (GET,POST, PUT) for retrieving a status. + + + + + + true + GET + true + HTTP method (GET,POST, PUT) for sending commands.