Skip to content

Latest commit

 

History

History
94 lines (63 loc) · 4.47 KB

File metadata and controls

94 lines (63 loc) · 4.47 KB

Checking the HTTP status of a PROC HTTP call

PROC HTTP doesn't fail when the API does

A REDCap API call that returns 403 Forbidden is, as far as PROC HTTP is concerned, a completely successful transaction. The request went out, a response came back, and the response body was written to the output fileref exactly as asked. The step ends without an ERROR in the log.

That body just happens to contain an error message instead of your data.

So if you don't check the status code yourself, the next step in your program will happily hand that error text to the JSON libname engine, and you'll spend your time debugging a confusing parse failure rather than reading the plain-English reason your call was rejected.

The two macro variables

When PROC HTTP receives a response, it sets two global macro variables:

SYS_PROCHTTP_STATUS_CODE     the numeric status - 200, 403, 500 ...
SYS_PROCHTTP_STATUS_PHRASE   the accompanying text - "OK", "Forbidden" ...

Note the spelling: PROCHTTP, one word, no underscore between PROC and HTTP.

This trips people up constantly, including me, and including the LLMs I've written SAS with. SYS_PROC_HTTP_STATUS_CODE reads better and matches the two-word procedure name, so it's what you'll type from memory. It isn't the name SAS uses.

The failure is loud, at least. An unresolved reference in the %IF leaves the literal text behind where a number should be:

WARNING: Apparent symbolic reference SYS_PROC_HTTP_STATUS_CODE not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition
       where a numeric operand is required.

In an interactive session that's obvious. In a batch log it's one error in a program that otherwise appears to keep running — easy to skim past, and your status checking is silently doing nothing.

They aren't always there

The status variables describe a response. If PROC HTTP never got one — the host is unreachable, the URL is wrong, a proxy or firewall dropped the connection — there's no status to report.

The trap is that these are ordinary global macro variables, so a value set by an earlier successful call in the same session is still sitting there. A check written against a stale 200 will wave through a call that never actually happened.

Two habits avoid this:

  • Clear the variables before the call, so a leftover value can't be mistaken for a fresh one.
  • Confirm the variable exists before testing it, with %SYMEXIST.

A defensive version of the check

/* clear any status left over from an earlier call in this session */
%SYMDEL SYS_PROCHTTP_STATUS_CODE SYS_PROCHTTP_STATUS_PHRASE / NOWARN;

PROC HTTP
      URL    = "&redcap_url."
      METHOD = "POST"
      IN     = postbody
      OUT    = apiresp;
   HEADERS "Content-Type" = "application/x-www-form-urlencoded";
RUN;

%MACRO check_http_response();

   %GLOBAL api_call_ok;
   %LET api_call_ok = 0;

   /* no status at all means no response came back */
   %IF %SYMEXIST(SYS_PROCHTTP_STATUS_CODE) = 0 %THEN %DO;
      %PUT ERROR: PROC HTTP returned no HTTP status - the request never reached
           the server, or no response was received.;
      %RETURN;
   %END;

   %PUT NOTE: HTTP status=&SYS_PROCHTTP_STATUS_CODE;
   %PUT NOTE: HTTP phrase=&SYS_PROCHTTP_STATUS_PHRASE;

   %IF &SYS_PROCHTTP_STATUS_CODE >= 200
       AND &SYS_PROCHTTP_STATUS_CODE < 300 %THEN %DO;
      %LET api_call_ok = 1;
   %END;
   %ELSE %DO;
      %PUT ERROR: HTTP request failed:
          &SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE;
   %END;

%MEND check_http_response;

Set a flag, don't abort

Note that the macro above reports a failure by setting api_call_ok rather than by aborting.

%ABORT only reliably stops a whole submitted program in batch mode. In an interactive session — Display Manager, SAS Studio, Enterprise Guide — %ABORT RETURN merely unwinds the current macro, while %ABORT CANCEL and %ABORT ABEND can tear down the client session entirely. None of that is what you want from a check you intend to reuse.

A flag behaves identically either way: wrap the steps that depend on the call in %IF &api_call_ok EQ 1 %THEN %DO; ... %END; and a failed call simply skips them, leaving the session alone.

basic-api-call-pro.sas puts this together with the rest of a working REDCap export, including dumping the response body when the call fails — worth doing, since REDCap has been observed returning error details as XML even when returnFormat=json was requested.