mirror of
https://github.com/shred/acme4j.git
synced 2025-12-13 11:14:02 +08:00
Add new methods for status change busy waiting
This commit is contained in:
@@ -16,7 +16,9 @@ package org.shredzone.acme4j;
|
||||
import static java.util.stream.Collectors.toUnmodifiableList;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -32,7 +34,7 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Represents an authorization request at the ACME server.
|
||||
*/
|
||||
public class Authorization extends AcmeJsonResource {
|
||||
public class Authorization extends AcmeJsonResource implements PollableResource {
|
||||
private static final long serialVersionUID = -3116928998379417741L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Authorization.class);
|
||||
|
||||
@@ -60,6 +62,7 @@ public class Authorization extends AcmeJsonResource {
|
||||
* {@link Status#INVALID}, {@link Status#DEACTIVATED}, {@link Status#EXPIRED},
|
||||
* {@link Status#REVOKED}.
|
||||
*/
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return getJSON().get("status").asStatus();
|
||||
}
|
||||
@@ -151,6 +154,24 @@ public class Authorization extends AcmeJsonResource {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the authorization is completed.
|
||||
* <p>
|
||||
* Is is completed if it reaches either {@link Status#VALID} or
|
||||
* {@link Status#INVALID}.
|
||||
* <p>
|
||||
* This method is synchronous and blocks the current thread.
|
||||
*
|
||||
* @param timeout
|
||||
* Timeout until a terminal status must have been reached
|
||||
* @return Status that was reached
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public Status waitForCompletion(Duration timeout)
|
||||
throws AcmeException, InterruptedException {
|
||||
return waitForStatus(EnumSet.of(Status.VALID, Status.INVALID), timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently deactivates the {@link Authorization}.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -41,7 +42,7 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* A representation of a certificate order at the CA.
|
||||
*/
|
||||
public class Order extends AcmeJsonResource {
|
||||
public class Order extends AcmeJsonResource implements PollableResource {
|
||||
private static final long serialVersionUID = 5435808648658292177L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Order.class);
|
||||
|
||||
@@ -60,6 +61,7 @@ public class Order extends AcmeJsonResource {
|
||||
* {@link Status#PROCESSING}, {@link Status#VALID}, {@link Status#INVALID}.
|
||||
* If the server supports STAR, another possible value is {@link Status#CANCELED}.
|
||||
*/
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return getJSON().get("status").asStatus();
|
||||
}
|
||||
@@ -186,6 +188,8 @@ public class Order extends AcmeJsonResource {
|
||||
* @see #execute(KeyPair, Consumer)
|
||||
* @see #execute(PKCS10CertificationRequest)
|
||||
* @see #execute(byte[])
|
||||
* @see #waitUntilReady(Duration)
|
||||
* @see #waitForCompletion(Duration)
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public void execute(KeyPair domainKeyPair) throws AcmeException {
|
||||
@@ -208,6 +212,8 @@ public class Order extends AcmeJsonResource {
|
||||
* @see #execute(KeyPair)
|
||||
* @see #execute(PKCS10CertificationRequest)
|
||||
* @see #execute(byte[])
|
||||
* @see #waitUntilReady(Duration)
|
||||
* @see #waitForCompletion(Duration)
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public void execute(KeyPair domainKeyPair, Consumer<CSRBuilder> builderConsumer) throws AcmeException {
|
||||
@@ -235,6 +241,8 @@ public class Order extends AcmeJsonResource {
|
||||
* @see #execute(KeyPair)
|
||||
* @see #execute(KeyPair, Consumer)
|
||||
* @see #execute(byte[])
|
||||
* @see #waitUntilReady(Duration)
|
||||
* @see #waitForCompletion(Duration)
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public void execute(PKCS10CertificationRequest csr) throws AcmeException {
|
||||
@@ -256,6 +264,8 @@ public class Order extends AcmeJsonResource {
|
||||
* @param csr
|
||||
* Binary representation of a CSR containing the parameters for the
|
||||
* certificate being requested, in DER format
|
||||
* @see #waitUntilReady(Duration)
|
||||
* @see #waitForCompletion(Duration)
|
||||
*/
|
||||
public void execute(byte[] csr) throws AcmeException {
|
||||
LOG.debug("finalize");
|
||||
@@ -268,6 +278,43 @@ public class Order extends AcmeJsonResource {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the order is ready for finalization.
|
||||
* <p>
|
||||
* Is is ready if it reaches {@link Status#READY}. The method will also return if the
|
||||
* order already has another terminal state, which is either {@link Status#VALID} or
|
||||
* {@link Status#INVALID}.
|
||||
* <p>
|
||||
* This method is synchronous and blocks the current thread.
|
||||
*
|
||||
* @param timeout
|
||||
* Timeout until a terminal status must have been reached
|
||||
* @return Status that was reached
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public Status waitUntilReady(Duration timeout)
|
||||
throws AcmeException, InterruptedException {
|
||||
return waitForStatus(EnumSet.of(Status.READY, Status.VALID, Status.INVALID), timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the order finalization is completed.
|
||||
* <p>
|
||||
* Is is completed if it reaches either {@link Status#VALID} or
|
||||
* {@link Status#INVALID}.
|
||||
* <p>
|
||||
* This method is synchronous and blocks the current thread.
|
||||
*
|
||||
* @param timeout
|
||||
* Timeout until a terminal status must have been reached
|
||||
* @return Status that was reached
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public Status waitForCompletion(Duration timeout)
|
||||
throws AcmeException, InterruptedException {
|
||||
return waitForStatus(EnumSet.of(Status.VALID, Status.INVALID), timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this order is auto-renewing, according to the ACME STAR specifications.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2024 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j;
|
||||
|
||||
import static java.time.Instant.now;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
|
||||
/**
|
||||
* Marks an ACME Resource with a pollable status.
|
||||
* <p>
|
||||
* The resource provides a status, and a method for updating the internal cache to read
|
||||
* the current status from the server.
|
||||
*
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public interface PollableResource {
|
||||
|
||||
/**
|
||||
* Default delay between status polls if there is no Retry-After header.
|
||||
*/
|
||||
Duration DEFAULT_RETRY_AFTER = Duration.ofSeconds(3L);
|
||||
|
||||
/**
|
||||
* Returns the current status of the resource.
|
||||
*/
|
||||
Status getStatus();
|
||||
|
||||
/**
|
||||
* Fetches the current status from the server.
|
||||
*
|
||||
* @return Retry-After time, if given by the CA, otherwise empty.
|
||||
*/
|
||||
Optional<Instant> fetch() throws AcmeException;
|
||||
|
||||
/**
|
||||
* Waits until a terminal status has been reached, by polling until one of the given
|
||||
* status or the given timeout has been reached. This call honors the Retry-After
|
||||
* header if set by the CA.
|
||||
* <p>
|
||||
* This method is synchronous and blocks the current thread.
|
||||
* <p>
|
||||
* If the resource is already in a terminal status, the method returns immediately.
|
||||
*
|
||||
* @param statusSet
|
||||
* Set of {@link Status} that are accepted as terminal
|
||||
* @param timeout
|
||||
* Timeout until a terminal status must have been reached
|
||||
* @return Status that was reached
|
||||
*/
|
||||
default Status waitForStatus(Set<Status> statusSet, Duration timeout)
|
||||
throws AcmeException, InterruptedException {
|
||||
Objects.requireNonNull(timeout, "timeout");
|
||||
Objects.requireNonNull(statusSet, "statusSet");
|
||||
if (statusSet.isEmpty()) {
|
||||
throw new IllegalArgumentException("At least one Status is required");
|
||||
}
|
||||
|
||||
var currentStatus = getStatus();
|
||||
if (statusSet.contains(currentStatus)) {
|
||||
return currentStatus;
|
||||
}
|
||||
|
||||
var timebox = now().plus(timeout);
|
||||
Instant now;
|
||||
|
||||
while ((now = now()).isBefore(timebox)) {
|
||||
// Poll status and get the time of the next poll
|
||||
var retryAfter = fetch()
|
||||
.orElse(now.plus(DEFAULT_RETRY_AFTER));
|
||||
|
||||
currentStatus = getStatus();
|
||||
if (statusSet.contains(currentStatus)) {
|
||||
return currentStatus;
|
||||
}
|
||||
|
||||
// Preemptively end the loop if the next iteration would be after timebox
|
||||
if (retryAfter.isAfter(timebox)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait until retryAfter is reached
|
||||
Thread.sleep(now.until(retryAfter, ChronoUnit.MILLIS));
|
||||
}
|
||||
|
||||
throw new AcmeException("Timeout has been reached");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,11 +13,14 @@
|
||||
*/
|
||||
package org.shredzone.acme4j.challenge;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.shredzone.acme4j.AcmeJsonResource;
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.PollableResource;
|
||||
import org.shredzone.acme4j.Problem;
|
||||
import org.shredzone.acme4j.Status;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
@@ -37,7 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
* own type. {@link Challenge#prepareResponse(JSONBuilder)} can be overridden to put all
|
||||
* required data to the challenge response.
|
||||
*/
|
||||
public class Challenge extends AcmeJsonResource {
|
||||
public class Challenge extends AcmeJsonResource implements PollableResource {
|
||||
private static final long serialVersionUID = 2338794776848388099L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Challenge.class);
|
||||
|
||||
@@ -76,6 +79,7 @@ public class Challenge extends AcmeJsonResource {
|
||||
* A challenge is only completed when it reaches either status {@link Status#VALID} or
|
||||
* {@link Status#INVALID}.
|
||||
*/
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return getJSON().get(KEY_STATUS).asStatus();
|
||||
}
|
||||
@@ -155,6 +159,8 @@ public class Challenge extends AcmeJsonResource {
|
||||
* If this method is invoked a second time, the ACME server is requested to retry the
|
||||
* validation. This can be useful if the client state has changed, for example after a
|
||||
* firewall rule has been updated.
|
||||
*
|
||||
* @see #waitForCompletion(Duration)
|
||||
*/
|
||||
public void trigger() throws AcmeException {
|
||||
LOG.debug("trigger");
|
||||
@@ -167,4 +173,22 @@ public class Challenge extends AcmeJsonResource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the challenge is completed.
|
||||
* <p>
|
||||
* Is is completed if it reaches either {@link Status#VALID} or
|
||||
* {@link Status#INVALID}.
|
||||
* <p>
|
||||
* This method is synchronous and blocks the current thread.
|
||||
*
|
||||
* @param timeout
|
||||
* Timeout until a terminal status must have been reached
|
||||
* @return Status that was reached
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public Status waitForCompletion(Duration timeout)
|
||||
throws AcmeException, InterruptedException {
|
||||
return waitForStatus(EnumSet.of(Status.VALID, Status.INVALID), timeout);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user