ApiUrlDiscoveryHelper.java
package io.github.evisentin.wordpress.rest.client.adapter.apache.discovery;
import io.github.evisentin.wordpress.rest.client.domain.exception.ApiUrlNotFoundException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.SneakyThrows;
import org.apache.commons.lang3.Strings;
import org.apache.hc.client5.http.classic.methods.HttpHead;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.Header;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Internal utility for discovering the WordPress REST API base URL.
*
* <p>This helper implements the WordPress REST API discovery mechanism by issuing an HTTP {@code HEAD} request against
* a site URL and inspecting {@code Link} response headers for the standard WordPress API relation
* {@code https://api.w.org/}.</p>
*
* <p>When a matching relation is found, the corresponding API endpoint URL is extracted and returned. Trailing slashes
* are removed to ensure a consistent base URL format.</p>
*
* <p>This class is intended for internal use by the client implementation and is not part of the public API. Consumers
* should rely on higher-level client abstractions rather than invoking this utility directly.</p>
*
* <p>The implementation follows the WordPress REST API discovery process described by WordPress and compatible API
* providers.</p>
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ApiUrlDiscoveryHelper {
private static final Pattern LINK_PATTERN = Pattern.compile("<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"");
/**
* Discovers the WordPress REST API base URL for a site.
*
* <p>An HTTP {@code HEAD} request is sent to the provided site URL and the returned {@code Link} headers are
* inspected for a relation of {@code https://api.w.org/}. The associated URL is returned as the API endpoint.</p>
*
* @param httpClient
* the HTTP client used to perform the discovery request; must not be {@code null}
* @param baseUrl
* the WordPress site URL from which discovery should start; must not be {@code null}
*
* @return the discovered WordPress REST API base URL
*
* @throws ApiUrlNotFoundException
* if no WordPress REST API discovery link is present in the response
*/
@SneakyThrows
public static String resolveApiUrl(final @NonNull CloseableHttpClient httpClient,
final @NonNull String baseUrl) {
final HttpHead request = new HttpHead(baseUrl);
return httpClient.execute(request, response -> {
for (Header header : response.getHeaders("Link")) {
final Matcher matcher = LINK_PATTERN.matcher(header.getValue());
while (matcher.find()) {
final String url = matcher.group(1);
final String rel = matcher.group(2);
if ("https://api.w.org/".equals(rel)) {
return Strings.CI.removeEnd(url, "/");
}
}
}
throw new ApiUrlNotFoundException(baseUrl);
});
}
}