我使用Delphi XE2构建了一个DataSnap REST服务器,并添加了一种通过TStream上传文件的服务器方法:
function TServerMethods.updateUploadFile(sFilename: string; UploadStream: TStream): string;
我希望能够从许多不同的客户端(Android、iOS等)调用该方法,我一直在使用各种REST客户端(如Postman(Chrome插件))测试该方法。然而,到目前为止,我还不能让它接受HTTPPOST主体的内容。每当我发送POST命令时,我总是得到以下响应:
{"error":"Message content is not a valid JSON value."}
我尝试过使用各种不同的"内容类型"设置,但似乎都不起作用。看起来DataSnap似乎坚持将内容作为JSON?我尝试将一些有效的JSON粘贴到内容区域,但这也给出了相同的错误响应。
是否有人成功地将TStream用作DataSnap服务器方法的输入参数?我应该换一种方式吗?我已经多次使用TStream作为下载文件的输出参数,它运行良好,这是我第一次尝试上传。
更新
我制作了一个快速的Delphi DataSnap客户端来测试uploadFile服务器方法,这一切都很好。然后,我使用Fiddler来检查Delphi客户端用于在内容主体中发送TStream的POST命令,并注意到它是一个整数(字节)的JSON数组,例如[37,80,68,70,45,49,46,51,13,10]
。因此,我可以在发布之前修改我的Android/iOS客户端,将二进制数据转换为这种字节数组格式,但这是我可以不用的开销。如果DataSnap在TStream是返回参数的情况下流式传输原始二进制,为什么它不能流式传输作为输入参数的原始二进制?
在POST命令中将内容数据添加到请求体时,DataSnap服务器似乎坚持该数据始终是JSON。这就是为什么当使用TStream作为输入参数时,Delphi DataSnap客户端会将流数据转换为整数(字节)的JSON数组。这种格式的大小效率非常低,因为每个字节最多有3位数字,再加上逗号,上传的数据大小可以增长4倍。
因此,我所做的是对要上传到Base64中的数据进行编码。我的服务器方法现在看起来是这样的:
function TServerMethods.updateUploadFile(sFilename: string; Base64Data: TJSONObject): string;
请注意,我正在将Base64字符串封装在TJSONObject中。这是因为,如果只指定String类型,Delphi DataSnap客户端将使用GET调用该方法,并尝试将整个Base64字符串放入URL路径中,从而导致"Connection Closed Gracefully"错误。使用TJSONObject会强制DataSnap使用POST并将数据放入内容主体中。传递的JSON对象是一个单对对象:
{"UploadedData":"JVBERi0xLjMNCiXi48B5SiWGTK3PaY.........."}
通过这种方式,上传的数据大小要小得多,传输速度也更快。我仍然希望能够在内容主体中流式传输原始数据,但DataSnap不允许这样做。