Skip to content

CurlSender throws "Invalid JSON response received" on successful trace submissions (empty 201 response) #63

@peterchrjoergensen

Description

@peterchrjoergensen

When submitting performance traces to the Flare API, the CurlSender incorrectly throws a ConnectionError with message "Invalid JSON response received" even though the request was successful.

The Flare API returns HTTP 201 Created with an empty response body for successful trace submissions. However, CurlSender::post() attempts to parse the empty string as JSON, which fails:

$body = json_decode($json, true);  // json_decode('') returns null

if (json_last_error() !== JSON_ERROR_NONE) {  // JSON_ERROR_SYNTAX
    throw new ConnectionError('Invalid JSON response received');
}

This error is then silently swallowed by Api::trace() which catches all exceptions:

public function trace(array $payload): void
{
    try {
        $this->post($payload, FlarePayloadType::Trace);
    } catch (Throwable) {
        // Silent failure - no indication that traces aren't being sent
    }
}

Steps to Reproduce

  1. Configure Flare with performance monitoring enabled
  2. Use AlwaysSampler or wait for sampling to trigger
  3. Make a request that generates a trace

Expected Behavior

Traces should be successfully submitted to Flare and interpreted as succesful.

Actual Behavior

Traces silently fail to submit because CurlSender throws an exception when parsing the empty 201 response body.

Environment

  • flare-client-php version: 2.8.0
  • PHP version: 8.2
  • Framework: WordPress (non-Laravel usage)

Suggested Fix

Check for empty response bodies before attempting JSON parsing:

// Handle empty responses (e.g., 201 Created with no body)
$body = [];
if (trim($json) !== '') {
    $body = json_decode($json, true);

    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new ConnectionError('Invalid JSON response received');
    }
}

Example:

         $json = curl_exec($curlHandle);

         if (is_bool($json)) {
             throw new ConnectionError(curl_error($curlHandle));
         }

-        $body = json_decode($json, true);
-
-        if (json_last_error() !== JSON_ERROR_NONE) {
-            throw new ConnectionError('Invalid JSON response received');
+        // Handle empty responses (e.g., 201 Created with no body)
+        $body = [];
+        if (trim($json) !== '') {
+            $body = json_decode($json, true);
+
+            if (json_last_error() !== JSON_ERROR_NONE) {
+                throw new ConnectionError('Invalid JSON response received');
+            }
         }

         $headers = curl_getinfo($curlHandle);

Additional Context

This bug is particularly insidious because:

  1. Error reporting works fine (errors return proper JSON responses)
  2. The trace collection code works correctly (sampling, spans, etc.)
  3. The exception is silently caught, so there's no indication of failure
  4. Debugging requires adding manual logging to the vendor code to discover the issue

I discovered this by adding debug logging throughout the Flare client and observing:

  • HTTP status: 201 (success!)
  • Response body: empty string
  • Result: ConnectionError thrown and silently caught

Workaround

Create a custom sender that extends CurlSender and overrides the post() method with the fix:

class FixedCurlSender extends CurlSender
{
    public function post(string $endpoint, string $apiToken, array $payload, FlarePayloadType $type, Closure $callback): void
    {
        // ... same as parent, but with empty response handling fix
    }
}

Then configure Flare to use it:

FlareConfig::make(apiToken: $apiToken)
    ->sendUsing(FixedCurlSender::class)
    // ... rest of config

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions