Has anybody successfully configured a file download via PASOE REST using a Data Object Handler service?
In my interface I have the content type set to "multipart/form-data", and the output body is ABL type "CLASS OpenEdge.Net.MultipartEntity". My current code is:
COPY-LOB FROM FILE cTmpXlsx TO memptrTmp. /* Load file/memptr into a Memptr object (for MessagePart) */ oMemptr = NEW Memptr(GET-SIZE(memptrTmp)). oMemptr:PutBytes(memptrTmp). poEntity = NEW MultipartEntity(). poEntity:Boundary = GUID. oPart = NEW MessagePart('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':u, oMemptr). oHeader = HttpHeaderBuilder:Build('Content-Disposition':u) :Value(SUBSTITUTE('form-data; name="fileContents"; filename=&1':u, QUOTER("Test.xlsx"))) :Header. poEntity:AddPart(oPart). oPart:Headers:Put(oHeader). RETURN 200.
I have a couple problems. The first is that the file that is downloaded contains all of the part header information. The second is that the downloaded filename is my URI/operation name and not the filename set in the Content-Disposition header.
Downloaded file contents:
--d2abaee5-58fa-13b3-3714-453b65266f8b Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: form-data; name="fileContents"; filename="Profit.xlsx" [File contents] --d2abaee5-58fa-13b3-3714-453b65266f8b--
Thoughts?
TIA
Louis Winter
Hi Louis,
Here is an example I wrote to upload multipart data. In this example I am not uploading to database, but I am able to extract the payload correctly.
method override protected integer HandlePost(INPUT poRequest AS IWebRequest): define variable oResp as IHttpResponse no-undo. define variable oEntity as MultipartEntity no-undo. define variable oPart as MessagePart no-undo. define variable oEntityWriter as MessageWriter no-undo. define variable oHeader as HttpHeader no-undo. define variable response as WebResponse no-undo. define variable writer as WebResponseWriter no-undo. define variable cImageFileName as character no-undo. def var i as integer. def var memptr1 as class memptr. response = new WebResponse(). response:StatusCode = 200. response:StatusReason = "OK". writer = new WebResponseWriter( response ). oEntityWriter = EntityWriterBuilder:Build(poRequest) :Writer. oEntityWriter:Open(). oEntityWriter:Write(poRequest:Entity). oEntityWriter:Close(). message " My ContentType is " poRequest:GetHeader("Content-Type"):Value. assign oEntity = cast(oEntityWriter:Entity, MultipartEntity). writer:write( "<HTML>" ). writer:write( "<HEAD>" ). writer:write( "<TITLE>Round Trip</TITLE>" ). writer:write( "</HEAD>" ). writer:write( "<BODY BGCOLOR=~"FFFFFF~">" ). writer:write( "<H2>Multipart Request Payload</H2>" ). DO i = 1 TO oEntity:Size: oPart = oEntity:GetPart(Integer(i)). oHeader = oPart:Headers:Get('Content-Disposition':u). cImageFileName = oHeader:GetParameterValue('filename':u). if type-of(oPart:Body,ByteBucket) then do: memptr1 = cast(oPart:Body, ByteBucket):GetBytes(). writer:Write("<BR>element position is " + String(i)). writer:Write("<BR>Image name: " + cImageFileName). writer:Write("<BR>Image size: " + String(GET-SIZE(memptr1:value))). writer:write("<BR>Content-Type: " + oPart:ContentType). writer:write("<BR>"). end. else do: writer:Write("<BR>element position is " + String(i)). writer:Write("<BR>Text Name: " + String(oPart:Body)). writer:write("<BR>Content-Type: " + oPart:ContentType). writer:write("<BR>"). end. END. writer:write( "</BODY>" ). writer:write( "</HTML>" ). writer:close(). writer:flush(). return integer(StatusCodeEnum:OK). END METHOD.
Thanks Irfan. I have the upload working already, my issue now is downloading a file. I've found download code (github.com/.../ImageWebHandler.cls) similar to yours that I've been trying to use, but it's not quite working.
I'm using a Data Object WebSpeed service which uses annotations to define the URIs. I'm also using a custom .map file to define both my upload and download URIs.
All of the examples I've seen use a WebResponse and WebResponseWriter. I'm not sure how these fit in with the PASOE REST service and how I would need to define my output argument.
Louis
Peter,
Sorry for the delay. I tried the changes. Returning 0 causes it to not download anything. Changing this back to 200 results in the file to download. The downloaded file is still the same, it contains the header information. I captured this with fiddler and the following is what is shown for the raw response:
HTTP/1.1 200 Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-XSS-Protection: 1; mode=block X-Frame-Options: DENY X-Content-Type-Options: nosniff Content-Type: multipart/mixed Transfer-Encoding: chunked Date: Wed, 21 Feb 2018 21:59:07 GMT 2000 --d58c7aac-9a72-66be-3714-e14ea1e9b748 Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="Test.xlsx" 1259 --d58c7aac-9a72-66be-3714-e14ea1e9b748-- 0
Any other ideas?
Louis
Does anybody have any thoughts/ideas on how to get a file to download successfully from a Data Object Handler REST interface without using a WebResponse and WebResponseWriter?
Louis
Louis,
I created an example of this at github.com/.../doh_multipart
LMK if that's what you're after (or not).
Peter,
I changed my map to match your contentType and all arguments for my download function (DownloadAcctCat), commented out my method and added your ReadMultipart (renamed to DownloadAcctCat), and changed image file name to "progress-oe.png". The test program returns 4 parts. If I use the download URL in a browser I still get a file with the boundary and other information.
localhost:8810/.../DownloadProfitAcctCat
Returns a file named "DownloadProfitAcctCat" with the following content:
--my-part-bound
Content-Type: application/json
{"part-01":1}
--my-part-bound
Content-Type: image/png
‰PNG
[file contents...]
--my-part-bound
Content-Type: text/plain
this is part number 3
--my-part-bound
Content-Type: application/json
{"part-04":4}
--my-part-bound--
I also tried commenting out the entity:AddPart() calls for cnt = 0 and 1 (leaving just the actual file) and the download file is still named "DownloadProfitAcctCat" and it has the boundary and Content-Type lines.
With this example, I want to be able to call "localhost:8810/.../DownloadProfitAcctCat" from the browser and have it download a file named "progress-oe.png" that is an actual image file.
Louis
Louis,
Do you care about multipart messages, or do you just want a file?
In both cases, you need a Content-Disposition header that tells the client the name of the file. See github.com/.../ImageWebHandler.cls for an example.
Note that for a single message body, the Content-Disposition is "attachment" and for the multipart response, it's "form-data".
HTH
Peter,
No, I don't care about multipart messages, I just want to download a single file. I started with multipart simply because that's what I was working with for the upload and didn't know exactly what all to change in the map and signature for anything else.
In my original attempt I had the Content-Disposition and it was still downloading a file with the name of the service (DownloadProfitAcctCat). I also had the output of the method as a MultipartEntity, which doesn't seem to work.
The following is how I was trying to set the header:
oHeader = HttpHeaderBuilder:Build('Content-Disposition':u)
:Value(SUBSTITUTE('attachment; filename=&1':u, QUOTER("Test.xlsx"))):Header.
In order to download the file directly without the multipart message, what should the signature of the method be?
Louis
Peter,
Just wanted to let you know quick that I think I'm close. I changed my map file to:
"/DownloadProfitAcctCat": { "GET": { "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "entity": { "name": "UploadProfitAcctCat", "function": "DownloadAcctCat", "arg": [ { "ablName": "", "ablType": "INTEGER", "ioMode": "RETURN", "msgElem": {"type": "StatusCode", "name": null} }, { "ablName": "poMemptr", "ablType": "CLASS OpenEdge.Core.Memptr", "ioMode": "OUTPUT", "msgElem": {"type": "BODY", "name": null} } ] } } }
The method is now:
METHOD PUBLIC INT DownloadAcctCat(OUTPUT poMemptr AS OpenEdge.Core.Memptr): DEF VAR cTmpXlsx AS CHAR NO-UNDO. DEF VAR memptrTmp AS MEMPTR NO-UNDO. cTmpXlsx = "Test.xlsx". COPY-LOB FROM FILE cTmpXlsx TO memptrTmp. poMemptr = NEW Memptr(GET-SIZE(memptrTmp)). poMemptr:PutBytes(memptrTmp). SET-SIZE(memptrTmp) = 0. RETURN 200. END METHOD.
Now, my contents of the download file are correct but the file name is "DownloadProfitAcctCat.xlsx". Since I'm now returning a Memptr, how do I set the Content-Disposition?
Louis
Peter,
Thanks for all your help, my download is now working great!
I ended up using a combination of your last examples. Since I am actually generating the file to be downloaded, I still wanted to be able to return the HTTP status so I have my return value as the StatusCode, and now have 2 output (HttpHeader and Memptr). I tried using the FileInputStream first which works, but my generated temp file doesn't get removed, I'm sure that was because the DOH had the file open for the response. The Memptr works better for my situation.
One last question, are the valid "type" and "name" values for the msgElem in the map file documented anywhere?
Louis
Peter,
Thanks for the info. I'm not planning on changing my code now that it's working, it's just nice to know what our options are.
Louis