instructure/canvas-lms · error · Pv4EmptyResponse

the response is empty or does not contain expected keys

Error message

the response is empty or does not contain expected keys

What it means

Pv4Client#fetch raises Pv4EmptyResponse when the PV4 service response body is empty, unparseable JSON, or parses to a JSON object without a "page_views" key. It guards against unexpected/blank payloads so callers never process malformed data.

Solutions

  1. Inspect the raw PV4 response (curl the endpoint) to see what is actually returned
  2. Verify PV4 service version matches the response schema Canvas expects ("page_views" key)
  3. Check proxies/load balancers for body modification or truncation
  4. Treat Pv4EmptyResponse as a transient/empty result and retry or show empty history

Example fix

// before
json = JSON.parse(response.body)
// after
json = begin
  response.body.empty? ? {} : JSON.parse(response.body)
rescue JSON::ParserError
  {}
end
raise Pv4EmptyResponse, "..." unless json.is_a?(Hash) && json.key?("page_views")
Defensive patterns

Strategy: fallback

Validate before calling

body = response_body_if_you_have_it
return {} if body.blank?

Type guard

def valid_pv4_json?(parsed)
  parsed.is_a?(Hash) && parsed.key?("page_views") && parsed["page_views"].is_a?(Array)
end

Try / catch

begin
  views = PageView.for_user(user)
rescue Pv4EmptyResponse
  views = [] # or retry once for transient truncation
end

Prevention

When it happens

Trigger: PV4 service returns 200 with an empty body, returns non-JSON content, or returns valid JSON lacking the "page_views" key — e.g. a proxy stripped the body, a load balancer error page, or a PV4 version change to the response schema.

Common situations: PV4 service partially deployed or behind a misconfigured proxy/gateway; PV4 API version changed response shape; transient network issues truncating the body; hitting a health-check endpoint instead of the data endpoint.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/c53998d421275ac3. Report an issue: GitHub.

Appendix: source

Thrown at app/models/page_view/pv4_client.rb:73

      case response.code.to_i
      when 400
        raise Pv4BadRequest, "invalid request"
      when 401
        raise Pv4Unauthorized, "unauthorized request"
      when 404
        raise Pv4NotFound, "resource not found"
      when 429
        raise Pv4TooManyRequests, "rate limit exceeded"
      end

      json =
        begin
          response.body.empty? ? {} : JSON.parse(response.body)
        rescue JSON::ParserError
          {}
        end
      raise Pv4EmptyResponse, "the response is empty or does not contain expected keys" unless json["page_views"]

      json["page_views"].map! do |pv|
        pv["session_id"] = pv.delete("sessionid")
        vhost = pv.delete("vhost")
        http_request = pv.delete("http_request")
        pv["url"] = if vhost.present? && http_request.present?
                      "#{HostUrl.protocol}://#{vhost}#{http_request}"
                    elsif http_request.present?
                      "#{HostUrl.protocol}:#{http_request}"
                    elsif vhost.present?
                      "#{HostUrl.protocol}://#{vhost}"
                    end
        pv["context_id"] = pv.delete("canvas_context_id")
        pv["context_type"] = pv.delete("canvas_context_type")
        pv["updated_at"] = pv["created_at"] = pv.delete("timestamp")
        pv["user_agent"] = pv.delete("agent").presence
        pv["account_id"] = pv.delete("root_account_id")
        pv["remote_ip"] = pv.delete("client_ip")

View on GitHub (pinned to 1c9f0bb801)