Codeunit 1297 Http Web Request Mgt.

App
Base Application
Namespace
System.Integration
Versions
17-28

Procedures, 41Events, 1Obsolete, 2

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/System/HttpWebRequestMgt.Codeunit.al561 lines, Copyright (c) Microsoft Corporation. MIT

namespace System.Integration;

using System;
using System.Environment;
using System.IO;
using System.Reflection;
using System.Text;
using System.Utilities;
using System.Xml;

codeunit 1297 "Http Web Request Mgt."
{
    var
        [NonDebuggable]
        HttpWebRequest: DotNet HttpWebRequest;
        TraceLogEnabled: Boolean;
        InvalidUrlErr: Label 'The URL is not valid.';
        NonSecureUrlErr: Label 'The URL is not secure.';
        GlobalSkipCheckHttps: Boolean;
        GlobalProgressDialogEnabled: Boolean;
        InternalErr: Label 'The remote service has returned the following error message:\\';
        NoCookieForYouErr: Label 'The web request has no cookies.';
        TimeoutErr: Label 'The server timed out waiting for the request.';

    [Scope('OnPrem')]
    procedure GetResponse(var ResponseInStream: InStream; var HttpStatusCode: DotNet HttpStatusCode; var ResponseHeaders: DotNet NameValueCollection): Boolean
    var
        WebRequestHelper: Codeunit "Web Request Helper";
        HttpWebResponse: DotNet HttpWebResponse;
    begin
        exit(WebRequestHelper.GetWebResponse(HttpWebRequest, HttpWebResponse, ResponseInStream, HttpStatusCode,
            ResponseHeaders, GlobalProgressDialogEnabled));
    end;

    [Scope('OnPrem')]
    procedure GetResponseStream(var ResponseInStream: InStream): Boolean
    var
        WebRequestHelper: Codeunit "Web Request Helper";
        HttpWebResponse: DotNet HttpWebResponse;
        HttpStatusCode: DotNet HttpStatusCode;
        ResponseHeaders: DotNet NameValueCollection;
    begin
        exit(WebRequestHelper.GetWebResponse(HttpWebRequest, HttpWebResponse, ResponseInStream, HttpStatusCode,
            ResponseHeaders, GlobalProgressDialogEnabled));
    end;

    [TryFunction]
    [Scope('OnPrem')]
    procedure ProcessFaultResponse(SupportInfo: Text)
    begin
        ProcessFaultXMLResponse(SupportInfo, '', '', '');
    end;

    [TryFunction]
    [Scope('OnPrem')]
    procedure ProcessFaultXMLResponse(SupportInfo: Text; NodePath: Text; Prefix: Text; NameSpace: Text)
    var
        HttpStatusCode: DotNet HttpStatusCode;
        ResponseHeaders: DotNet NameValueCollection;
    begin
        ProcessFaultXMLResponse(SupportInfo, NodePath, Prefix, NameSpace, HttpStatusCode, ResponseHeaders);
    end;

    [TryFunction]
    [Scope('OnPrem')]
    procedure ProcessFaultXMLResponse(SupportInfo: Text; NodePath: Text; Prefix: Text; NameSpace: Text; var HttpStatusCode: DotNet HttpStatusCode; var ResponseHeaders: DotNet NameValueCollection)
    var
        TempBlobReturn: Codeunit "Temp Blob";
        WebRequestHelper: Codeunit "Web Request Helper";
        XMLDOMMgt: Codeunit "XML DOM Management";
        WebException: DotNet WebException;
        WebExceptionResponse: DotNet HttpWebResponse;
        XmlDoc: DotNet XmlDocument;
        ResponseInputStream: InStream;
        ErrorText: Text;
        ServiceURL: Text;
    begin
        ErrorText := WebRequestHelper.GetWebResponseError(WebException, ServiceURL);

        WebExceptionResponse := WebException.Response();
        if not IsNull(WebExceptionResponse) then begin
            HttpStatusCode := WebExceptionResponse.StatusCode();
            ResponseHeaders := WebExceptionResponse.Headers();
            ResponseInputStream := WebExceptionResponse.GetResponseStream();

            TraceLogStreamToTempFile(ResponseInputStream, 'WebExceptionResponse', TempBlobReturn);

            if NodePath <> '' then
                if TryLoadXMLResponse(ResponseInputStream, XmlDoc) then
                    if Prefix = '' then
                        ErrorText := XMLDOMMgt.FindNodeText(XmlDoc.DocumentElement, NodePath)
                    else
                        ErrorText := XMLDOMMgt.FindNodeTextWithNamespace(XmlDoc.DocumentElement, NodePath, Prefix, NameSpace);
        end;

        if ErrorText = '' then
            ErrorText := WebException.Message;

        ErrorText := InternalErr + ErrorText;

        if SupportInfo <> '' then
            ErrorText += '\\' + SupportInfo;

        Error(ErrorText);
    end;

    [TryFunction]
    procedure ProcessFaultJsonResponse(var ResponseJson: Text)
    var
        WebRequestHelper: Codeunit "Web Request Helper";
        WebException: DotNet WebException;
        ServiceURL: Text;
        HttpError: Text;
    begin
        ResponseJson := '';
        HttpError := WebRequestHelper.GetWebResponseError(WebException, ServiceURL);
        ParseWebResponseError(ResponseJson, WebException);

        if ResponseJson = '' then
            Error(HttpError);
    end;

    procedure ParseFaultJsonResponse(ResponseJson: Text): Text
    var
        JSONMgt: Codeunit "JSON Management";
        "code": Text;
        name: Text;
        description: Text;
    begin
        if JSONMgt.GetJsonWebResponseError(ResponseJson, code, name, description) then
            exit(StrSubstNo('Http error %1 (%2)\%3', code, name, description));
    end;

    [TryFunction]
    procedure CheckUrl(Url: Text)
    var
        Uri: DotNet Uri;
        UriKind: DotNet UriKind;
    begin
        if not Uri.TryCreate(Url, UriKind.Absolute, Uri) then
            Error(InvalidUrlErr);

        if not GlobalSkipCheckHttps and not (Uri.Scheme = 'https') then
            Error(NonSecureUrlErr);
    end;

    [Scope('OnPrem')]
    procedure GetUrl(): Text
    begin
        exit(HttpWebRequest.RequestUri.AbsoluteUri);
    end;

    [Scope('OnPrem')]
    procedure GetUri(): Text
    begin
        exit(HttpWebRequest.RequestUri.PathAndQuery);
    end;

    [Scope('OnPrem')]
    procedure GetMethod(): Text
    begin
        exit(HttpWebRequest.Method);
    end;

    local procedure TraceLogStreamToTempFile(var ToLogInStream: InStream; Name: Text; var TempBlobTraceLog: Codeunit "Temp Blob")
    var
        Trace: Codeunit Trace;
    begin
        if TraceLogEnabled then
            Trace.LogStreamToTempFile(ToLogInStream, Name, TempBlobTraceLog);
    end;

    [TryFunction]
    [Scope('OnPrem')]
    procedure TryLoadXMLResponse(ResponseInputStream: InStream; var XmlDoc: DotNet XmlDocument)
    var
        XMLDOMManagement: Codeunit "XML DOM Management";
    begin
        XMLDOMManagement.LoadXMLDocumentFromInStream(ResponseInputStream, XmlDoc);
    end;

    procedure SetTraceLogEnabled(Enabled: Boolean)
    begin
        TraceLogEnabled := Enabled;
    end;

    procedure DisableUI()
    begin
        GlobalProgressDialogEnabled := false;
    end;

    [Scope('OnPrem')]
    procedure Initialize(URL: Text)
    var
        EnvironmentInfo: Codeunit "Environment Information";
    begin
        if not EnvironmentInfo.IsSaaS() then
            OnOverrideUrl(URL);

        HttpWebRequest := HttpWebRequest.Create(URL);
        SetDefaults();
    end;

    local procedure SetDefaults()
    var
        CookieContainer: DotNet CookieContainer;
    begin
        HttpWebRequest.Method := 'GET';
        HttpWebRequest.KeepAlive := true;
        HttpWebRequest.AllowAutoRedirect := true;
        HttpWebRequest.UseDefaultCredentials := true;
        HttpWebRequest.Timeout := 60000;
        HttpWebRequest.Accept('application/xml');
        HttpWebRequest.ContentType('application/xml');
        CookieContainer := CookieContainer.CookieContainer();
        HttpWebRequest.CookieContainer := CookieContainer;

        GlobalSkipCheckHttps := true;
        GlobalProgressDialogEnabled := GuiAllowed;
        TraceLogEnabled := true;
    end;

    [Scope('OnPrem')]
    [NonDebuggable]
    procedure AddBodyAsText(BodyText: Text)
    var
        Encoding: DotNet Encoding;
    begin
        // Assume UTF8
        AddBodyAsTextWithEncoding(BodyText, Encoding.UTF8);
    end;

    [Scope('OnPrem')]
    procedure AddBodyAsAsciiText(BodyText: Text)
    var
        Encoding: DotNet Encoding;
    begin
        AddBodyAsTextWithEncoding(BodyText, Encoding.ASCII);
    end;

    internal procedure AddBodyAsTextWithEncoding(BodyText: Text; Encoding: DotNet Encoding)
    var
        RequestStr: DotNet Stream;
        StreamWriter: DotNet StreamWriter;
    begin
        RequestStr := HttpWebRequest.GetRequestStream();
        StreamWriter := StreamWriter.StreamWriter(RequestStr, Encoding);
        StreamWriter.Write(BodyText);
        StreamWriter.Flush();
        StreamWriter.Close();
        StreamWriter.Dispose();
    end;

    [Scope('OnPrem')]
    procedure SetTimeout(NewTimeout: Integer)
    begin
        HttpWebRequest.Timeout := NewTimeout;
    end;

    [Scope('OnPrem')]
    procedure SetMethod(Method: Text)
    begin
        HttpWebRequest.Method := Method;
    end;

    [Scope('OnPrem')]
    procedure SetDecompresionMethod(DecompressionMethod: DotNet DecompressionMethods)
    begin
        HttpWebRequest.AutomaticDecompression := DecompressionMethod;
    end;

    [Scope('OnPrem')]
    procedure SetContentType(ContentType: Text)
    begin
        HttpWebRequest.ContentType := ContentType;
    end;

    [Scope('OnPrem')]
    procedure SetReturnType(ReturnType: Text)
    begin
        HttpWebRequest.Accept := ReturnType;
    end;

    [Scope('OnPrem')]
    procedure SetProxy(ProxyUrl: Text)
    var
        WebProxy: DotNet WebProxy;
    begin
        if ProxyUrl = '' then
            exit;

        WebProxy := WebProxy.WebProxy(ProxyUrl);

        HttpWebRequest.Proxy := WebProxy;
    end;

    procedure SetExpect(expectValue: Boolean)
    begin
        HttpWebRequest.ServicePoint.Expect100Continue := expectValue;
    end;

    [Scope('OnPrem')]
    procedure SetContentLength(ContentLength: BigInteger)
    begin
        HttpWebRequest.ContentLength := ContentLength;
    end;

    [Scope('OnPrem')]
    procedure AddSecurityProtocolTls12()
    var
        Convert: DotNet Convert;
        SecurityProtocolType: DotNet SecurityProtocolType;
        SecurityProtocol: Integer;
    begin
        SecurityProtocol := Convert.ToInt32(SecurityProtocolType.Tls12);
        AddSecurityProtocol(SecurityProtocol);
    end;

    local procedure AddSecurityProtocol(SecurityProtocol: Integer)
    var
        TypeHelper: Codeunit "Type Helper";
        Convert: DotNet Convert;
        ServicePointManager: DotNet ServicePointManager;
        CurrentSecurityProtocol: Integer;
    begin
        CurrentSecurityProtocol := Convert.ToInt32(ServicePointManager.SecurityProtocol);
        if TypeHelper.BitwiseAnd(CurrentSecurityProtocol, SecurityProtocol) <> SecurityProtocol then
            ServicePointManager.SecurityProtocol := TypeHelper.BitwiseOr(CurrentSecurityProtocol, SecurityProtocol);
    end;

    [Scope('OnPrem')]
    [NonDebuggable]
    procedure AddHeader("Key": Text; Value: Text)
    begin
        case Key of
            'Accept':
                SetReturnType(Value);
            'Content-Type':
                SetContentType(Value);
            else
                HttpWebRequest.Headers.Add(Key, Value);
        end;
    end;

    [Scope('OnPrem')]
    [NonDebuggable]
    procedure AddHeader("Key": Text; Value: SecretText)
    begin
        HttpWebRequest.Headers.Add(Key, Value.Unwrap());
    end;

    [Scope('OnPrem')]
    procedure AddBody(BodyFilePath: Text)
    var
        FileManagement: Codeunit "File Management";
        FileStream: DotNet FileStream;
        FileMode: DotNet FileMode;
    begin
        if BodyFilePath = '' then
            exit;

        FileManagement.IsAllowedPath(BodyFilePath, false);

        FileStream := FileStream.FileStream(BodyFilePath, FileMode.Open);
        FileStream.CopyTo(HttpWebRequest.GetRequestStream());
    end;

    [Scope('OnPrem')]
    procedure AddBodyBlob(var TempBlob: Codeunit "Temp Blob")
    var
        RequestStr: DotNet Stream;
        BlobStr: InStream;
    begin
        if not TempBlob.HasValue() then
            exit;

        RequestStr := HttpWebRequest.GetRequestStream();
        TempBlob.CreateInStream(BlobStr);
        CopyStream(RequestStr, BlobStr);
        RequestStr.Flush();
        RequestStr.Close();
        RequestStr.Dispose();
    end;

    [NonDebuggable]
    procedure AddBasicAuthentication(BasicUserId: Text; BasicUserPassword: SecretText)
    var
        Credential: DotNet NetworkCredential;
    begin
        HttpWebRequest.UseDefaultCredentials(false);
        Credential := Credential.NetworkCredential();
        Credential.UserName := BasicUserId;
        Credential.Password := BasicUserPassword.Unwrap();
        HttpWebRequest.Credentials := Credential;
    end;

    [Scope('OnPrem')]
    procedure SetUserAgent(UserAgent: Text)
    begin
        HttpWebRequest.UserAgent := UserAgent;
    end;

    [Scope('OnPrem')]
    procedure SetCookie(var Cookie: DotNet Cookie)
    begin
        HttpWebRequest.CookieContainer.Add(Cookie);
    end;

    [Scope('OnPrem')]
    procedure GetCookie(var Cookie: DotNet Cookie)
    var
        CookieCollection: DotNet CookieCollection;
    begin
        if not HasCookie() then
            Error(NoCookieForYouErr);
        CookieCollection := HttpWebRequest.CookieContainer.GetCookies(HttpWebRequest.RequestUri);
        Cookie := CookieCollection.Item(0);
    end;

    [Scope('OnPrem')]
    procedure HasCookie(): Boolean
    begin
        exit(HttpWebRequest.CookieContainer.Count > 0);
    end;

    procedure CreateInstream(var InStr: InStream)
    var
        TempBlob: Codeunit "Temp Blob";
    begin
        TempBlob.CreateInStream(InStr);
    end;

    [IntegrationEvent(false, false)]
    [Scope('OnPrem')]
    procedure OnOverrideUrl(var Url: Text)
    begin
        // Provides an option to rewrite URL in non SaaS environments.
    end;

    [Scope('OnPrem')]
    procedure SendRequestAndReadTextResponse(var ResponseBody: Text; var ErrorMessage: Text; var ErrorDetails: Text; var HttpStatusCode: DotNet HttpStatusCode; var ResponseHeaders: DotNet NameValueCollection): Boolean
    var
        TempBlob: Codeunit "Temp Blob";
        ResponseInStream: InStream;
        TextLine: Text;
    begin
        if not SendRequestAndReadResponse(TempBlob, ErrorMessage, ErrorDetails, HttpStatusCode, ResponseHeaders) then
            exit(false);

        TempBlob.CreateInStream(ResponseInStream);
        while ResponseInStream.ReadText(TextLine) > 0 do
            ResponseBody += TextLine;

        exit(true);
    end;

    [Scope('OnPrem')]
    procedure SendRequestAndReadResponse(var TempBlob: Codeunit "Temp Blob"; var ErrorMessage: Text; var ErrorDetails: Text; var HttpStatusCode: DotNet HttpStatusCode; var ResponseHeaders: DotNet NameValueCollection): Boolean
    var
        WebRequestHelper: Codeunit "Web Request Helper";
        WebException: DotNet WebException;
        WebExceptionResponse: DotNet HttpWebResponse;
        ResponseInStream: InStream;
        WebExceptionResponseText: Text;
        TextLine: Text;
        ServiceUrl: Text;
    begin
        TempBlob.CreateInStream(ResponseInStream);

        ClearLastError();
        if GetResponse(ResponseInStream, HttpStatusCode, ResponseHeaders) then
            exit(true);

        ErrorMessage := GetLastErrorText;

        WebRequestHelper.GetWebResponseError(WebException, ServiceUrl);
        WebExceptionResponse := WebException.Response;
        if SYSTEM.IsNull(WebExceptionResponse) then
            exit(false);

        HttpStatusCode := WebExceptionResponse.StatusCode;
        ResponseHeaders := WebExceptionResponse.Headers;
        WebExceptionResponse.GetResponseStream().CopyTo(ResponseInStream);
        while ResponseInStream.ReadText(TextLine) > 0 do
            WebExceptionResponseText += TextLine;

        ErrorDetails := WebExceptionResponseText;

        exit(false);
    end;

    local procedure SetJSONContent(var ResponseJson: Text; ResponseInStream: InStream): Boolean
    var
        JSONMgt: Codeunit "JSON Management";
        ContentJson: Text;
        Content: Text;
    begin
        if ResponseInStream.Read(Content) = 0 then
            exit(true);

        if not JSONMgt.InitializeFromString(Content) then
            exit(false);

        ContentJson := JSONMgt.WriteObjectToString();
        JSONMgt.InitializeObject(ResponseJson);
        JSONMgt.AddJson('Content', ContentJson);
        ResponseJson := JSONMgt.WriteObjectToString();
        exit(true);
    end;

    local procedure ParseWebResponseError(var ResponseJson: Text; WebException: DotNet WebException)
    var
        JSONMgt: Codeunit "JSON Management";
        HttpWebResponse: DotNet HttpWebResponse;
        Convert: DotNet Convert;
        WebExceptionStatus: DotNet WebExceptionStatus;
        ResponseInputStream: InStream;
        ErrorDescription: Text;
        StatusCode: Text;
        StatusCodeString: Text;
        WebExceptionResponse: Boolean;
    begin
        if IsNull(WebException) then
            exit;

        case true of
            WebException.Status.Equals(WebExceptionStatus.Timeout):
                begin
                    StatusCode := '408';
                    StatusCodeString := 'Request Timeout';
                    ErrorDescription := TimeoutErr;
                end;
            not IsNull(WebException.Response):
                begin
                    WebExceptionResponse := true;
                    HttpWebResponse := WebException.Response;
                    StatusCode := Format(Convert.ToInt32(HttpWebResponse.StatusCode));
                    StatusCodeString := HttpWebResponse.StatusCode.ToString();
                    ErrorDescription := HttpWebResponse.StatusDescription;
                end;
            else
                exit;
        end;

        JSONMgt.SetJsonWebResponseError(ResponseJson, StatusCode, StatusCodeString, ErrorDescription);

        // Try to get more details
        if WebExceptionResponse then begin
            ResponseInputStream := HttpWebResponse.GetResponseStream();
            if SetJSONContent(ResponseJson, ResponseInputStream) then
                if JSONMgt.InitializeFromString(ResponseJson) then begin
                    ErrorDescription := JSONMgt.GetValue('Content.error_description');
                    if ErrorDescription <> '' then begin
                        JSONMgt.SetValue('Error.description', ErrorDescription);
                        ResponseJson := JSONMgt.WriteObjectToString();
                    end;
                end;
        end;
    end;
}