Skip to content

dotCDN purge cache fails silently due to JSONObject not handling empty response from Bunny.net API #35320

@dsolistorres

Description

@dsolistorres

Bug Summary

The DotCDNAPIImpl.invalidate() method fails when purging individual URLs via the Bunny.net API (POST https://api.bunny.net/purge?url=...). The Bunny.net purge endpoint returns HTTP 200 with an empty body on success, but the code attempts to parse the empty response as JSON, causing a JSONException.

Root Cause

This is a regression introduced in commit 3e0bfcd ("moving away from objectMapper") in the com.dotcms.dotcdn plugin. The change replaced ObjectMapper-based JSON parsing with JSONObject in the getData() method but removed the error-handling safety net (Try.of(...).getOrElse(Map.of())).

Before (working):

private Map<String, Object> getData(CircuitBreakerUrlBuilder url) {
    final String response = Try.of((() -> url.build().doString())).getOrElse("{\"response\":\"FAILURE\"}");
    return Try.of(() -> ObjectMapperProvider.mapper.get().readValue(response, Map.class))
            .getOrElse(Map.of());
}

When Bunny returned an empty body, ObjectMapper.readValue("", Map.class) would throw, the Try caught it, and Map.of() was returned — which didn't contain "response" -> "FAILURE", so the purge was correctly treated as successful.

After (broken):

private Map<String, Object> getData(CircuitBreakerUrlBuilder url) {
    final String response = Try.of((() -> url.build().doString())).getOrElse("{\"response\":\"FAILURE\"}");
    JSONObject jsonObject = new JSONObject(response);
    return jsonObject;
}

new JSONObject("") throws a JSONException with no try/catch, crashing the entire invalidate() call.

Impact

  • Purging individual URLs via the /api/v1/dotcdn DELETE endpoint fails even when Bunny.net successfully processes the purge.
  • The invalidateAll() method (which uses RestClientBuilder and checks HTTP status directly) is NOT affected.
  • Actionlet-triggered invalidations are also affected since they go through the same invalidate() code path.

Steps to Reproduce

  1. Configure dotCDN app with valid Bunny.net API key, pull zone ID, and CDN domain.
  2. Send a DELETE request to /api/v1/dotcdn to purge cache for a specific URL.
  3. Observe that the operation fails with a JSONException even though Bunny.net returns HTTP 200.

Fix

The invalidate() method should use CircuitBreakerUrl.doResponse() to get the HTTP status code directly instead of routing through getData(), since the Bunny purge endpoint does not return a JSON response body. Success should be determined by statusCode == 200. Additionally, request/response logging should be added for observability.

Affected File

com.dotcms.dotcdn plugin: src/main/java/com/dotcms/cloud/cdn/api/DotCDNAPIImpl.java

Acceptance Criteria

  • invalidate() no longer routes purge calls through getData() / JSON parsing
  • Purge success is determined by HTTP status code (200) from Bunny.net API
  • Request URL and response (status code + body) are logged for observability
  • invalidateAll() behavior remains unchanged
  • Stats endpoint (getStats()) behavior remains unchanged

dotCMS Version

Current Evergreen
26.04.02-01

Severity

High - Major functionality broken

Links

https://helpdesk.dotcms.com/a/tickets/36175

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions