Question
Emanuel Lazar · Feb 18, 2021

httpRequest POST file Upload multipart

httpRequest POST file Upload multipart

1. is there a code example step-by-step, how to build each part  ?

I tried the demo from intersystems doc's,

I checked it in .Net response, that didnot recognize the File-data part

2. I noticed, there is :

SET BinaryMIMEPart.ContentType="application/octet-stream"

but missing : 

SET BinaryMIMEPart.ContentDisposition (as analogue, let say for VBA EXCEL )

or I do it via the:

Do BinaryMIMEPart.SetHeader("Content-Disposition",...) ?

that is not analouge for VBA

need any code example link, to execute correctly all the multipart (cache call it MIMEtype)

settings

 

 

 

 

 

 

Product version: Ensemble 2018.1
$ZV: Cache for Windows (x86-64) 2018.1.4 (Build 505_1) Thu May 28 2020 10:12:49 EDT
10
3 0 11 214
Log in or sign up to continue

Replies

I had the same challenges as you when I was tackling this - documentation wasn't really fleshing it out well enough. ISC Sales Engineer helped me work through it.

Here is what I have used with success to submit both an XML doc and PDF doc to a vendor along with two parameters associated with the request (ReportId and CustomerId.) Requires use of MIME Parts. I hope this helps you. I had to genericize some of the code to share but it is commented by me what each part does and how it pulls together. 

Note this assumes you're passing in a variable pRequest that is a message class that holds your data. Also I am running this on 2019.1, not 2018 so not sure of the differences when using things like %JSONImport (may be none but I don't know that for certain.)

        Set tURL = "fully_qualified_endpoint_url_here"
        
        // Instantiate reportId MIME Part
        Set reportId = ##class(%Net.MIMEPart).%New()
        // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
        // Final string looks like: form-data; name="reportId"
        S tContentDisp1 = "form-data; name="_$CHAR(34)_"reportId"_$CHAR(34)
        Do reportId.SetHeader("Content-Disposition", tContentDisp1)
        // Get the ReportId from the incoming Request (from BPL) and write to the MIME Part body.
        S tReportId = pRequest.ReportId
        Set reportId.Body = ##class(%GlobalCharacterStream).%New()
        Do reportId.Body.Write(tReportId)
        
        // Instantiate customerId MIME Part
        Set customerId = ##class(%Net.MIMEPart).%New()
        // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
        // Final string looks like: form-data; name="customerId"
        S tContentDisp2 = "form-data; name="_$CHAR(34)_"customerId"_$CHAR(34)
        Do customerId.SetHeader("Content-Disposition", tContentDisp2)
        // Get the CustomerId from the incoming Request (from BPL) and write to the MIME Part body.
        S tCustomerId = pRequest.CustomerId
        Set customerId.Body = ##class(%GlobalCharacterStream).%New()
        Do customerId.Body.Write(tCustomerId)
        
        // Instantiate file1 (XML Structured Doc) MIME Part
        Set file1 = ##class(%Net.MIMEPart).%New()
        // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
        // Final string looks like: form-data; name="file1"; filename="<pRequest.CaseNumber>.xml"
        S tXmlFileName = pRequest.CaseNumber_".xml"
        S tContentDisp3 = "form-data; name="_$CHAR(34)_"file1"_$CHAR(34)_"; filename="_$CHAR(34)_tXmlFileName_$CHAR(34)
        Do file1.SetHeader("Content-Disposition", tContentDisp3)
        // Get the XML as a Stream from the incoming Request (from BPL) and write to the MIME Part body.
        Set tStream = ##class(%GlobalCharacterStream).%New()
        Set tSC = pRequest.XmlDoc.OutputToLibraryStream(tStream)
        Set file1.Body = tStream
        Set file1.ContentType = "application/xml"
        
        // Instantiate file1 (PDF Report) MIME Part
        Set file2 = ##class(%Net.MIMEPart).%New()
        // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
        // Final string looks like: form-data; name="file1"; filename="<pRequest.CaseNumber>.xml"
        S tPdfFileName = pRequest.CaseNumber_".pdf"
        S tContentDisp4 = "form-data; name="_$CHAR(34)_"file2"_$CHAR(34)_"; filename="_$CHAR(34)_tPdfFileName_$CHAR(34)
        Do file2.SetHeader("Content-Disposition", tContentDisp4)
        // Get the PDF Stream from the incoming Request (from BPL) and write to the MIME Part body.
        Set file2.Body = pRequest.PdfDoc.Stream
        Set file2.ContentType = "application/pdf"
        
        // Package sub-MIME Parts into Root MIME Part
        Set rootMIME = ##class(%Net.MIMEPart).%New()
        Do rootMIME.Parts.Insert(reportId)
        Do rootMIME.Parts.Insert(customerId)
        Do rootMIME.Parts.Insert(file1)
        Do rootMIME.Parts.Insert(file2)
        
        // Write out Root MIME Element (containing sub-MIME parts) to HTTP Request Body.
        Set writer = ##class(%Net.MIMEWriter).%New()
        Set sc = writer.OutputToStream(..%HttpRequest.EntityBody)
        if $$$ISERR(sc) {do $SYSTEM.Status.DisplayError(sc) Quit}
        Set sc = writer.WriteMIMEBody(rootMIME)
        if $$$ISERR(sc) {do $SYSTEM.Status.DisplayError(sc) Quit}
        
        // Set the HTTP Request Headers
        // Specify the Authorization header containing the OAuth2 Bearer Access Token.
        Set tToken = "set your token here or pull from wherever"
        Set tSC = ..%HttpRequest.SetHeader("Authorization","Bearer "_tToken)
        // Specify the Content-Type and Root MIME Part Boundary (required for multipart/form-data encoding.)
        Set tContentType = "multipart/form-data; boundary="_rootMIME.Boundary
        Set tSC = ..%HttpRequest.SetHeader("Content-Type",tContentType)

        // Call SendFormDataArray method in the adapter to execute POST. Response contained in tHttpResponse
        Set tSC=..Adapter.SendFormDataArray(.tHttpResponse,"POST", ..%HttpRequest, "", "", tURL)
        
        // Validate that the call succeeded and returned a response. If not, throw error.
        If $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&tHttpResponse.Data.Size 
        {
            Set tSC = $$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
        }
        Quit:$$$ISERR(tSC)

        If $IsObject(tHttpResponse)
        {
            // Instantiate the response object
            S pResponse = ##class(Sample.Messages.Response.VendorResponseMsgClass).%New()
            // Convert JSON Response Payload into a Response Object
            S tSC = pResponse.%JSONImport(tHttpResponse.Data)

        }

Hi Craig,

I followed your code, there some obstacles, that I cannot implement your example :

1.  I run the from standard Module.int (not class)

2. what is the syntax : ..%HttpRequest.SetHeader(...) ?

3. what is : ..Adapter.SendFormDataArray(.tHttpResponse,"POST", ..%HttpRequest, "", "", tURL) ?

what the ..Adapter means in standard module ?

I used just :

 Set RootMIMEPart=##class(%Net.MIMEPart).%New()

 Set BinaryMIMEPart=##class(%Net.MIMEPart).%New()

 Set contentDisp=contentDisp_"form-data; name="_Q_"fileUpload"_Q

BPT> Set contentDisp=contentDisp_"; filename="_Q_file_Q

 SET contentDisp=contentDisp_"; filename="_Q_file_Q

Set BinaryMIMEPart.ContentDisposition=contentDisp

 Do BinaryMIMEPart.SetHeader("Content-Disposition",contentDisp)

  Set BinaryMIMEPart.ContentType="application/octet-stream"

and so on...

using standard objects

I'm not really clear on what you mean by "standard Module.int" so sounds like we may be approaching this in different ways and I apologize for any confusion I caused.

%HttpRequest is %Net.HttpRequest (you can find syntax for SetHeader here) and the Adapter in this case refers to the adapter attached to the EnsLib.REST.Operation class via Parameter, which in this case is EnsLib.HTTP.OutboundAdapter.

Emanuel,

Since you're writing this in a normal .int, you won't want to use "..%HTTPRequest". You can just instantiate a new request object and name it something like httpRequest:

set httpRequest=##class(%Net.HttpRequest).%New()

And instead of Craig's ..Adapter call, you can use httpRequest's Post method.

tried multi Part form with one file.pdf to own .Net listener 

the "POST" did not arrive to "web server" ?

my example (took from craig, with some corrections) :

on :

httpRequest.SendFormDataArray...

got error, so I tried .POST

code example :

HttpMimeRequest ;
S Q=$C(34)

 S HOST = "127.0.0.1" ;"localhost"
 S PORT = "8080"
 
 Set httpRequest=##class(%Net.HttpRequest).%New() 
 Set httpRequest.Server=HOST
 Set httpRequest.Port = PORT

 // Create root MIMEPart
 Set RootMIMEPart=##class(%Net.MIMEPart).%New()

 //Create binary subpart and insert file data
 Set BinaryMIMEPart=##class(%Net.MIMEPart).%New()
 
 Set contentDisp=""
 Set contentDisp=contentDisp_"form-data; name="_Q_"fileUpload"_Q
 Set contentDisp=contentDisp_"; filename="_Q_file_Q
 ;Set BinaryMIMEPart.ContentDisposition=contentDisp
  Do BinaryMIMEPart.SetHeader("Content-Disposition",contentDisp)
  Set BinaryMIMEPart.ContentType="application/pdf" ;"application/octet-stream"
 
 Set ContentType= "multipart/form-data; boundary="_RootMIMEPart.Boundary
 Set httpRequest.ContentType=ContentType

 // do we need it : ?
 Do BinaryMIMEPart.SetHeader("Content-Type", ContentType)

 Set stream=##class(%FileBinaryStream).%New()
 Set stream.Filename=file
 Do stream.LinkToFile(file)
 Set BinaryMIMEPart.Body=stream

 // Insert both subparts into the root part
 Do RootMIMEPart.Parts.Insert(BinaryMIMEPart)

 // create MIME writer; write root MIME message
 Set writer=##class(%Net.MIMEWriter).%New()

  // Prepare outputting to the HttpRequestStream
  Set status=writer.OutputToStream(httpRequest.EntityBody)
  if ('status) {do $SYSTEM.Status.DisplayError(status) Quit}

 // Now write down the content
 Set status=writer.WriteMIMEBody(RootMIMEPart)
 if ('status) {do $SYSTEM.Status.DisplayError(status) Quit}

 //  QueryString Example ;
 Set jq = " index.htm?CUSTID=myCust"

 //set url="alfresco/service/sample/upload.json?"
 //      _"alf_ticket=TICKET_caee62bf36f0ea5bd51194fce161f99092b75f62"
 ;set url="http://localhost:8080"
 ;set status=httpRequest.Post(url,0) 
 ;if ('status) {do $SYSTEM.Status.DisplayError(status) Quit}
P1 ; 
 ;Set url = HOST_"http://"_HOST_":"_PORT_"/"_jq
 ;Set tSC = httpRequest.SendFormDataArray(.httpResponse,"POST", httpRequest, "", "", url)
 
  Set rc=httpRequest.Post(jq) 
 //

What error did you receive when you used SendFormDataArray?

Hi Marc,

1.

P1   ;

           Set url = HOST_"http://"_HOST_":"_PORT_"/"_jq
           Set tSC = httpRequest.SendFormDataArray(.httpResponse,"POST", httpRequest, "", "", url)

         //Set rc=httpRequest.Post(jq)

the error I get:

<METHOD DOES NOT EXIST>P1+2^HTTP3 *SendFormDataArray,%Net.HttpRequest

2. I asked berfore:

in what Part I set contentDisposition ?

I mean not in SetHeaders,  but as another part in Body ?

or, I don't need it ?

1) Ah, that makes sense. SendFormDataArray is a method in EnsLib.HTTP.OutboundAdapter, but you're not using an adapter.

To do a POST using %Net.HTTPRequest, you'll need to use the Post() method.
 

2) I had a look at the HTTP specs, and it looks like content-disposition is required for each part:

In a multipart/form-data body, the HTTP Content-Disposition general header is a header that must be used on each subpart of a multipart body to give information about the field it applies to. The subpart is delimited by the boundary defined in the Content-Type header. Used on the body itself, Content-Disposition has no effect.

You can set this like any other header using the SetHeader method in %Net.MIMEPart. You're already doing that here:

Do BinaryMIMEPart.SetHeader("Content-Type", ContentType)

Here's a working example based on Craig's original sample. It sends a request with 3 mime parts: a form variable with a value, an XML file, and a PDF file loaded from disk.

    set tURL="http://some.url/path/to/upload"

    set tHttpRequest = ##class(%Net.HttpRequest).%New()    
    
    // ----------------------------------------
    // Instantiate reportId MIME Part
    Set reportId = ##class(%Net.MIMEPart).%New()

    // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
    // Final string looks like: form-data; name="reportId"
    S tContentDisp = "form-data; name="_$CHAR(34)_"reportId"_$CHAR(34)
    Do reportId.SetHeader("Content-Disposition", tContentDisp)

    // Write the reportId to the MIME Part body.
    Set reportId.Body = ##class(%GlobalCharacterStream).%New()
    Do reportId.Body.Write("RptID12345")
    
    // ----------------------------------------    
    // Instantiate file1 (XML Doc) MIME Part
    
    Set file1 = ##class(%Net.MIMEPart).%New()

    // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
    // Final string looks like: form-data; name="file1"; filename="<pRequest.CaseNumber>.xml"
    S tContentDisp = "form-data; name="_$CHAR(34)_"file1"_$CHAR(34)_"; filename="_$CHAR(34)_"xmlfile.xml"_$CHAR(34)
    Do file1.SetHeader("Content-Disposition", tContentDisp)

    // Write XML to the MIME Part body.
    Set file1.Body = ##class(%GlobalCharacterStream).%New()
    Set file1.ContentType = "application/xml"
    do file1.Body.Write("<myXML><element1>value</element1></myXML>")
    
    
    // ----------------------------------------
    // Instantiate file1 (PDF Report) MIME Part
    Set file2 = ##class(%Net.MIMEPart).%New()

    // Define/Set the Content-Disposition header indicating how this MIME part is encoded and what it contains.
    // Final string looks like: form-data; name="file1"; filename="<pRequest.CaseNumber>.xml"
    S tContentDisp = "form-data; name="_$CHAR(34)_"file2"_$CHAR(34)_"; filename="_$CHAR(34)_"PDFFile.pdf"_$CHAR(34)
    Do file2.SetHeader("Content-Disposition", tContentDisp)

    // Get the content for the PDF file
    set tFile=##class(%Stream.FileBinary).%New()
    do tFile.LinkToFile("C:\Projects\test.pdf")

    // Write PDF content to the MIME Part body.
    Set file2.Body = ##class(%GlobalBinaryStream).%New()
    Set file2.ContentType = "application/pdf"
    do file2.Body.CopyFrom(tFile)


    // ----------------------------------------
    // Pack everything up and send the request

    // Package sub-MIME Parts into Root MIME Part
    Set rootMIME = ##class(%Net.MIMEPart).%New()
    Do rootMIME.Parts.Insert(reportId)
    Do rootMIME.Parts.Insert(file1)
    Do rootMIME.Parts.Insert(file2)
    
    
    // Write out Root MIME Element (containing sub-MIME parts) to HTTP Request Body.
    Set writer = ##class(%Net.MIMEWriter).%New()
    Set sc = writer.OutputToStream(tHttpRequest.EntityBody)
    if $$$ISERR(sc) {do $SYSTEM.Status.DisplayError(sc) Quit}
    Set sc = writer.WriteMIMEBody(rootMIME)
    if $$$ISERR(sc) {do $SYSTEM.Status.DisplayError(sc) Quit}
    
    
    // Specify the Content-Type and Root MIME Part Boundary (required for multipart/form-data encoding.)
    Set tContentType = "multipart/form-data; boundary="_rootMIME.Boundary
    Set tSC = tHttpRequest.SetHeader("Content-Type",tContentType)

    // Call SendFormDataArray method in the adapter to execute POST. Response contained in tHttpResponse
    Set tSC=tHttpRequest.Post(tURL)
    
    If $$$ISERR(tSC) {
        // Oops, an error. Do something    
    }

Hi Marc,

1. I built ASP.NET some "server" http listener with [HttpPost] controller

2. I activate your code meanwhile on 1 file (.pdf)

with URL ...serverAsp/postfile/ ...

and got correctly the "file" from your POST() code, means in .NET:

var request = System.Web.HttpContext.Current.Request;

            var files = request.Files.Count;

---> files.Count = 1

so .NET server recognized multi-part structure with 1 file !

4. I saw you made little changes to Craig code, and made it more simpler with just .Post(Url),  without any "adapters"... and nor .SendFormDataArray... 

that did not work for me in the beginning.

5. I'll made more testing , to see if the whole file is transfered correctly

but its seems to work.

thanks,

Hi Marc,

I have another Topic, that did not get an answer (from26/08/2020) ,

can you help about, or notice the Owner ? :

 
located on :
my (emanuel Lazar) question :
Emanuel Lazar · Aug 26, 2020
to :
 
the question text :
********************

Dmitriy,
1. I tried the open socket script,  you wrote, and it works OK.

2.now I want to send some string messages on the opened socket,

how I do it ?

I have to add on each "MyMessage" string some header before ?

can you write exact script how I implement using send message(s), and handle get response on each

message.

did not find any docs about

*************************
 
any help ,
will be appreciated