Hi Rick,
I'm working on integrating a call from my app to a new internal REST service and I'm getting the error response shown in the post title. I've no doubt that I'm doing something wonky but figuring that out has turned into a challenge.
Running in my dev I can stop at the point where I've got everything prepared, take the headers and payload, copy them into a Websurge request hitting the same endpoint, and I get a 200.
But when I call it in the context of a wwJsonServiceClient object's CallService
method I get a 400. The only thing I can think of at the moment is the service doesn't like the escaped double quotes in the post buffer but that doesn't make much sense.
Here's a snippet from the method that calls the API:
loSendBatch=CREATEOBJECT("wwJsonServiceClient")
WITH m.loSendBatch
.lSaveRequestData=.t.
.CreateWWHTTP()
.CreateSerializer()
.oSerializer.PropertyNameOverrides=[batchId,userName,dateSubmitted,invBalance,approvalId]
.oHttp.cContentType=[application/json; charset=utf-8]
* add headers
.oHttp.AddHeader("Authorization", m.lcBearerToken)
.oHttp.AddHeader("X-RFC-Signature", m.lcHash)
.oHttp.nConnectTimeout=600
* - serialize json from parameter object.
lcJson=STRCONV(.oSerializer.Serialize(m.toJson),9)
.oHttp.AddPostKey(m.lcJSON)
m.llRetval=.t.
* tbd - response handling
lcEndpoint=[<snip>]
luResponse=.CallService(m.lcEndpoint, .oHttp)
IF .oHttp.cResultCode=[200] && success
m.llRetval=.t.
ELSE
ASSERT VARTYPE(m.luResponse)=[O] MESSAGE [why is the response not an object?]
IF VARTYPE(m.luResponse)=[O] && response is a json object
m.llRetval=.f.
m.lcString=CRLF+[batch ]+TRANSFORM(m.tnBatchID)+[ rejected]+CRLF
m.lcString=m.lcString+ALLTRIM(m.luResponse.code)+CRLF
m.lcString=m.lcString+ALLTRIM(m.luResponse.Message)+CRLF
m.lcString=m.lcString+TRANSFORM(m.luResponse.data.json_error_code)+CRLF
m.lcString=m.lcString+ALLTRIM(m.luResponse.data.json_error_message)+CRLF
m.lcString=m.lcString+TRANSFORM(m.luResponse.data.Status)+CRLF
m.lcString=m.lcString+[Full request (encoded)]+CRLF
m.lcString=m.lcString+ALLTRIM(.cRequestData)+CRLF
m.lcString=m.lcString+[Full response]+CRLF
m.lcString=m.lcString+ALLTRIM(.cResponseData)+CRLF
logEvent(m.lcString,FORCEEXT([.\Logs\sendBatchPayments_]+DTOS(DATE()),[log])) && tbd
ELSE
* tbd?
ENDIF
ENDIF
ENDWITH && m.loSendBatch
RETURN m.llRetval
I know there are some explicit calls that I should not need to make such as creating the oHTTP and oSerializer objects but as part of my troubleshooting I'm trying to remove assumptions. As you can see I've set the object lSaveRequestData
property to .t. and am writing that out to a log.
Here's the log slightly sanitized for my protection and reformatted to make it easier to read:
10/31/2024 03:55 PM:
batch 50 rejected
rest_invalid_json
Invalid JSON body passed.
4
Syntax error
400
Full request (encoded)
{"ccontenttype": "application/json; charset=utf-8",
"cerrormsg": "",
"cextraheaders": "Authorization: Bearer <snip>\r\nX-RFC-Signature: tsSCCrKewFGXQZd6lJDe9ilkr6ZqNyjE8Zo+ugrrs+o=\r\n",
"chttpheaders": "",
"chttpproxybypass": "",
"chttpproxyname": "",
"chttpproxypassword": "",
"chttpproxyusername": "",
"chttpverb": "POST",
"clink": "",
"cpassword": "",
"cpostbuffer": "{\"batchId\": 50,\"charges\": [{\"approvalId\": 330713121,\"invBalance\": 125,\"invno\": \"10147-WL54\"}],\"dateSubmitted\": \"2024-11-01T01:53:44Z\",\"userName\": \"test@test.com\"}",
"cresultcode": "",
"cresultcodemessage": "",
"cserver": "",
"cuseragent": "West Wind Internet Protocols 8",
"cusername": "",
"cversion": "8",
"lallowgzip": false,
"lcacherequest": false,
"ldecodeutf8": false,
"lhttpcanceldownload": false,
"lignorecertificatewarnings": false,
"lsecurelink": false,
"luselargepostbuffer": false,
"name": "Wwhttp",
"nclientcertnumberindex": 0,
"nconnecttimeout": 600,
"ncontentsize": 0,
"nerror": 0,
"nhttpconnecttype": 0,
"nhttpport": 0,
"nhttppostmode": 1,
"nhttpserviceflags": 0,
"nhttpworkbuffersize": 65556,
"nserviceflags": 0,
"opoststream": {"cfilename": "C:\\USERS\\RKAYE\\APPDATA\\LOCAL\\TEMP\\{D21A9C5D-ACF7-4D82-9BCB-E9C3AB71D581}.txt",
"name": "Wwfilestream",
"nhandle": 125,
"nlength": 0}}
Full response
{"code":"rest_invalid_json",
"message":"Invalid JSON body passed.",
"data":{"status":400,
"json_error_code":4,
"json_error_message":"Syntax error"
}
}
Any thoughts you care to share will be much appreciated.
TIA
Wow - you really know how to make something simple very complicated! Your code is posting the .oHttp object instance data to the server 😄
If you'd have to do all that why would you even use this component? It would be easier to just use wwhttp and wwJsonSerializer manually.
You don't need to do anything with the HTTP object unless you need to add custom headers. Otherwise just pass your object (not the JSON) to .CallService()
and it'll handle the rest.
loSendBatch=CREATEOBJECT("wwJsonServiceClient")
WITH m.loSendBatch
.lSaveRequestData=.t.
.oSerializer.PropertyNameOverrides=[batchId,userName,dateSubmitted,invBalance,approvalId]
* add custom headers
.oHttp.AddHeader("Authorization", m.lcBearerToken)
.oHttp.AddHeader("X-RFC-Signature", m.lcHash)
.oHttp.nConnectTimeout=600
lcEndpoint=[<snip>]
luResponse=.CallService(m.lcEndpoint, m.toJson)
I urge you to re-read the topic linked above a couple of times and use the approach recommended at the bottom of topic description - use a service class that holds all your service calls and manage each call in small blocks with method wrappers that return clean results, handle errors cleanly and return a logical error response in case something goes wrong (as it often can with service calls).
+++ Rick ---