Skip to content

Commit 88ac63a

Browse files
authored
[energidataservice] Consider Retry-After header (openhab#20899)
* Consider Retry-After header Resolves openhab#20897 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
1 parent a5a1598 commit 88ac63a

4 files changed

Lines changed: 99 additions & 12 deletions

File tree

bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/ApiController.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import static org.openhab.binding.energidataservice.internal.EnergiDataServiceBindingConstants.*;
1616

17+
import java.time.Duration;
1718
import java.time.Instant;
1819
import java.time.LocalDateTime;
1920
import java.time.format.DateTimeFormatter;
@@ -53,6 +54,7 @@
5354
import org.openhab.binding.energidataservice.internal.api.serialization.InstantDeserializer;
5455
import org.openhab.binding.energidataservice.internal.api.serialization.LocalDateTimeDeserializer;
5556
import org.openhab.binding.energidataservice.internal.exception.DataServiceException;
57+
import org.openhab.binding.energidataservice.internal.exception.DataServiceRateLimitException;
5658
import org.openhab.core.i18n.TimeZoneProvider;
5759
import org.osgi.framework.FrameworkUtil;
5860
import org.slf4j.Logger;
@@ -198,10 +200,34 @@ private String sendRequest(Request request, Map<String, String> properties)
198200
updatePropertiesFromResponse(response, properties);
199201

200202
int status = response.getStatus();
203+
String responseContent = response.getContentAsString();
204+
201205
if (!HttpStatus.isSuccess(status)) {
206+
if (logger.isTraceEnabled()) {
207+
logger.trace("Request failed with HTTP error {}: {}", status, responseContent);
208+
logger.trace("Response headers: {}", response.getHeaders());
209+
}
210+
211+
if (status == HttpStatus.TOO_MANY_REQUESTS_429) {
212+
String retryAfter = response.getHeaders().get("Retry-After");
213+
if (retryAfter != null) {
214+
try {
215+
int retryAfterSeconds = Integer.parseInt(retryAfter);
216+
if (retryAfterSeconds < 0) {
217+
logger.debug("Invalid Retry-After header value: '{}'", retryAfter);
218+
throw new DataServiceRateLimitException("Rate limit is exceeded");
219+
}
220+
throw new DataServiceRateLimitException(
221+
"Rate limit is exceeded. Retrying after " + retryAfter + " seconds.",
222+
Duration.ofSeconds(retryAfterSeconds));
223+
} catch (NumberFormatException e) {
224+
logger.debug("Invalid Retry-After header value: '{}'", retryAfter);
225+
throw new DataServiceRateLimitException("Rate limit is exceeded", e);
226+
}
227+
}
228+
}
202229
throw new DataServiceException("The request failed with HTTP error " + status, status);
203230
}
204-
String responseContent = response.getContentAsString();
205231
if (responseContent.isEmpty()) {
206232
throw new DataServiceException("Empty response");
207233
}

bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/console/EnergiDataServiceCommandExtension.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import java.time.Instant;
1717
import java.time.LocalDate;
1818
import java.time.format.DateTimeParseException;
19-
import java.util.Arrays;
2019
import java.util.List;
2120
import java.util.stream.Stream;
2221

@@ -161,7 +160,7 @@ private class ParsedUpdateParameters {
161160

162161
@Override
163162
public List<String> getUsages() {
164-
return Arrays.asList(buildCommandUsage(SUBCMD_UPDATE + " ["
163+
return List.of(buildCommandUsage(SUBCMD_UPDATE + " ["
165164
+ String.join("|", Stream.of(PriceComponent.values()).map(PriceComponent::toString).toList())
166165
+ "] <StartDate> [<EndDate>]", "Update time series in requested period"));
167166
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.energidataservice.internal.exception;
14+
15+
import java.time.Duration;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.eclipse.jetty.http.HttpStatus;
19+
20+
/**
21+
* {@link DataServiceRateLimitException} is a specialized Energi Data Service exception
22+
* thrown in case of exceeding the API rate limit. It contains information about how long to
23+
* wait before retrying the request.
24+
*
25+
* @see <a href="https://www.energidataservice.dk/guides/api-guides">Energi Data Service API documentation on rate
26+
* limiting</a>
27+
*
28+
* @author Jacob Laursen - Initial contribution
29+
*/
30+
@NonNullByDefault
31+
public class DataServiceRateLimitException extends DataServiceException {
32+
33+
private static final Duration DEFAULT_RETRY_AFTER = Duration.ofMinutes(30);
34+
35+
private static final long serialVersionUID = 1L;
36+
37+
private final Duration retryAfter;
38+
39+
public DataServiceRateLimitException(String message) {
40+
this(message, DEFAULT_RETRY_AFTER);
41+
}
42+
43+
public DataServiceRateLimitException(String message, Throwable cause) {
44+
super(message, HttpStatus.TOO_MANY_REQUESTS_429);
45+
initCause(cause);
46+
this.retryAfter = DEFAULT_RETRY_AFTER;
47+
}
48+
49+
public DataServiceRateLimitException(String message, Duration retryAfter) {
50+
super(message, HttpStatus.TOO_MANY_REQUESTS_429);
51+
this.retryAfter = retryAfter;
52+
}
53+
54+
public Duration getRetryAfter() {
55+
return retryAfter;
56+
}
57+
}

bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/retry/RetryPolicyFactory.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.eclipse.jdt.annotation.NonNullByDefault;
2121
import org.eclipse.jetty.http.HttpStatus;
2222
import org.openhab.binding.energidataservice.internal.exception.DataServiceException;
23+
import org.openhab.binding.energidataservice.internal.exception.DataServiceRateLimitException;
2324
import org.openhab.binding.energidataservice.internal.retry.strategy.ExponentialBackoff;
2425
import org.openhab.binding.energidataservice.internal.retry.strategy.FixedTime;
2526
import org.openhab.binding.energidataservice.internal.retry.strategy.Linear;
@@ -40,16 +41,20 @@ public class RetryPolicyFactory {
4041
* @return retry strategy
4142
*/
4243
public static RetryStrategy fromThrowable(Throwable e) {
43-
if (e instanceof DataServiceException dse) {
44-
switch (dse.getHttpStatus()) {
45-
case HttpStatus.TOO_MANY_REQUESTS_429:
46-
return new ExponentialBackoff().withMinimum(Duration.ofMinutes(30));
47-
default:
48-
return new ExponentialBackoff().withMinimum(Duration.ofMinutes(1)).withJitter(0.2);
49-
}
50-
}
44+
ExponentialBackoff strategy = switch (e) {
45+
case DataServiceRateLimitException rle ->
46+
new ExponentialBackoff()
47+
.withMinimum(rle.getRetryAfter());
5148

52-
return new ExponentialBackoff().withMinimum(Duration.ofMinutes(1)).withJitter(0.2);
49+
case DataServiceException dse when dse.getHttpStatus() == HttpStatus.TOO_MANY_REQUESTS_429 ->
50+
new ExponentialBackoff()
51+
.withMinimum(Duration.ofMinutes(30));
52+
53+
default -> new ExponentialBackoff()
54+
.withMinimum(Duration.ofMinutes(1));
55+
};
56+
57+
return strategy.withJitter(0.2);
5358
}
5459

5560
/**

0 commit comments

Comments
 (0)