2
votes

I've built a DataSnap REST server using Delphi XE2 and I've added a server method for uploading files via TStream :

function TServerMethods.updateUploadFile(sFilename: string; UploadStream: TStream): string;

I want to be able to call this from a number of different clients (Android, iOS etc) and I've been testing the method using various REST clients such as Postman (Chrome plugin). However so far I cannot get it to accept the content for the HTTP POST body. Whenever I send the POST command I always get the following response :

{"error":"Message content is not a valid JSON value."}

I've tried using various different 'Content-Type' settings but nothing seems to work. It looks as though DataSnap is insisting on the content being JSON? I've tried pasting some valid JSON into the content area but this also gave the same error response.

Has anybody successfully used TStream as an input parameter for a DataSnap server method? Should I be doing it another way? I've used TStream as an output parameter for downloading files many times and it works well, this is my first attempt at uploading.

UPDATE

I made a quick Delphi DataSnap client to test the uploadFile server method and this all works great. I then used Fiddler to examine the POST command the Delphi client uses to send the TStream in the content body, and noticed it is a JSON array of integers (bytes) e.g. [37,80,68,70,45,49,46,51,13,10]. So I can see that I could modify my Android/iOS clients to convert the binary data to this byte array format before POSTing, but this is an overhead I could do without. If DataSnap streams raw binary when TStream is a return parameter, why can't it stream raw binary as an input parameter?

1

1 Answers

4
votes

It seems when adding content data to the request body in a POST command, DataSnap server insists that this data is always JSON. This is why when using TStream as an input parameter, the stream data is converted to a JSON array of integers (bytes) by the Delphi DataSnap client. This format is very size inefficient as with upto 3 digits per byte, plus the comma, the size of the data being uploaded can grow by as much as 4 times.

What I have therefore done instead is to encode the data to upload in Base64. My server method now looks like this :

function TServerMethods.updateUploadFile(sFilename: string; Base64Data: TJSONObject): string;

Notice I'm wrapping the Base64 string in a TJSONObject. This is because if you just specify a String type, the Delphi DataSnap client will call the method with a GET and attempt to put the whole Base64 string in the URL path, causing a 'Connection Closed Gracefully' error. Using a TJSONObject forces DataSnap to use a POST and put the data in the content body. The JSON object passed is a single pair object :

{"UploadedData":"JVBERi0xLjMNCiXi48B5SiWGTK3PaY.........."}

This way the size of the data uploaded is much smaller and faster to transfer. I'd still prefer to be able to stream the raw data in the content body but DataSnap doesn't allow this.