Sample code for 30+ languages & platforms
DataFlex Requires Chilkat v11.7.0+

Box: Upload a Large File in Parts and Commit the Session

See more Box Examples

Run the reusable setup once per HttpCurl object, then select names and execute the operation. The setup is repeated here only to make this published example independently runnable. Resolve documents by name, then perform the explicit chunked-upload session, part, and commit loop. Supports 20 MB through 2 GB using int. Saves recovery information and honors bounded commit Retry-After.

See the Box API guide for setup and the complete workflow.

Chilkat DataFlex Downloads

DataFlex
Use ChilkatAx-win32.pkg

Procedure Test
    Boolean iSuccess
    Handle hoCurl
    Handle hoSetupCommand
    Integer iSetupReplacements
    String sParentName
    String sFolderName
    Handle hoAuthorizedCommand1
    Integer iHeaderReplacement1
    String sLocalPath
    Handle hoFile
    String sSizeString
    Handle hoSizeText
    Variant vSizeMatch
    Handle hoSizeMatch
    Integer iFileSize
    Handle hoHttp
    Variant vResponse
    Handle hoResponse
    Variant vRequest
    Handle hoRequest
    Handle hoSession
    Integer iPartSize
    String sUploadUrl
    String sCommitUrl
    Handle hoHash
    String sFileDigest
    Variant vCommit
    Handle hoCommit
    Handle hoPartResponse
    Variant vChunk
    Handle hoChunk
    Handle hoRange
    Handle hoDigestHeader
    Integer iOffset
    Integer iPartIndex
    Integer iPartLength
    Integer iLastByte
    String sPartDigest
    Integer iAttempts
    Integer iWaitSeconds
    Handle hoRetryHeader
    Variant vRetryMatch
    Handle hoRetryMatch
    Boolean iCompleted
    String sTemp1
    Integer iTemp1
    Boolean bTemp1

    Move False To iSuccess

    //  Requires Chilkat 11.7.0 or later and a previously unlocked Chilkat bundle.
    //  See Global Unlock Sample.
    //  Guide: Box API Guide.

    Move False To iSuccess

    //  BEGIN ONE-TIME SETUP
    //  This block is repeated so each published example can run on its own.
    //  In an application, run the setup example once and pass the SAME curl object
    //  to operation code below. Do not recreate it for every operation.
    //  See Reusable Box HttpCurl Setup.
    Get Create (RefClass(cComChilkatHttpCurl)) To hoCurl
    If (Not(IsComObjectCreated(hoCurl))) Begin
        Send CreateComObject of hoCurl
    End
    Send ComSetVar To hoCurl "access_token" "BOX_ACCESS_TOKEN"
    Send ComSetVar To hoCurl "parent_name" "/"
    //  Box's fixed root ID is an implementation detail; callers supply names.
    Send ComSetVar To hoCurl "parent_id" "0"
    Get Create (RefClass(cComChilkatStringBuilder)) To hoSetupCommand
    If (Not(IsComObjectCreated(hoSetupCommand))) Begin
        Send CreateComObject of hoSetupCommand
    End

    //  Register findParent; registration sends no request.
    Get ComSetString Of hoSetupCommand 'curl -X GET "https://api.box.com/2.0/folders/0/items?limit=1000&fields=id,type,name" -H "Authorization: Bearer ACCESS_TOKEN"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoSetupCommand "ACCESS_TOKEN" sTemp1 To iSetupReplacements
    Get ComGetAsString Of hoSetupCommand To sTemp1
    Get ComAddFunction Of hoCurl "findParent" sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  Register findFolder; registration sends no request.
    Get ComSetString Of hoSetupCommand 'curl -X GET "https://api.box.com/2.0/folders/{{parent_id}}/items?limit=1000&fields=id,type,name" -H "Authorization: Bearer ACCESS_TOKEN"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoSetupCommand "ACCESS_TOKEN" sTemp1 To iSetupReplacements
    Get ComGetAsString Of hoSetupCommand To sTemp1
    Get ComAddFunction Of hoCurl "findFolder" sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  Register findFile; registration sends no request.
    Get ComSetString Of hoSetupCommand 'curl -X GET "https://api.box.com/2.0/folders/{{folder_id}}/items?limit=1000&fields=id,type,name" -H "Authorization: Bearer ACCESS_TOKEN"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoSetupCommand "ACCESS_TOKEN" sTemp1 To iSetupReplacements
    Get ComGetAsString Of hoSetupCommand To sTemp1
    Get ComAddFunction Of hoCurl "findFile" sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  Register findDestination; registration sends no request.
    Get ComSetString Of hoSetupCommand 'curl -X GET "https://api.box.com/2.0/folders/0/items?limit=1000&fields=id,type,name" -H "Authorization: Bearer ACCESS_TOKEN"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoSetupCommand "ACCESS_TOKEN" sTemp1 To iSetupReplacements
    Get ComGetAsString Of hoSetupCommand To sTemp1
    Get ComAddFunction Of hoCurl "findDestination" sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  Register getFileDetails; registration sends no request.
    Get ComSetString Of hoSetupCommand 'curl -X GET "https://api.box.com/2.0/files/{{file_id}}?fields=id,sha1,file_version" -H "Authorization: Bearer ACCESS_TOKEN"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoSetupCommand "ACCESS_TOKEN" sTemp1 To iSetupReplacements
    Get ComGetAsString Of hoSetupCommand To sTemp1
    Get ComAddFunction Of hoCurl "getFileDetails" sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  Register getEnterprise; registration sends no request.
    Get ComSetString Of hoSetupCommand 'curl -X GET "https://api.box.com/2.0/users/me?fields=enterprise" -H "Authorization: Bearer ACCESS_TOKEN"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoSetupCommand "ACCESS_TOKEN" sTemp1 To iSetupReplacements
    Get ComGetAsString Of hoSetupCommand To sTemp1
    Get ComAddFunction Of hoCurl "getEnterprise" sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  Register output rules once. Chilkat 11.7.0 evaluates {{name}} variables
    //  when extracting each output, using the current value at that time.
    Get ComAddOutput2 Of hoCurl "findParent" "entries" "name" "{{parent_name}}" True "id" "parent_id" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComAddOutput2 Of hoCurl "findFolder" "entries" "name" "{{folder_name}}" True "id" "folder_id" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComAddOutput2 Of hoCurl "findFile" "entries" "name" "{{file_name}}" True "id" "file_id" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComAddOutput2 Of hoCurl "findDestination" "entries" "name" "{{destination_name}}" True "id" "destination_id" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComAddOutput Of hoCurl "getFileDetails" "file_version.id" "file_version_id" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComAddOutput Of hoCurl "getFileDetails" "sha1" "file_sha1" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComAddOutput Of hoCurl "getEnterprise" "enterprise.id" "enterprise_id" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  END ONE-TIME SETUP
    //  The name lookups below assume each listing fits in one page (1000 items).
    //  Parent and destination names select root children; folder_name selects a child
    //  of parent_name. "/" selects the root parent. These are not arbitrary paths.

    //  BEGIN OPERATION -- keep the configured object when reusing it.
    //  Inputs are names. Retain cached IDs when the selected names have not changed.
    Move "/" To sParentName
    Move "documents" To sFolderName
    Get ComGetVar Of hoCurl "parent_name" To sTemp1
    Get ComVarDefined Of hoCurl "parent_id" To bTemp1
    If ((sTemp1 <> sParentName) Or ((sParentName = "/") And (bTemp1 = False))) Begin
        Send ComSetVar To hoCurl "parent_name" sParentName
        Send ComClearVar To hoCurl "parent_id"
        Send ComClearVar To hoCurl "folder_id"
        Send ComClearVar To hoCurl "file_id"
        Send ComClearVar To hoCurl "file_version_id"
        Send ComClearVar To hoCurl "file_sha1"
        If (sParentName = "/") Begin
            Send ComSetVar To hoCurl "parent_id" "0"
        End

    End

    Get ComGetVar Of hoCurl "folder_name" To sTemp1
    If (sTemp1 <> sFolderName) Begin
        Send ComSetVar To hoCurl "folder_name" sFolderName
        Send ComClearVar To hoCurl "folder_id"
        Send ComClearVar To hoCurl "file_id"
        Send ComClearVar To hoCurl "file_version_id"
        Send ComClearVar To hoCurl "file_sha1"
    End

    //  Resolve the named destination first. HttpCurl does not expand variables
    //  inside multipart -F attributes in the tested build, so this upload is two phases.
    Send ComClearTargetOutput To hoCurl "*"
    Get Create (RefClass(cComChilkatStringBuilder)) To hoAuthorizedCommand1
    If (Not(IsComObjectCreated(hoAuthorizedCommand1))) Begin
        Send CreateComObject of hoAuthorizedCommand1
    End
    Get ComSetString Of hoAuthorizedCommand1 'curl -X GET "https://api.box.com/2.0/folders/{{folder_id}}?fields=id" -H "Authorization: Bearer {{access_token}}"' To iSuccess
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Get ComReplace Of hoAuthorizedCommand1 "{{access_token}}" sTemp1 To iHeaderReplacement1
    Get ComGetAsString Of hoAuthorizedCommand1 To sTemp1
    Get ComDoYourThing Of hoCurl sTemp1 To iSuccess
    Get ComStatusCode Of hoCurl To iTemp1
    If ((iSuccess <> True) Or (iTemp1 <> 200)) Begin
        Get ComStatusCode Of hoCurl To iTemp1
        Showln "Box request failed. HTTP status: " iTemp1
        Get ComFailReason Of hoCurl To iTemp1
        Showln "Failure reason: " iTemp1
        Get ComResponseBodyStr Of hoCurl To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    //  This example limits size to 2 GB so offsets fit all generated languages' integers.
    //  The source must not change during hashing or upload. Create qa_output first.
    Move "qa_data/large-document.zip" To sLocalPath
    Get Create (RefClass(cComCkFileAccess)) To hoFile
    If (Not(IsComObjectCreated(hoFile))) Begin
        Send CreateComObject of hoFile
    End
    //  Check the decimal size before converting so an oversized file cannot overflow int.
    Get ComFileSizeStr Of hoFile sLocalPath To sSizeString
    Get ComLastMethodSuccess Of hoFile To bTemp1
    If (bTemp1 <> True) Begin
        Get ComLastErrorText Of hoFile To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get Create (RefClass(cComChilkatStringBuilder)) To hoSizeText
    If (Not(IsComObjectCreated(hoSizeText))) Begin
        Send CreateComObject of hoSizeText
    End
    Get ComSetString Of hoSizeText sSizeString To iSuccess
    Get Create (RefClass(cComChilkatJsonObject)) To hoSizeMatch
    If (Not(IsComObjectCreated(hoSizeMatch))) Begin
        Send CreateComObject of hoSizeMatch
    End
    Get ComRegexMatch Of hoSizeText "^([0-9]{1,9}|1[0-9]{9}|2000000000)$"     Get pvComObject of hoSizeMatch to vSizeMatch
vSizeMatch 100 To iTemp1
    If (iTemp1 <> 1) Begin
        Showln "Choose an input between 20 MB and 2 GB for this example."
        Procedure_Return
    End

    Move (sSizeString) To iFileSize
    If (iFileSize < 20971520) Begin
        Showln "Choose an input between 20 MB and 2 GB for this example."
        Procedure_Return
    End

    Get Create (RefClass(cComChilkatHttp)) To hoHttp
    If (Not(IsComObjectCreated(hoHttp))) Begin
        Send CreateComObject of hoHttp
    End
    Get ComGetVar Of hoCurl "access_token" To sTemp1
    Set ComAuthToken Of hoHttp To sTemp1
    Get Create (RefClass(cComChilkatHttpResponse)) To hoResponse
    If (Not(IsComObjectCreated(hoResponse))) Begin
        Send CreateComObject of hoResponse
    End
    Get Create (RefClass(cComChilkatJsonObject)) To hoRequest
    If (Not(IsComObjectCreated(hoRequest))) Begin
        Send CreateComObject of hoRequest
    End
    Get ComGetVar Of hoCurl "folder_id" To sTemp1
    Get ComUpdateString Of hoRequest "folder_id" sTemp1 To iSuccess
    Get ComUpdateString Of hoRequest "file_name" "large-document.zip" To iSuccess
    Get ComUpdateInt Of hoRequest "file_size" iFileSize To iSuccess
    Get pvComObject of hoRequest to vRequest
    Get pvComObject of hoResponse to vResponse
    Get ComHttpJson Of hoHttp "POST" "https://upload.box.com/api/2.0/files/upload_sessions" vRequest "application/json" vResponse To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoHttp To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComStatusCode Of hoResponse To iTemp1
    If (iTemp1 <> 201) Begin
        Get ComBodyStr Of hoResponse To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get Create (RefClass(cComChilkatJsonObject)) To hoSession
    If (Not(IsComObjectCreated(hoSession))) Begin
        Send CreateComObject of hoSession
    End
    Get ComBodyStr Of hoResponse To sTemp1
    Get ComLoad Of hoSession sTemp1 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoSession To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComWriteFile Of hoSession "qa_output/box_upload_session.json" To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoSession To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComIntOf Of hoSession "part_size" To iPartSize
    Get ComStringOf Of hoSession "session_endpoints.upload_part" To sUploadUrl
    Get ComStringOf Of hoSession "session_endpoints.commit" To sCommitUrl
    If ((iPartSize <= 0) Or (sUploadUrl = "") Or (sCommitUrl = "")) Begin
        Showln "Invalid session response."
        Procedure_Return
    End

    Get Create (RefClass(cComChilkatCrypt2)) To hoHash
    If (Not(IsComObjectCreated(hoHash))) Begin
        Send CreateComObject of hoHash
    End
    Set ComHashAlgorithm Of hoHash To "sha1"
    Set ComEncodingMode Of hoHash To "base64"
    Get ComHashFileENC Of hoHash sLocalPath To sFileDigest
    Get ComLastMethodSuccess Of hoHash To bTemp1
    If (bTemp1 <> True) Begin
        Get ComLastErrorText Of hoHash To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get ComFileOpen Of hoFile sLocalPath 2147483648 1 3 0 To iSuccess
    If (iSuccess <> True) Begin
        Get ComLastErrorText Of hoFile To sTemp1
        Showln sTemp1
        Procedure_Return
    End

    Get Create (RefClass(cComChilkatJsonObject)) To hoCommit
    If (Not(IsComObjectCreated(hoCommit))) Begin
        Send CreateComObject of hoCommit
    End
    Get Create (RefClass(cComChilkatJsonObject)) To hoPartResponse
    If (Not(IsComObjectCreated(hoPartResponse))) Begin
        Send CreateComObject of hoPartResponse
    End
    Get Create (RefClass(cComChilkatBinData)) To hoChunk
    If (Not(IsComObjectCreated(hoChunk))) Begin
        Send CreateComObject of hoChunk
    End
    Get Create (RefClass(cComChilkatStringBuilder)) To hoRange
    If (Not(IsComObjectCreated(hoRange))) Begin
        Send CreateComObject of hoRange
    End
    Get Create (RefClass(cComChilkatStringBuilder)) To hoDigestHeader
    If (Not(IsComObjectCreated(hoDigestHeader))) Begin
        Send CreateComObject of hoDigestHeader
    End
    Move 0 To iOffset
    Move 0 To iPartIndex

    While (iOffset < iFileSize)
        Get ComClear Of hoChunk To iSuccess
        Get pvComObject of hoChunk to vChunk
        Get ComFileReadBd Of hoFile iPartSize vChunk To iSuccess
        If (iSuccess <> True) Begin
            Get ComLastErrorText Of hoFile To sTemp1
            Showln sTemp1
            Send ComFileClose To hoFile
            Procedure_Return
        End

        Get ComNumBytes Of hoChunk To iPartLength
        If (iPartLength <= 0) Begin
            Showln "Unexpected end of file."
            Send ComFileClose To hoFile
            Procedure_Return
        End

        Move (iOffset + (iPartLength - 1)) To iLastByte
        Send ComClear To hoRange
        Get ComAppend Of hoRange "bytes " To iSuccess
        Get ComAppendInt Of hoRange iOffset To iSuccess
        Get ComAppend Of hoRange "-" To iSuccess
        Get ComAppendInt Of hoRange iLastByte To iSuccess
        Get ComAppend Of hoRange "/" To iSuccess
        Get ComAppendInt Of hoRange iFileSize To iSuccess
        Get pvComObject of hoChunk to vChunk
        Get ComHashBdENC Of hoHash vChunk To sPartDigest
        Get ComLastMethodSuccess Of hoHash To bTemp1
        If (bTemp1 <> True) Begin
            Get ComLastErrorText Of hoHash To sTemp1
            Showln sTemp1
            Send ComFileClose To hoFile
            Procedure_Return
        End

        Get ComSetString Of hoDigestHeader "sha=" To iSuccess
        Get ComAppend Of hoDigestHeader sPartDigest To iSuccess
        Get ComGetAsString Of hoDigestHeader To sTemp1
        Send ComSetRequestHeader To hoHttp "Digest" sTemp1
        Get ComGetAsString Of hoRange To sTemp1
        Send ComSetRequestHeader To hoHttp "Content-Range" sTemp1
        Get pvComObject of hoChunk to vChunk
        Get pvComObject of hoResponse to vResponse
        Get ComHttpBd Of hoHttp "PUT" sUploadUrl vChunk "application/octet-stream" vResponse To iSuccess
        Get ComStatusCode Of hoResponse To iTemp1
        If ((iSuccess <> True) Or (iTemp1 <> 200)) Begin
            Get ComStatusCode Of hoResponse To iTemp1
            Showln "Part failed. HTTP status: " iTemp1
            Get ComBodyStr Of hoResponse To sTemp1
            Showln sTemp1
            Showln "Inspect the saved session and server list_parts before retrying this part."
            Send ComFileClose To hoFile
            Procedure_Return
        End

        Get ComBodyStr Of hoResponse To sTemp1
        Get ComLoad Of hoPartResponse sTemp1 To iSuccess
        If (iSuccess <> True) Begin
            Send ComFileClose To hoFile
            Procedure_Return
        End

        Set ComI Of hoCommit To iPartIndex
        //  Copy the server's acknowledged part fields into the commit manifest.
        Get ComStringOf Of hoPartResponse "part.part_id" To sTemp1
        Get ComUpdateString Of hoCommit "parts[i].part_id" sTemp1 To iSuccess
        Get ComIntOf Of hoPartResponse "part.offset" To iTemp1
        Get ComUpdateInt Of hoCommit "parts[i].offset" iTemp1 To iSuccess
        Get ComIntOf Of hoPartResponse "part.size" To iTemp1
        Get ComUpdateInt Of hoCommit "parts[i].size" iTemp1 To iSuccess
        Get ComStringOf Of hoPartResponse "part.sha1" To sTemp1
        Get ComUpdateString Of hoCommit "parts[i].sha1" sTemp1 To iSuccess
        Get ComWriteFile Of hoCommit "qa_output/box_upload_parts.json" To iSuccess
        If (iSuccess <> True) Begin
            Get ComLastErrorText Of hoCommit To sTemp1
            Showln sTemp1
            Send ComFileClose To hoFile
            Procedure_Return
        End

        Move (iOffset + iPartLength) To iOffset
        Move (iPartIndex + 1) To iPartIndex
    Loop

    Send ComFileClose To hoFile
    Send ComRemoveRequestHeader To hoHttp "Content-Range"
    Get ComSetString Of hoDigestHeader "sha=" To iSuccess
    Get ComAppend Of hoDigestHeader sFileDigest To iSuccess
    Get ComGetAsString Of hoDigestHeader To sTemp1
    Send ComSetRequestHeader To hoHttp "Digest" sTemp1
    Move 0 To iAttempts

    Get Create (RefClass(cComChilkatStringBuilder)) To hoRetryHeader
    If (Not(IsComObjectCreated(hoRetryHeader))) Begin
        Send CreateComObject of hoRetryHeader
    End
    Get Create (RefClass(cComChilkatJsonObject)) To hoRetryMatch
    If (Not(IsComObjectCreated(hoRetryMatch))) Begin
        Send CreateComObject of hoRetryMatch
    End
    Move False To iCompleted
    While ((iAttempts < 10) And (iCompleted = False))
        Get pvComObject of hoCommit to vCommit
        Get pvComObject of hoResponse to vResponse
        Get ComHttpJson Of hoHttp "POST" sCommitUrl vCommit "application/json" vResponse To iSuccess
        If (iSuccess <> True) Begin
            Get ComStatusCode Of hoResponse To iTemp1
            Showln "Commit failed: " iTemp1
            Get ComBodyStr Of hoResponse To sTemp1
            Showln sTemp1
            Procedure_Return
        End

        Get ComStatusCode Of hoResponse To iTemp1
        If (iTemp1 = 201) Begin
            Move True To iCompleted
        End
        Else Begin
            Get ComStatusCode Of hoResponse To iTemp1
            If (iTemp1 <> 202) Begin
                Get ComStatusCode Of hoResponse To iTemp1
                Showln "Unexpected commit status: " iTemp1
                Procedure_Return
            End

            //  Box's asynchronous commit response supplies Retry-After in seconds.
            Get ComGetHeaderField Of hoResponse "Retry-After" To sTemp1
            Get ComSetString Of hoRetryHeader sTemp1 To iSuccess
            Get ComRegexMatch Of hoRetryHeader "^[0-9]{1,2}$"             Get pvComObject of hoRetryMatch to vRetryMatch
vRetryMatch 100 To iTemp1
            If (iTemp1 <> 1) Begin
                Get ComGetAsString Of hoRetryHeader To sTemp1
                Showln "Commit pending. Preserve the session and inspect Retry-After before resuming: " sTemp1
                Procedure_Return
            End

            Get ComGetAsString Of hoRetryHeader To sTemp1
            Move (sTemp1) To iWaitSeconds
            If ((iWaitSeconds < 1) Or (iWaitSeconds > 60)) Begin
                Get ComGetHeaderField Of hoResponse "Retry-After" To sTemp1
                Showln "Commit pending. Resume commit after Retry-After: " sTemp1
                Procedure_Return
            End

            Send ComSleepMs To hoHttp (iWaitSeconds * 1000)
        End

        Move (iAttempts + 1) To iAttempts
    Loop

    If (iCompleted = False) Begin
        Showln "Commit still pending; retain session and part manifests to resume commit later."
        Procedure_Return
    End

    Get ComBodyStr Of hoResponse To sTemp1
    Showln "Upload committed: " sTemp1


End_Procedure