Salesforce中调用REST API上传文件
昨天接到任务,说是调用某个REST API上传文件一直调不通,让我看一下。先用Flow调试了一下Apex代码,提示415错误,看了下代码,Content-Type设成json了,改成multipart/form-data,这下不提示415了,但提示400错误。
于是只好查资料,找了半天,说是base64编码的问题,从stackOverFlow上扒下来一段代码,再试,倒是不提示400了,却又出来个错误,说什么starting boundary找不到,无法解析Content-Disposition: filename。摸不着头脑,试了半天,却发现是由于犯了个愚蠢的错误,httpRequest的Body没有替换成用base64重新编码好的,改过来,再试,果然成功了。
具体的做法:
关键是stackOverFlow上的那段代码,抄录如下:
public with sharing class UploadFileHelper { // The boundary is alligned so it doesn't produce padding characters when base64 encoded. private final static string Boundary = '1ff13444ed8140c7a32fc4e6451aa76d'; /** * Returns the request's content type for multipart/form-data requests. */ public static string GetContentType() { return 'multipart/form-data; charset="UTF-8"; boundary="' + Boundary + '"'; } /** * Pad the value with spaces until the base64 encoding is no longer padded. */ private static string SafelyPad( string value, string valueCrLf64, string lineBreaks) { string valueCrLf = ''; blob valueCrLfBlob = null; while (valueCrLf64.endsWith('=')) { value += ' '; valueCrLf = value + lineBreaks; valueCrLfBlob = blob.valueOf(valueCrLf); valueCrLf64 = EncodingUtil.base64Encode(valueCrLfBlob); } return valueCrLf64; } /** * Write a boundary between parameters to the form's body. */ public static string WriteBoundary() { string value = '--' + Boundary + '\r\n'; blob valueBlob = blob.valueOf(value); return EncodingUtil.base64Encode(valueBlob); } /** * Write a boundary at the end of the form's body. */ public static string WriteBoundary( EndingType ending) { string value = ''; if (ending == EndingType.Cr) { // The file's base64 was padded with a single '=', // so it was replaced with '\r'. Now we have to // prepend the boundary with '\n' to complete // the line break. value += '\n'; } else if (ending == EndingType.None) { // The file's base64 was not padded at all, // so we have to prepend the boundary with // '\r\n' to create the line break. value += '\r\n'; } // Else: // The file's base64 was padded with a double '=', // so they were replaced with '\r\n'. We don't have to // do anything to the boundary because there's a complete // line break before it. value += '--' + Boundary + '--'; blob valueBlob = blob.valueOf(value); return EncodingUtil.base64Encode(valueBlob); } /** * Wirte a file to the form's body. */ public static WriteFileResult WriteFile( string key, string value, string mimeType, blob fileBlob) { EndingType ending = EndingType.None; string contentDisposition = 'Content-Disposition: form-data; name="' + key + '"; filename="' + value + '"'; string contentDispositionCrLf = contentDisposition + '\r\n'; blob contentDispositionCrLfBlob = blob.valueOf(contentDispositionCrLf); string contentDispositionCrLf64 = EncodingUtil.base64Encode(contentDispositionCrlfBlob); string content = SafelyPad(contentDisposition, contentDispositionCrLf64, '\r\n'); string contentType = 'Content-Type: ' + mimeType; string contentTypeCrLf = contentType + '\r\n\r\n'; blob contentTypeCrLfBlob = blob.valueOf(contentTypeCrLf); string contentTypeCrLf64 = EncodingUtil.base64Encode(contentTypeCrLfBlob); content += SafelyPad(contentType, contentTypeCrLf64, '\r\n\r\n'); string file64 = EncodingUtil.base64Encode(fileBlob); integer file64Length = file64.length(); string file64Ending = file64.substring(file64Length - 3, file64Length); if (file64Ending.endsWith('==')) { file64Ending = file64Ending.substring(0, 1) + '0K';// 0K = \r\n file64 = file64.substring(0, file64Length - 3) + file64Ending; ending = EndingType.CrLf; } else if (file64Ending.endsWith('=')) { file64Ending = file64Ending.substring(0, 2) + 'N';// N = \r file64 = file64.substring(0, file64Length - 3) + file64Ending; ending = EndingType.Cr; } content += file64; return new WriteFileResult(content, ending); } /** * Write a key-value pair to the form's body. */ public static string WriteBodyParameter( string key, string value) { string contentDisposition = 'Content-Disposition: form-data; name="' + key + '"'; string contentDispositionCrLf = contentDisposition + '\r\n\r\n'; blob contentDispositionCrLfBlob = blob.valueOf(contentDispositionCrLf); string contentDispositionCrLf64 = EncodingUtil.base64Encode(contentDispositionCrLfBlob); string content = SafelyPad(contentDisposition, contentDispositionCrLf64, '\r\n\r\n'); string valueCrLf = value + '\r\n'; blob valueCrLfBlob = blob.valueOf(valueCrLf); string valueCrLf64 = EncodingUtil.base64Encode(valueCrLfBlob); content += SafelyPad(value, valueCrLf64, '\r\n'); return content; } /** * Helper class containing the result of writing a file's blob to the form's body. */ public class WriteFileResult { public final string Content { get; private set; } public final EndingType EndingType { get; private set; } public WriteFileResult( string content, EndingType ending) { this.Content = content; this.EndingType = ending; } } /** * Helper enum indicating how a file's base64 padding was replaced. */ public enum EndingType { Cr, CrLf, None } }
其中Boundary 的值是个GUID去掉短横。可见这里关键是base64编码的处理。
调用的代码如下:
public static HttpResponse uploadFile(string url, string fileName, string fileMimeType, blob fileBlob) { string contentType = UploadFileHelper.GetContentType(); string form64 = ''; form64 += UploadFileHelper.WriteBoundary(); UploadFileHelper.WriteFileResult result = UploadFileHelper.WriteFile('file', fileName, fileMimeType, fileBlob); form64 += result.Content; form64 += UploadFileHelper.WriteBoundary(result.EndingType); blob formBlob = EncodingUtil.base64Decode(form64); string contentLength = string.valueOf(formBlob.size()); HttpRequest request = new HttpRequest(); request.setEndpoint(url); request.setHeader('Connection', 'keep-alive'); request.setHeader('Content-Length', contentLength); request.setHeader('Content-Type', contentType); request.setBodyAsBlob(formBlob); request.setMethod('POST'); request.setTimeout(120000); return new Http().send(request); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2018-01-05 看了一个帖子后的感想
2018-01-05 文学方面的思考
2018-01-05 计算机学习的回顾和体会
2018-01-05 什么时候需要好教材和好教材是怎样的
2018-01-05 浏览器自动化的一些体会
2018-01-05 出错处理的一些思考