]> git.basschouten.com Git - openhab-addons.git/commitdiff
[http] add POST/PUT support for state requests (#10022)
authorJ-N-K <J-N-K@users.noreply.github.com>
Wed, 3 Feb 2021 10:28:44 +0000 (11:28 +0100)
committerGitHub <noreply@github.com>
Wed, 3 Feb 2021 10:28:44 +0000 (11:28 +0100)
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
bundles/org.openhab.binding.http/README.md
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpChannelConfig.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpThingConfig.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RateLimitedHttpClient.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RefreshingUrlCache.java
bundles/org.openhab.binding.http/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.http/src/main/resources/OH-INF/thing/thing-types.xml

index 59dd0c5205fc3ce5080a3432bef6fcfcfd8fcb5c..998e394d49ade30b1a1deb893a23ba07b1fe1d36 100644 (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
 
 ```
index b18e3c119f2691e2f0ca16e51fd949356283793e..ba5c3c0691bd4a3a66b7fcbfd2393ada6c0c965e 100644 (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);
         }
     }
index 2effb1a0b16bbcd119ca2a18bf951175dc8c40f1..8803e986f251887ce61d4e554f5cece00a0ef6dd 100644 (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;
 
index 5de92016c3c812b1338ca9042aae2fab9a64ee47..84b9b5c6268c1c8aa684246ce23ae994c6655869 100644 (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;
 
index 6e81888e40ab299265d0dd3f17e90a2020879a92..000703b304d9d10c053acc3beff13c3e9faa27d2 100644 (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);
+        }
     }
 }
index ceb7d342321c9e276ed17aafc5d6715a2cdc9fd3..4ef2cb504ed80fc2803ed127ab7d82f26d00906a 100644 (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 -> {
index 0ee64f42ed96183ee3bbae2c00323416a7a0b133..456cebfd7721a9c14553c2145cc0969e73157392 100644 (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>
                        <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>
        </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>
                        <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>
        </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>
                        <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>
        </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>
                        <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>
                        <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>
                        <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>
        </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>
                        <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="playValue" type="text">
                        <label>Play Value</label>
                        <description>The value that represents PLAY</description>
        </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>
                        <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>
        </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>
                        <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>
index cf40b92a4f0a38398bc91ec19786fbf1da94db69..ff7917a97b24f93f89bcd1ec978ecda090c24d92 100644 (file)
                                <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>