2

Hello Progress 4GL Developers,

I would like to consume the UPS Tracking API in an ABL session.
I have ran the WSDLAnalyser for the UPS WSDL and have followed the documentation.

Here is an example of the request in XML that I wish to send (including header):

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:v2="http://www.ups.com/XMLSchema/XOLTWS/Track/v2.0" xmlns:v11="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0">
<soapenv:Header>
   <v1:UPSSecurity>
      <v1:UsernameToken>
         <v1:Username>MYUSERNAME</v1:Username>
         <v1:Password>MYPASSWORD</v1:Password>
      </v1:UsernameToken>
      <v1:ServiceAccessToken>
         <v1:AccessLicenseNumber>MYLICENSENUMBER</v1:AccessLicenseNumber>
      </v1:ServiceAccessToken>
   </v1:UPSSecurity>
</soapenv:Header>
<soapenv:Body>
   <v2:TrackRequest>
      <v2:ReferenceNumber>
         <v2:Code>MYCODE</v2:Code>
         <v2:Value>MYORDERNUM</v2:Value>
      </v2:ReferenceNumber>
   </v2:TrackRequest>
</soapenv:Body>
</soapenv:Envelope>

This request works as expected when sent using SOAPUI to test the request. Here is my Progress4GL code so far to try and make the same request:

{us/mf/mfdtitle.i}
{/qond/apps/mfgpro/customizations/mfg/work/src/us/xx/xxwhsxml.i}

define variable hs as handle no-undo.
define variable hp as handle no-undo.
DEFINE VARIABLE cBody  AS LONGCHAR NO-UNDO.
DEFINE VARIABLE cBody2 AS LONGCHAR NO-UNDO.

cBody = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:v2="http://www.ups.com/XMLSchema/XOLTWS/Track/v2.0" xmlns:v11="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0">
<soapenv:Header>
   <v1:UPSSecurity>
      <v1:UsernameToken>
         <v1:Username>MYUSERNAME</v1:Username>
         <v1:Password>MYPASSWORD</v1:Password>
      </v1:UsernameToken>
      <v1:ServiceAccessToken>
         <v1:AccessLicenseNumber>MYLICENSENUMBER</v1:AccessLicenseNumber>
      </v1:ServiceAccessToken>
   </v1:UPSSecurity>
</soapenv:Header>
<soapenv:Body>
   <v2:TrackRequest>
      <v2:ReferenceNumber>
         <v2:Code>MYCODE</v2:Code>
         <v2:Value>MYORDERNUM</v2:Value>
      </v2:ReferenceNumber>
   </v2:TrackRequest>
</soapenv:Body>
</soapenv:Envelope>'.

create server hs.
hs:connect( "-WSDL /home/jbetts/track/Track.wsdl -Port TrackPort -TargetNamespace http://www.ups.com/WSDL/XOLTWS/Track/v2.0 -nohostverify -nosessionreuse" ). 

run TrackPortType set hp on server hs.
run ProcessTrack in hp ( input cBody, output cBody2 ).

delete procedure hp.
hs:disconnect().
delete object hs.

I think the problem is that I need to send the header in a certain way when I run the ProcessTrack procedure (using SET-CALLBACK-PROCEDURE) but I'm not sure how.

Thanks in advance!

Tom Bascom
  • 13,405
  • 2
  • 27
  • 33
Joseph Betts
  • 107
  • 1
  • 11
  • Hi Joseph. I am also trying to use the tracking webservice from Progress4GL. But I am running into an issue that my session crashes as it is executing the request. Some answers pointed me to upgrade to version 11.7, while I am using 11.5. Just wanted to check if you ran into any issues like that and if so how did you resolve it. Any tips will help me. Here is the link to my [question](https://stackoverflow.com/questions/63297874/c0000005-access-violation-exception-with-progress-openedge-when-calling-a-web-se) – davidbilla Aug 07 '20 at 09:26
  • Hello davidbilla, For this particular SOAP web service I didn't come across any issues, however I did indeed come across some issues using the HTTPClientLibrary to try and consume a different REST web service in my OpenEdge environment. It would happen when running using .NET. To overcome that issue, I actually ended up adapting that version of my code to instead enter the operating system and use CURL rather than the HTTPClient. So for example, instead of HTTPClientBuilder, I would use the following procedure (which takes the URL as an input): ``` – Joseph Betts Aug 14 '20 at 19:03
  • Thank you Joseph. I too adopted to a rest service. – davidbilla Aug 15 '20 at 04:33

1 Answers1

3

SET-CALLBACK-PROCEDURE is the way.

You need to do something like this (setRequestSessionHandler is the name of the procedure so it can be whatever you want) before you RUN the SOAP-call:

hp:SET-CALLBACK-PROCEDURE("REQUEST-HEADER", "setRequestSessionHeader").

The request-callback has two input and two output parameters (names taken from the documentation, they can be called anything as long as input/output and datatype is correct):

DEFINE OUTPUT PARAMETER hSOAPHeader         AS HANDLE    NO-UNDO. 
DEFINE INPUT  PARAMETER cOperationNamespace AS CHARACTER NO-UNDO.  
DEFINE INPUT  PARAMETER cOperationLocalName AS CHARACTER NO-UNDO.  
DEFINE OUTPUT PARAMETER plDeleteOnDone AS LOGICAL   NO-UNDO. 

hSOAPHeader — A handle to a SOAP header object that encapsulates the header of the SOAP message that is about to be sent (request header) or that has just been received (response header). In a response header handler, the SOAP header object has no content if the NUM-HEADER-ENTRIES attribute on the object handle returns the value 0; otherwise it contains one or more SOAP header entries. In a request header handler, this is an OUTPUT parameter, therefore if the outgoing SOAP message requires a SOAP header, you must either build a SOAP header for this parameter to reference or provide an existing SOAP header saved from a previous response callback.

cOperationNamespace — Contains the namespace portion of the operation's qualified name. Use this parameter together with the cOperationLocalName parameter if you need to identify the operation for which the SOAP message is being sent or received.

cOperationLocalName — Contains the local-name portion of the operation's qualified name. Use this parameter together with the cOperationNamespace parameter if you need to identify the operation for which the SOAP message is being sent or received.

lDeleteOnDone — (Request callback only) Tells OpenEdge to delete the SOAP header object and all of the parsed XML after the SOAP header has been inserted into the out-bound SOAP message

This is what I do to mimic WS-Security, not exactly what you need and also quite a lot of code. You should be able to "mine it" for whatever you need.

PROCEDURE setRequestSessionHeader :
/*------------------------------------------------------------------------------
  Purpose:     
  Parameters:  <none>
  Notes:       
------------------------------------------------------------------------------*/

DEFINE OUTPUT PARAMETER phHeader       AS HANDLE    NO-UNDO. 
DEFINE INPUT  PARAMETER pcNamespace    AS CHARACTER NO-UNDO.  
DEFINE INPUT  PARAMETER pcLocalNS      AS CHARACTER NO-UNDO.  
DEFINE OUTPUT PARAMETER plDeleteOnDone AS LOGICAL   NO-UNDO. 

DEFINE VARIABLE hSoapHeaderEntryref1    AS HANDLE      NO-UNDO.
DEFINE VARIABLE hSoapHeaderEntryref2    AS HANDLE      NO-UNDO.
DEFINE VARIABLE hSoapHeaderEntryref3    AS HANDLE      NO-UNDO.
DEFINE VARIABLE hSoapHeaderEntryref4    AS HANDLE      NO-UNDO.
DEFINE VARIABLE hSoapHeaderEntryref5    AS HANDLE      NO-UNDO.
DEFINE VARIABLE hXDocument              AS HANDLE      NO-UNDO.
DEFINE VARIABLE hOASSecurity            AS HANDLE      NO-UNDO.
DEFINE VARIABLE hOASUsernameToken       AS HANDLE      NO-UNDO.
DEFINE VARIABLE hOASUserName            AS HANDLE      NO-UNDO.
DEFINE VARIABLE hOASPassword            AS HANDLE      NO-UNDO.
DEFINE VARIABLE hOASPasswordType        AS HANDLE      NO-UNDO.
DEFINE VARIABLE hOASNonce               AS HANDLE      NO-UNDO.
DEFINE VARIABLE hWSUCreated             AS HANDLE      NO-UNDO.
DEFINE VARIABLE hADDMessageID           AS HANDLE      NO-UNDO.
DEFINE VARIABLE hADDTo                  AS HANDLE      NO-UNDO.
DEFINE VARIABLE hADDAction              AS HANDLE      NO-UNDO.
DEFINE VARIABLE hAMA_SecurityHostedUser AS HANDLE      NO-UNDO.
DEFINE VARIABLE hUserId                 AS HANDLE      NO-UNDO.
DEFINE VARIABLE hTxt                    AS HANDLE      NO-UNDO.
DEFINE VARIABLE hAttr                   AS HANDLE      NO-UNDO.
DEFINE VARIABLE hRootNode               AS HANDLE      NO-UNDO.

/* Namespaces */
DEFINE VARIABLE cNSAddressing           AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cNSSecurity             AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cNSSecurityUtils        AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cNSAmaSec               AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cMessageId              AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cUserName               AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cPasswordClear          AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cPasswordDigest         AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cCreated                AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cAction                 AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cTo                     AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cNonceB64               AS CHARACTER   NO-UNDO.
DEFINE VARIABLE cOfficeId               AS CHARACTER   NO-UNDO.
DEFINE VARIABLE mNonce                  AS MEMPTR      NO-UNDO.

ASSIGN 
    cNSAddressing     = "http://www.w3.org/2005/08/addressing"
    cNSSecurity       = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    cNSSecurityUtils  = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    cNSAmaSec         = "http://illkeepthisasecret..."
    cAction           = "http://illkeepthisasecret..."
    cTo               = "https://noded1.production.webservices.amadeus.com/1ASIWIBNAIT".

ASSIGN
    cUserName         = "usernamegoeshere"
    cOfficeId         = "anothersettinggoeshere"
    cPasswordClear    = "passwordgoeshere".

/* Delete header when done! */
ASSIGN 
    plDeleteOnDone = TRUE.

CREATE SOAP-HEADER ghSoapHeader.
CREATE SOAP-HEADER-ENTRYREF hSoapHeaderEntryref1.
CREATE SOAP-HEADER-ENTRYREF hSoapHeaderEntryref2.
CREATE SOAP-HEADER-ENTRYREF hSoapHeaderEntryref3.
CREATE SOAP-HEADER-ENTRYREF hSoapHeaderEntryref4.
CREATE SOAP-HEADER-ENTRYREF hSoapHeaderEntryref5.


CREATE X-DOCUMENT hXDocument.
CREATE X-NODEREF hRootNode.
CREATE X-NODEREF hOASSecurity.
CREATE X-NODEREF hTxt.
CREATE X-NODEREF hOASUsernameToken.
CREATE X-NODEREF hOASPassword.
CREATE X-NODEREF hOASUserName.
CREATE X-NODEREF hOASNonce.
CREATE X-NODEREF hADDMessageID.
CREATE X-NODEREF hADDTo.
CREATE X-NODEREF hADDAction.
CREATE X-NODEREF hWSUCreated.
CREATE X-NODEREF hAMA_SecurityHostedUser.
CREATE X-NODEREF hUserId.


/* Not DATETIME-TZ! */
DEFINE VARIABLE dtZuluNow AS DATETIME    NO-UNDO.

/* Genereate a random key and base64-encode it */
SET-SIZE(mNonce) = 16.

mNonce = GENERATE-RANDOM-KEY.

cNonceB64 = BASE64-ENCODE(mNonce).

/* Get time in UTC/GMT/ZULU/Timezone 0 */
dtZuluNow = DATETIME-TZ(NOW,0).

/* Manipulate the date string to fit specs... */
ASSIGN 
    cMessageId = LC(SUBSTRING(STRING(GENERATE-UUID), 8, 20))
    cCreated = STRING(dtZuluNow, "9999-99-99THH:MM:SS") + ":000Z".
/* This is internal stuff for generating a digest... */
RUN generatePasswordDigest( mNonce, cCreated, cPasswordClear, OUTPUT cPasswordDigest).

/* Root node */
hXDocument:CREATE-NODE-NAMESPACE(hRootNode, "", "root", "element").
hXDocument:INSERT-BEFORE(hRootNode, ?).

/**** Addressing ****/
/* MessageID */
ghSoapHeader:ADD-HEADER-ENTRY(hSoapHeaderEntryref1).
hXDocument:CREATE-NODE-NAMESPACE(hADDMessageID, cNSAddressing, "MessageID", "ELEMENT").
hRootNode:APPEND-CHILD(hAddMessageID).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cMessageId.
hADDMessageId:APPEND-CHILD(hTxt).
hSoapHeaderEntryref1:SET-NODE(hADDMessageID).

/* Action */
ghSoapHeader:ADD-HEADER-ENTRY(hSoapHeaderEntryref2).
hXDocument:CREATE-NODE-NAMESPACE(hADDAction, cNSAddressing, "Action", "ELEMENT").
hRootNode:APPEND-CHILD(hADDAction).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cAction.
hADDAction:APPEND-CHILD(hTxt).
hSoapHeaderEntryref2:SET-NODE(hADDAction).

/* To */
ghSoapHeader:ADD-HEADER-ENTRY(hSoapHeaderEntryref3).
hXDocument:CREATE-NODE-NAMESPACE(hADDTo, cNSAddressing, "To", "ELEMENT").
hRootNode:APPEND-CHILD(hADDTo).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cTo.
hADDTo:APPEND-CHILD(hTxt).
hSoapHeaderEntryref3:SET-NODE(hADDTo).

/**** Security ****/
/* Root node */
ghSoapHeader:ADD-HEADER-ENTRY(hSoapHeaderEntryref4).
hXDocument:CREATE-NODE-NAMESPACE(hOASSecurity, cNSSecurity, "Security", "ELEMENT").
hRootNode:APPEND-CHILD(hOASSecurity).

/* UserNameToken node */
hXDocument:CREATE-NODE-NAMESPACE(hOASUsernameToken, cNSSecurity, "UsernameToken", "ELEMENT").
hOASUsernameToken:SET-ATTRIBUTE("Id", "UsernameToken-1").
hOASSecurity:INSERT-BEFORE(hOASUsernameToken, ?).

/* Username */
hXDocument:CREATE-NODE-NAMESPACE(hOASUserName, cNSSecurity, "Username", "ELEMENT").
hRootNode:APPEND-CHILD(hOASUserName).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cUserName.
hOASUserName:APPEND-CHILD(hTxt).
hOASUsernameToken:APPEND-CHILD(hOASUserName).

/* Nonce */
hXDocument:CREATE-NODE-NAMESPACE(hOASNonce, cNSSecurity, "Nonce", "ELEMENT").
hOASNonce:SET-ATTRIBUTE("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary").
hOASUsernameToken:APPEND-CHILD(hOASNonce).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cNonceB64.
hOASNonce:APPEND-CHILD(hTxt).

/* Password hash */
hXDocument:CREATE-NODE-NAMESPACE(hOASPassword, cNSSecurity, "Password", "ELEMENT").
hOASPassword:SET-ATTRIBUTE("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest").
hOASUsernameToken:APPEND-CHILD(hOASPassword).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cPasswordDigest.
hOASPassword:APPEND-CHILD(hTxt).

/* Created - timestamp */
hXDocument:CREATE-NODE-NAMESPACE(hWSUCreated, cNSSecurityUtils, "Created", "ELEMENT").
hOASUsernameToken:APPEND-CHILD(hWSUCreated).
hXDocument:CREATE-NODE(hTxt, "", "TEXT").
hTxt:NODE-VALUE = cCreated.
hWSUCreated:APPEND-CHILD(hTxt).
hSoapHeaderEntryref4:SET-NODE(hOASSecurity).

/**** AMA_SecurityHostedUser ****/
/* Root node */
ghSoapHeader:ADD-HEADER-ENTRY(hSoapHeaderEntryref5).
hXDocument:CREATE-NODE-NAMESPACE(hAMA_SecurityHostedUser, cNSAMASec, "AMA_SecurityHostedUser", "ELEMENT").
hRootNode:APPEND-CHILD(hAMA_SecurityHostedUser).


/* UserID */
hXDocument:CREATE-NODE-NAMESPACE(hUserID, cNSAMASec, "UserID", "ELEMENT").

hUserID:SET-ATTRIBUTE("AgentDutyCode", "SU"). 
hUserID:SET-ATTRIBUTE("RequestorType", "U").
hUserID:SET-ATTRIBUTE("PseudoCityCode", cOfficeId).
hUserID:SET-ATTRIBUTE("POS_Type", "1").

hAMA_SecurityHostedUser:APPEND-CHILD(hUserID).
hSoapHeaderEntryref5:SET-NODE(hAMA_SecurityHostedUser).

/* Output the header */
phHeader = ghSoapHeader.

/* Cleanup */
IF VALID-HANDLE(hOASSecurity) THEN 
    DELETE OBJECT hOASSecurity.
IF VALID-HANDLE(hOASUsernameToken) THEN
    DELETE OBJECT hOASUsernameToken.
IF VALID-HANDLE(hOASUserName) THEN
    DELETE OBJECT hOASUserName.
IF VALID-HANDLE(hOASPassword) THEN
    DELETE OBJECT hOASPassword.
IF VALID-HANDLE(hADDMessageID) THEN
    DELETE OBJECT hADDMessageID.  
IF VALID-HANDLE(hOASNonce) THEN
    DELETE OBJECT hOASNonce.
IF VALID-HANDLE(hTxt) THEN
    DELETE OBJECT hTxt.
IF VALID-HANDLE(hADDTo) THEN
    DELETE OBJECT hADDTo.    
IF VALID-HANDLE(hWSUCreated) THEN
    DELETE OBJECT hWSUCreated.
IF VALID-HANDLE(hADDAction) THEN
    DELETE OBJECT hADDAction.     
IF VALID-HANDLE(hXDocument) THEN
    DELETE OBJECT hXDocument.
IF VALID-HANDLE(hRootNode) THEN
    DELETE OBJECT hRootNode.
IF VALID-HANDLE(hSoapHeaderEntryRef1) THEN
    DELETE OBJECT hSoapHeaderEntryRef1.
IF VALID-HANDLE(hSoapHeaderEntryRef2) THEN
    DELETE OBJECT hSoapHeaderEntryRef2.
IF VALID-HANDLE(hSoapHeaderEntryRef3) THEN
    DELETE OBJECT hSoapHeaderEntryRef3.
IF VALID-HANDLE(hSoapHeaderEntryRef4) THEN
    DELETE OBJECT hSoapHeaderEntryRef4.
IF VALID-HANDLE(hSoapHeaderEntryRef5) THEN
    DELETE OBJECT hSoapHeaderEntryRef5.
IF VALID-HANDLE(hAMA_SecurityHostedUser) THEN
    DELETE OBJECT hAMA_SecurityHostedUser.
IF VALID-HANDLE(hUserId) THEN
    DELETE OBJECT hUserId.

END.
Jensd
  • 7,886
  • 2
  • 28
  • 37