0

When testing some REST API, I'm using a cURL command. However when the request failed, it seems that cURL does not indicate that there was a non-success status code (HTTP/2 405 in my case); instead I just see the response (which is a JSON "messages" array).

As it would require "quite a lot of AI" to deduce from the contents of the message array that there was an error, I wonder how I can reliably detect the non-success status code when using cURL.

Maybe it could even be a bug in cURL (regarding use of HTTP/2).

Example session (I deliberately mad a request error to demonstrate):

> curl -u monitor  'https://redacted.server.name/api/status/get-token'
Enter host password for user 'monitor':

{
  "messages": [
    "The global command get-token requires to use PUT"
  ]
}
> echo $?
0
6
  • add --verbose to your command line. Commented Aug 21, 2023 at 7:19
  • The question was not "how to see", but "how to get", meaning how to test using a program. If I would have to "grep" the headers, wouldn't that make cURL rather useless? Finally how do you think I found out that there's a "HTTP/2 405" status? ;-) Commented Aug 21, 2023 at 7:28
  • Then I suggest you rephrase your question. CURL returns string output and not boolean or int, you need to parse its returned content for that. CURL is not really made for testing purposes in that way that your phrasing. Commented Aug 21, 2023 at 7:37
  • 1
    Dupe stackoverflow.com/questions/38906626/… Commented Aug 21, 2023 at 9:39
  • @Anuga As the HTTP status codes are well-defined, cURL could map them to exit status. HTTP does not require to send any more error details along with the status code, so relying on the response data only may be unreliable. For real use I won't use cURL or BASH. Commented Aug 22, 2023 at 6:42

2 Answers 2

1
curl --write-out '\nresponse_code=%{response_code}\n' 'https://redacted.server.name/api/status/get-token'

should end with

response_code=405

if you need it in a variable i suppose you could do

RESPONSE=$(curl --write-out '\n%{response_code}' 'https://redacted.server.name/api/status/get-token')
RESPONSE_CODE=$(tail -1 <<<$RESPONSE)
RESPONSE=$(echo "$RESPONSE" | head --lines=-1)

now echo "$RESPONSE_CODE" should give you 405 and echo "$RESPONSE" should give you the original http response body...

Btw seems you're approaching the complexity where using bash no longer makes sense, maybe try switching to a better scripting language like PHP, Python, or Perl? They all have good libcurl bindings.

Sign up to request clarification or add additional context in comments.

8 Comments

HTTP body doesn't have to end with NL, and for JSON it often doesn't; safer to force \n before RESPONSE_CODE as well. And can just write \n%{response_code}\n then do RESPONSE_CODE=$(tail -1 <<<$RESPONSE) instead of eval. Or use no newlines and take 3 chars: stackoverflow.com/questions/54038132/…
@dave_thompson_085 seems curl hardcodes a newline between the HTTP Body and --write-out - great idea to use RESPONSE_CODE=$(tail -1 <<<$RESPONSE) tho
I agree that cURL with BASH has limits, but quite a lot of REST API docs refer to using cURL, and just to verify the principle, I used cURL and BASH. Once it's at parsing or creating JSON, it's better to leave BASH, however.
It doesn't add newline for me; please try this test server I set up temporarily on AWS (either approach, but especially yours)
@dave_thompson_085 dang you're right, it doesn't add a newline, and the code is vulnerable. thanks for pointing it out, code updated.
|
0

As the response is JSON, I decided to format the extra status codes JSON-like, too (based on the suggestion in https://stackoverflow.com/a/76943672/6607497). I use these extra options to curl:

-w \
'"_conn%{urlnum}": %{http_connect}\n'\
'"_code%{urlnum}": %{http_code}\n'\
'"_emsg%{urlnum}": "%{errormsg}"\n'

With curl's response in RESP I transform the result (all JSON values are assumed to be either numbers or strings on a single line). FILE is some temporary filename:

sed -n -e 's/^ *"\([^"]\+\)": *"\?\([^"]\+\)"\?,\?$/\1=\2/p' > "$FILE"

To lookup values I added this helper:

get_info()
{
    sed -n -e 's/^'"$1"'=\(.\+\)$/\1/p' "$FILE"
}

So finally the code to check the result (with a squid proxy present) looks like this:

STATUS=$(get_info _code0)
[ -z "$STATUS" -o "$STATUS" = 000 ] && STATUS=$(get_info _conn0)
if [ "$STATUS" = 200 ]; then
    # success...
    rm "$FILE"
else
    EMSG=$(get_info _emsg0)
    [ -n "$EMSG" ] && STATUS="$STATUS $EMSG"
    emsg "$0: failed with status \"$STATUS\":"
    emsg "$RESP"  # show the complete response for debugging purposes
    rm "$FILE"
fi

emsg just writes to STDERR:

emsg()
{
    echo "$@" >&2
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.