http client multipart form data 400 (Bad Request) response

Posted by Rod Anderson on 11-Aug-2017 10:59

I apologize in advance for the long post.  I'm struggling with multipart forms and continue to get response 400 (bad request).  The specifications are as follows:

/*  requirements */

A file can be added to the request by the "operations" relation (the "uri" field for the "rel = operations" object in the above example). The POST to this URL requires the request content type "multipart/form-data". This URL can be invoked multiple times to send multiple files in one request. The request body must contain fields named:

blob: the file, converted to an array of bytes

checksum: a checksum generated by you so that POS can validate receiving the correct data

algorithm: the algorithm used to generate the checkum. Polling will reject the request unless using one of SHA-1, SHA-256, SHA-384, and SHA-512

transforms: a comma-separated list of any packaging or compression applied to the file (zip, bzip, tar, etc.), listed in the order POS should execute to un-package the file

/*  end requirements */

There's obviously something wrong with my code below but I can't figure it out.  I'm sure it's my ignorance but hopefully, someone can help cure that.

Thanks in advance,

// File

ASSIGN 
    oMultiEntity          = NEW MultipartEntity()
    oMultiEntity:Boundary = GUID .
CREATE X-DOCUMENT hSoapDocument.
hSoapDocument:LOAD('file':u, fileSubmission, FALSE /* validate */ ).
hSoapDocument:SAVE('memptr':u, mData).
     
     
ASSIGN 
    oSoapDoc        = NEW MEMPTR(mData)
    oPart           = NEW MessagePart('application/octet-stream':u, oSoapDoc)
    oPart:ContentId = 'blob':u.
           
oMultiEntity:AddPart(oPart).
    
// Hash
set-size(mData) = 0.
 
DEFINE VAR r AS RAW NO-UNDO.

// Not sure if this is the correct approach??? r = MESSAGE-DIGEST("SHA-256",lcFileSubmission). SET-SIZE(mData) = LENGTH(r). mData = r. ASSIGN oHash = NEW MEMPTR(mData) oPart = NEW MessagePart('text/plain':u, oHash) oPart:ContentId = 'hash':u . oMultiEntity:AddPart(oPart). //algorithm = "SHA-256". set-size(mData) = 0. DEF VAR algorithm AS LONGCHAR NO-UNDO INIT "SHA-256". SET-SIZE(mData) = LENGTH("SHA-256") + 1. PUT-STRING(mData,1) = "SHA-256". ASSIGN oAlgorithm = NEW MEMPTR(mData) oPart = NEW MessagePart('text/plain':u, oAlgorithm) oPart:ContentId = 'algorithm':u . oMultiEntity:AddPart(oPart). //transforms set-size(mData) = 0. DEF VAR transforms AS LONGCHAR NO-UNDO INIT "". SET-SIZE(mData) = LENGTH(transforms) + 1. PUT-STRING(mData,1) = transforms. ASSIGN otransforms = NEW MEMPTR(mData) oPart = NEW MessagePart('text/plain':u, otransforms) oPart:ContentId = 'transforms':u . oMultiEntity:AddPart(oPart). httpClient = ClientBuilder:Build():Client. oCreds = NEW Credentials('smisdev.mydomain.com', 'abc', 'abc123'). FIND FIRST links WHERE links.rel = "operations" NO-LOCK NO-ERROR. oRequest = RequestBuilder:Post(links.uri, oMultiEntity) :UsingBasicAuthentication(oCreds) :ContentType('multipart/form-data') :AcceptJson() :REQUEST. oResponse = httpClient:Execute(oRequest). MESSAGE oResponse:StatusCode SKIP oResponse:StatusReason VIEW-AS ALERT-BOX.

Posted by Peter Judge on 11-Aug-2017 12:38

Hey Rod,
 
You need to (manually) set the Content-Disposition header on the part. Something like the below should work – you’ll need to change the Value though.
 
ASSIGN
    oSoapDoc        = NEW MEMPTR(mData)
    oPart           = NEW MessagePart('application/octet-stream':u, oSoapDoc)
    oPart:ContentId = 'blob':u.
oPart:Headers:Put(HttpHeaderBuilder:Build('Content-Disposition')
                                               :Value('attachment; fileName="soap.xml"')
                                               :Header).
 
oMultiEntity:AddPart(oPart).
 
 
 
 
 

All Replies

Posted by Rod Anderson on 11-Aug-2017 11:52

I now know that I'm getting back "Could find no Content-Disposition header within part"?

Posted by Peter Judge on 11-Aug-2017 12:23

You need to manually add the Content-Disposition header to the part. Something like the below (obvs the values need to change).

ASSIGN
    otransforms     = NEW MEMPTR(mData)
    oPart           = NEW MessagePart('text/plain':u, otransforms)
    oPart:ContentId = 'transforms':u
. oPart:SetHeader(HttpHeaderBuilder:Build('Content-Disposition') :Value('inline; filename="redball.png"') :Header) . oMultiEntity:AddPart(oPart)


Posted by Peter Judge on 11-Aug-2017 12:38

Hey Rod,
 
You need to (manually) set the Content-Disposition header on the part. Something like the below should work – you’ll need to change the Value though.
 
ASSIGN
    oSoapDoc        = NEW MEMPTR(mData)
    oPart           = NEW MessagePart('application/octet-stream':u, oSoapDoc)
    oPart:ContentId = 'blob':u.
oPart:Headers:Put(HttpHeaderBuilder:Build('Content-Disposition')
                                               :Value('attachment; fileName="soap.xml"')
                                               :Header).
 
oMultiEntity:AddPart(oPart).
 
 
 
 

Posted by Rod Anderson on 11-Aug-2017 13:34

Thanks so much.  Not sure I would have figured that out.

Posted by Peter Judge on 11-Aug-2017 13:40

I should note that as a general rule, for multipart message parts, you can set any header via that Headers:Put() call . You can remove them via Headers:Remove(‘header-name’) or Headers:Clear() and check for their existence via Header:Has(“header-name”)

Posted by Peter Judge on 11-Aug-2017 13:40

And there’s API doc at documentation.progress.com/.../OpenEdge.Net.HTTP.HttpHeaderCollection.html (the HttpHeaderCollection the type exposed by the part’s Headers property).

This thread is closed