Web Connection
Error in wwHttp.GetMessageFromResultCode
Gravatar is a globally recognized avatar based on your email address. Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  All
  Mar 28, 2020 @ 07:43am

Hi Rick,

While working on some new code for submitting data to a json endpoint (or webhook as the host devs refer to it) I ran into the following problem in the method referenced in the subject. Here's the workflow:

m.lcURL=.cWLEndpointLots  
DO wwhttp  
DO wwJsonSerializer  
.oProxy=CREATEOBJECT("wwJsonServiceClient")  
.oSerializer=.oProxy.CreateSerializer()  
.oHttp=.oProxy.CreatewwHttp()  
m.lcJSON=.oSerializer.Serialize(.oWLJson)  
.oHttp.AddPostKey(m.lcJSON)  
m.loResponse=.oProxy.CallService(m.lcUrl, .oHttp, [PUT])  

In the jsonServiceClient CallService method, it then hits this bit of code:

*** Serialize single value/object
IF !IsNullOrEmpty(lvPostData)
  loSer = THIS.CreateSerializer()
  lcJson = loSer.Serialize(lvPostData)

When the Serialize method attempts to do its thing on the json passed to it, it blows up in the GetMessageFromResultCode method in the wwHttp class. For some yet to be determined reason, the cResultCode property is a boolean type instead of a number or char which results in a type mismatch error in VFP. In GetMessageFromResultCode I made the following changes:

*!*	IF VARTYPE(lcErrorNumber) = "N"
*!*	   lcErrorNumber = TRANSFORM(lcErrorNumber)
*!*	ENDIF
*!* - rk - 2020-3-27 - make sure it's character string no matter what
IF VARTYPE(lcErrorNumber) <> "C"
   lcErrorNumber = TRANSFORM(lcErrorNumber)
ENDIF

As a safety I also added an OTHERWISE to the CASE statement that follows:

	CASE lcErrorNumber = "3"
	    lcResult = "Redirection"	
*!* - rk - 2020-3-28 - added otherwise
	OTHERWISE 
	    lcResult = ""	
ENDCASE

I realize the root cause here is how the cResultCode property got set to a boolean instead of a number or char but I haven't gotten that far yet. In the meantime, I was able to continue with my testing of calling this new endpoint. (It ultimately returned a 404 so I've passed the buck back to the other team supplying the endpoint to get me waht I need to keep this job moving.)

I guess it's entirely possible this goes away once I get the right URL/info from the other devs but I thought it would be useful if I passed this back to you as I've apparently found a way for cResultCode to not be a char datatype.

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Richard Kaye
  Mar 28, 2020 @ 10:52am

Poking at this a bit more and now I'm thinking that the thing to change in GetMessageFromResultCode is the assignment of this.cResultCode. For whatever reason, lcErrorNumber does not get passed from the access method, and this is how cResultCode is converted from a char to a boolean. So adding an IIF on this assignment should also avoid the datatype mismatch.

       this.cResultCode = lcErrorNumber
Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Richard Kaye
  Mar 28, 2020 @ 11:06am

I made this change at the top of the method instead of my earlier change and it also fixes the problem. I did leave the OTHERWISE in the case.

FUNCTION GetMessageFromResultCode(lcErrorNumber)
LOCAL lnMemoWidth, lnAt, lcLine, lcResult
*!* - rk - 2020-3-27 - make sure it's character string no matter what
lcErrorNumber=IIF(VARTYPE(lcErrorNumber)=[L],"",lcErrorNumber)

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Mar 28, 2020 @ 01:09pm

A little more context would be useful here.

From what I can see the result code is set in wwHttp by this code:

*** Check the HTTP Result Code
lcHeaders = SPACE(7)
lnHeaderSize = 6
lnRetval = HttpQueryInfo(hHTTPResult,;
   HTTP_QUERY_STATUS_CODE,;
   @lcHeaders,@lnHeaderSize,NULL)
THIS.cResultCode = TRIM(STRTRAN(lcHeaders,CHR(0),""))

which can never be logical. It can fail potentially if the API call fails, but it can't be logical.

I think the thing to do is to set a breakpoint in that method and follow up the call stack to see where the value gets set. Something must be poking that value in there. The method is called from the cResultCodeMessage accessor which is the only place I can see this getting called. This doesn't pass a parameter which means the parameter is .F. into the function, but then the IF EMPTY() check traps that and the code uses the cResultCode so I'm not sure. I've certainly have never seen that particular error.

Maybe you have a repro I can look at...

Your fix breaks the function because it can work with a numeric code (although I don't think that's used).

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Mar 28, 2020 @ 05:16pm

Thanks for the Saturday reply, Rick. I hope you are doing OK in these...interesting times.

I tried to provide enough context for things to make sense but apparently fell short... 😃

I think the last change I made, which only sets lcErrorCode to a blank string at the beginning of the method, avoids the problem of a numeric type being handled properly. I did restore your original transform code to only convert numerics.

There's so much recursion going on in here it's kind of easy to get lost. Maybe I can record a little movie of my debugging session and send that your way.

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Mar 29, 2020 @ 09:09am

The IF EMPTY you refer to doesn't seem to be handling the situation I'm running into. It's hitting the ELSE and continuing on so it is allowing the method to continue whilst lcErrorNmber is a logical value.

IF EMPTY(lcErrorNumber)
   IF this.cResultCode = "2"
      RETURN "OK"
   ENDIF
   
   *** Translate header
   IF !EMPTY(this.cHttpHeaders)	
           lnMemoWidth = SET("MEMOWIDTH")
           SET MEMOWIDTH TO 253
   	   lcLine = MLINE(this.cHttpHeaders,1)
   	   SET MEMOWIDTH TO (lnMemoWidth)
   	   
   	   *** Skip over HTTP/1.1 401 and pick up just the message
   	   lnAt = AT(" ",lcLine,2)
   	   IF lnAt > 0
   	      lcLine = SUBSTR(lcLine,lnAt+1)
   	   ENDIF
   	      
   	   RETURN lcLine
   ELSE
 --->      this.cResultCode = lcErrorNumber
   ENDIF
ENDIF

The Serialize call in CallService where it's been blowing up is happening before the endpoint actually gets called. It's a bit puzzling to me since I'm essentially using the same strategy in other places and that Serialize call works just fine. I'm planning on stepping through the successful workflow to see if I can determine why one of these things is not like the other.

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Mar 30, 2020 @ 04:40pm

Ok now I see it 😃

So I think the code needs to be this:

FUNCTION GetMessageFromResultCode(lcErrorNumber)
LOCAL lnMemoWidth, lnAt, lcLine, lcResult

IF EMPTY(lcErrorNumber)   
   *** short circuit success results
   IF this.cResultCode = "200"
      RETURN "OK"
   ENDIF
   
   *** Translate header
   IF !EMPTY(this.cHttpHeaders)	
           lnMemoWidth = SET("MEMOWIDTH")
           SET MEMOWIDTH TO 253
   	   lcLine = MLINE(this.cHttpHeaders,1)
   	   SET MEMOWIDTH TO (lnMemoWidth)
   	   
   	   *** Skip over HTTP/1.1 401 (second space) and pick up just the message
   	   lnAt = AT(" ",lcLine,2)
   	   IF lnAt > 0
   	      lcLine = SUBSTR(lcLine,lnAt+1)
   	   ENDIF
   	      
   	   RETURN lcLine
   ELSE
---->      RETURN ""  && Nothing we can do here
   ENDIF
ENDIF

IF VARTYPE(lcErrorNumber) = "N"
   lcErrorNumber = TRANSFORM(lcErrorNumber)
ENDIF

lcResult = ""
DO CASE 
    CASE lcErrorNumber = "200"
        lcResult = "OK"
    CASE lcErrorNumber = "201"
        lcResult = "Created"        
    CASE lcErrorNumber = "202"
        lcResult = "Accepted"                
    CASE lcErrorNumber = "204"
        lcResult = "No Content"        
    CASE lcErrorNumber = "206"
        lcResult = "Partial Content"    
        
                    
	CASE lcErrorNumber = "500"	
		lcResult = "Internal Server Error"
	CASE lcErrorNumber = "501"	
		lcResult = "Not implemented"
	CASE lcErrorNumber = "502"	
		lcResult = "Bad Gateway"
	CASE lcErrorNumber = "503"	
		lcResult = "Service Unavailable"
	CASE lcErrorNumber = "504"	
		lcResult = "Gateway Timeout"
	CASE lcErrorNumber = "505"	
		lcResult = "HTTP Version Not Supported"		
	CASE lcErrorNumber = "5"
	    lcResult = "Server Error"

	CASE lcErrorNumber = "400"
  	    lcResult = "Bad Request"	    
	CASE lcErrorNumber = "401"
  	    lcResult = "Unauthorized"
  	CASE lcErrorNumber = "403"
  	    lcResult = "Forbidden"  	
  	CASE lcErrorNumber = "404"
  	    lcResult = "Not Found"  	
  	CASE lcErrorNumber = "405"
  	    lcResult = "Method Not Allowed"  	
	CASE lcErrorNumber = "406"
  	    lcResult = "Not Acceptable"  	    
  	CASE lcErrorNumber = "408"
  	    lcResult = "Server Request Timeout"  	
  	CASE lcErrorNumber = "409"
  	    lcResult = "Conflict"  	
  	CASE lcErrorNumber = "4"
  	    lcResult = "Invalid Request Format (Generic HTTP Error)"  	

	CASE lcErrorNumber = "301"
	    lcResult = "Moved Permanently"	    
	CASE lcErrorNumber = "302"
	    lcResult = "Found and Redirected"
	CASE lcErrorNumber = "303"
	    lcResult = "See Other"
	CASE lcErrorNumber = "304"
	    lcResult = "Not Modified"
	CASE lcErrorNumber = "3"
	    lcResult = "Redirected"	
	OTHERWISE
	    lcResult = ""  && unknown
ENDCASE

RETURN lcResult

If .f. is passed in and there are no HTTP headers (which I'm not sure how that could happen either) then I guess the value should be empty. I'm not sure what a sensible default would be here, other than 500 which is effectively an error or no response.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Mar 31, 2020 @ 07:57am

I would think that the VFP comparison to "2" would be fine assuming there might be more than one 2xx code? But this is where my inexperience with this http stuff kicks in.

I have recorded a little debugging movie for you. What's the best way to get it into your hands?

I have another related question and will start a different topic for that.

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Mar 31, 2020 @ 02:23pm

I would think that the VFP comparison to "2" would be fine assuming there might be more than one 2xx code? But this is where my inexperience with this http stuff kicks in.

Yes that comparison works if you only need to know whehter it worked or not. But in that case your code should look at the cResultCode. This function is supposed to get you the appropriate HTTP message which might be useful to an application to display without having to fix up the messages in your own application.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Mar 31, 2020 @ 03:38pm

The part I still fail to understand is why this is all happening before any post request is made. In which case there will not be any cResultCode value. IAC I emailed you a link to the little debugging movie I made.

It looks like you're preferred solution is to return out of the method earlier. I will update my local copy to match that. Thanks for playing along. 😃

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Mar 31, 2020 @ 04:25pm

I'm guessing it has to do with some sort of pre-flight request. Perhaps and Authentication call from a 401 Not Authorized result?

That code should not fire until the request is done.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Mar 31, 2020 @ 09:02pm

It looks to me because you're serializing the wwHttp object? Somehow the wwHttp object is getting serialized otherwise that wouldn't be hit. Can't really tell but the code is triggered from the JSON serializer so you must be somehow passing the http object to get serialized?

IAC, the fix of returning empty should work for that - but I'd still like to see how it is that wwHttp is getting serialized unless I'm reading that wrong (possible based on what I'm looking at in the editor).

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Apr 2, 2020 @ 09:40am

I think you're right that somehow I'm managing to serialize some data twice. I just emailed you a link to another debugging movie. If that's not helpful enough let me know and I'll be happy to book some of your time to get this figured out.

Thanks.

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Apr 2, 2020 @ 11:26am

And can you elaborate on what a "pre-flight request" is?

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Apr 2, 2020 @ 11:29am

Cancel that last bit. Searching the wisdom of the internet....

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Apr 2, 2020 @ 04:20pm

Pre-Flight Request: Some operations like Authentication or options requests in the browser ask for information from the server before making the actual request.

For example Windows authentication by default tries to connect without credentials first, and only if it gets a 401 it sends another request with the cached authentication ticket. XHR POST requests often make an OPTIONS to see what CORS headers are supported by the server. These requests happen before the actual HTTP request is fired.

Shouldn't matter to HTTP client requests directly - if anything WinInet will do this automatically.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Apr 2, 2020 @ 04:32pm

Thanks, Rick. Did you get the links to the 2 debugging session recordings I sent?

This particular service does not have any explicit auth that I'm aware of. I've asked the other team for more information on how the service is built. In the meantime, if you watched the 2nd movie I sent, it shows that the object is getting serialized again and this is messing with the \ escaping of double quotes. If you have some time I'd love to have your eyes on this.

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Rick Strahl
  Richard Kaye
  Apr 3, 2020 @ 04:23pm

Is this still an issue after fixing that return value in the method? I don't really feel like tracing down a paper tiger here for a problem that's no longer an issue...

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Error in wwHttp.GetMessageFromResultCode
  Richard Kaye
  Rick Strahl
  Apr 4, 2020 @ 07:27am

No, and my apologies for taking up so much of your time discussing this. Now my issue is why my post data is getting encoded twice, and I was looking for your expert help with that. However, I finally had a breakthrough this morning and figured it out. (It might also explain how I managed to find the use case for the original bug report.) I revisited the fine documentation on the wwhttp topic. After reviewing that I decided to use wwhttp.post instead of jsonserviceclient.callservice and my double encoding went away. You certainly gave me a clue in one of your earlier comments in this thread but it just took too long for it to penetrate. This does leave me with one final question about when it is better to use the service client vs the http class, but I think I'll go read some more and see if enlightenment ensues. Thanks again, as always, for your help.

© 1996-2024