West Wind Internet and Client Tools
How to get the error details
Gravatar is a globally recognized avatar based on your email address. How to get the error details
  Marcin
  All
  Apr 18, 2019 @ 05:13pm

Hello Rick,

I have the following call to the service.

lcReturn = goJSON.CallService(lcUrl,poOferta,"PUT")					
IF (goJSON.lError)
   RETURN .F.
else 
   RETURN .T.	
endif   		  
 

poOferta contains the parameters passed to the service. Sometimes they are not complete and the service responds with the error message. I can recognize the fact of the error by lError beeing .T. and cErrorMSG contaning "Unprocessable Entity". That is all OK but additionally the service is also providing the details of the error passed back as JSON string which I can see using Fiddler. Unfortunately, the value returned by the method CallService is null. I tried to look at various properties but I could not find the response. Is it possible to get it in such a case?
Regards, Marcin

Gravatar is a globally recognized avatar based on your email address. re: How to get the error details
  Rick Strahl
  Marcin
  Apr 18, 2019 @ 06:03pm

Hmmm... you're right...

So looks like the code in CallService() needs a change to actually failed result value by returning the response:

lcResult = loHttp.HttpGet(lcUrl)

IF loHttp.nError != 0
   this.cErrorMsg = loHttp.cErrorMsg
   this.lError = .T. 

   *** REMOVE RETURN NULL HERE
ENDIF

*** If the response has JSON in it
IF ATC("application/json",loHttp.cHttpHeaders) > 0
    IF ATC("charset=utf-8",loHttp.cHttpHeaders) > 0
      lcResult = STRCONV(lcResult,11)
    ENDIF
    loSer = THIS.CreateSerializer()
    loResult = loSer.DeserializeJson(lcResult)    
    RETURN loResult
ENDIF

RETURN lcResult
ENDFUNC
*   CallService

So this would return NULL before if the result was not a valid 200 response in addition to real HTTP protocol/network errors. So 404 (not found), 401 (Unauthorized) or 500 (Server Error) all would return null.

WIth the change above the processing proceeds and still get a response back. If the data is JSON it'll be deserialized into an object (most likely a JSON object).

So now you effectively can't do a null check for errors but you have to explicitly check the .lError property as you are already doing, but you'd still get the result back.

So:

loProxy = CREATEOBJECT("wwJsonServiceClient")

*** Invalid Url
lcUrl = "http://albumviewer.west-wind.com/api/artistds"

*** Make the service call and returns an Album object
loAlbums = loProxy.CallService(lcUrl)

? loProxy.lError  && .T.
? loAlbums        && long HTML screen for 500 server error

IF VARTYPE(loAlbums) = "O"
   ? loAlbums.Count
ENDIF

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: How to get the error details
  Marcin
  Marcin
  Apr 19, 2019 @ 07:12am

Hi, It is OK now. I can get read the property "UserMessage" and check the details of the error. This is structured information not so long as in your example. There is another point regarding JSON and UTF-8. Your code is looking for "application/json" to decide about de deserialization and code conversion. Look at the example from the service I am connecting to. The header of the response when there is no error is like that:

HTTP/1.1 200 OK
Expires: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
X-XSS-Protection: 1; mode=block
Pragma: no-cache
X-Frame-Options: DENY
Trace-Id: daf77b5f138789e2
Date: Fri, 19 Apr 2019 13:34:27 GMT
X-RateLimit-Remaining: 9000
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked
Content-Type: application/vnd.allegro.public.v1+json;charset=UTF-8
X-RateLimit-Limit: 9000

It is JSON UTF-8 response but it is not deserialized and not converted . To make our life more complex the header of the respose when the error occurs is like that:

HTTP/1.1 422 Unprocessable Entity
Expires: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
X-XSS-Protection: 1; mode=block
Pragma: no-cache
X-Frame-Options: DENY
Trace-Id: 1b584f502f2ac962
Date: Fri, 19 Apr 2019 13:41:58 GMT
X-RateLimit-Remaining: 8999
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked
Content-Type: application/json
X-RateLimit-Limit: 9000

and of course it is de-serialized as it match the condition in your code.

To conclude, do you think that changing the code to look for "json" string in a header instead for "application/json" would be a universal solution? It seems that every service can define the headers in their way? Is there any other way to check if the response contains JSON? Regards, Marcin

Gravatar is a globally recognized avatar based on your email address. re: How to get the error details
  Rick Strahl
  Marcin
  Apr 19, 2019 @ 01:44pm

Not sure what you're asking.

If the response is a non-200 (or rather >= 400) repsonse, .lError will be true. So your 422 result should return .lError .t.

*** REQUEST STATUS ERROR HANDLING
*** - Check for error codes on HTTP errors
***   Any status code > 399 is considered an error
***   We set lError and cErrorMsg
***   But we return the full response
IF THIS.nError = 0 AND this.cResultCode > "399"
   this.nError = VAL(this.cResultCode)
   this.cErrorMsg = this.cResultCodeMessage
ENDIF

IOW, you should always be able to check for lError. If you want to deserialize the error and you're not sure whether there's JSON there, deserialize and try/catch any errors - if it fails it's not JSON.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: How to get the error details
  Marcin
  Rick Strahl
  Apr 19, 2019 @ 04:25pm

I am asking why callservice method attempt to de-serialize response only when the header contains "application/json" string. Is it a standard and the only one way to mark Json type of response? Header example presented by me was for JSON type response but the response was not de-serialized by callservice because of the different string in the header. My question is not related to error situations only, e.g it can happen with regular response for GET. Is it not good enough to check only for "json" string in the header? I am asking because I just started with restapi and I am not sure if such a change can cause other problems in the future. Regards, Marcin

Gravatar is a globally recognized avatar based on your email address. re: How to get the error details
  Rick Strahl
  Marcin
  Apr 20, 2019 @ 05:01pm

Yes it's a standard. A proper HTTP response should include a content type and for JSON it should be application/json. While it's possible to return a response without a content type or any content type, that wouldn't be a proper HTTP response and should be considered a raw response error. Content-Type is the defacto way to describe what content is returned and if something doesn't follow those rules, well - it's breaking the basic rules in which case they are on their own...

As with all things HTTP - few things in HTTP are required but Content-Type is one of the few that are required on each request that has content - both for request and response.

+++ Rick ---

© 1996-2019