How to POST multipart binary & non-binary with httr (for Salesforce API)

核能气质少年 提交于 2019-12-12 03:33:59

问题


I'm having some trouble using httr to POST a file to Salesforce's REST API. I thought this SO question might take care of it, but didn't seem to. I have a hunch that this is because Saleforce wants a non-binary part and upload_file creates an object of class form_file that's handled as a binary, but maybe someone has an alternate explanation/solution...

Salesforce asks for the following curl request to insert a new document:

curl https://yourInstance.salesforce.com/services/data/v23.0/sobjects/Document/ -H
"Authorization: Bearer token" -H "Content-Type: multipart/form-data;
boundary=\"boundary_string\"" --data-binary @newdocument.json

with newdocument.json looking like this:

--boundary_string
Content-Disposition: form-data; name="entity_document";
Content-Type: application/json
{
"Description" : "Marketing brochure for Q1 2011",
"Keywords" : "marketing,sales,update",
"FolderId" : "005D0000001GiU7",
"Name" : "Marketing Brochure Q1",
"Type" : "pdf"
}
--boundary_string
Content-Type: application/pdf
Content-Disposition: form-data; name="Body"; filename="2011Q1MktgBrochure.pdf"
Binary data goes here.
--boundary_string--

If I try to generate a similar output using @Jeroen's answer as a helpful guide, I get a Bad Request error:

library(httr)
library(jsonlite)

url <- "https://[Salesforce Instance]/services/data/v39.0/sobjects/Document/"
header <- add_headers(c(Authorization = "Bearer [Salesforce Key]"))

media <- tempfile()
png(media, width = 800, height = 600)
plot(cars)
dev.off()

metadata <- tempfile()
x <- data.frame(FolderId="a0a1300000ZG7u3", Name="test.png") #Salesforce Record ID and file name
json <- toJSON(unbox(x), pretty=T)
writeLines(json, metadata)

body <- list(entity_document = upload_file(metadata, type = "application/json; charset=UTF-8"), 
             Body = upload_file(media, type = "image/png"))


req <- POST(url, header, verbose(), body = body)

(verbose output)

-> POST /services/data/v39.0/sobjects/Document/ HTTP/1.1
-> Host: [Salesforce Instance]
-> User-Agent: libcurl/7.51.0 r-curl/2.3 httr/1.2.1.9000
-> Accept-Encoding: gzip, deflate
-> Accept: application/json, text/xml, application/xml, */*
-> Authorization: Bearer [Salesforce Key]
-> Content-Length: 3720
-> Expect: 100-continue
-> Content-Type: multipart/form-data; boundary=------------------------6525413b2350e313
-> 
<- HTTP/1.1 100 Continue
>> --------------------------6525413b2350e313
>> Content-Disposition: form-data; name="entity_document"; filename="file1510059b5200f"
>> Content-Type: application/json
>> 

>> {
>>     "FolderId": "a0a1300000ZG7u3",
>>     "Name": "test.png"
>>   }

>> 
>> --------------------------6525413b2350e313
>> Content-Disposition: form-data; name="Body"; filename="file151001ac96950"
>> Content-Type: image/png
>> 

>> ‰PNG
>> 

>> 
>> --------------------------6525413b2350e313--

<- HTTP/1.1 400 Bad Request

with the exception of the filename in the first part, this is pretty close to the desired output, but Salesforce returns the message:

content(req)

[[1]]
[[1]]$message
[1] "Cannot include more than one binary part"

[[1]]$errorCode
[1] "INVALID_MULTIPART_REQUEST"

I tried with just the json but get a slightly different Bad Request:

body <- list(entity_document= json, 
             Body = upload_file(media, type = "image/png"))

-> Content-Type: multipart/form-data; boundary=------------------------ecbd3787f083e4b1
-> 
<- HTTP/1.1 100 Continue
>> --------------------------ecbd3787f083e4b1
>> Content-Disposition: form-data; name="entity_document"
>> 
>> {
>>     "FolderId": "a0a1300000ZG7u3",
>>     "Name": "test.png"
>>   }
>> --------------------------ecbd3787f083e4b1
>> Content-Disposition: form-data; name="Body"; filename="file151001ac96950"
>> Content-Type: image/png
>> 

>> ‰PNG
>> 

>> 
>> --------------------------ecbd3787f083e4b1--

content(req)

[[1]]
[[1]]$message
[1] "Multipart message must include a non-binary part"

[[1]]$errorCode
[1] "INVALID_MULTIPART_REQUEST"

Digging into the details, it seems to me that curl either reads and posts the metadata file as a binary if I use upload_file or posts the json without the content type if I post it as character. Is this the only issue? And if so, is there any way to modify the handler to accept the content type?

Any help is much appreciated!


回答1:


Here's a (temporary) solution based on @Jeroen's latest additions to curl (see Github issue for reference):

library(curl) #>=2.4 on Cran
library(httr)
library(jsonlite)

url <- "https://[Salesforce Instance]/services/data/v39.0/sobjects/Document/"
header <- add_headers(c(Authorization = "Bearer [Salesforce Key]"))

path <- tempfile()
png(path, width = 800, height = 600)
plot(cars)
dev.off()

media <- upload_file(path)

x  <- data.frame(Name=basename(path),ParentId="[Salesforce Parent ID]",Description="",IsPrivate=FALSE)
x <- as.character(toJSON(unbox(x),pretty=T))
metadata <- form_data(x,"application/json") #new curl function to handle a character body with defined type

body <- list(metadata=metadata,Body=media)

req <- httr:::request(fields=body)
req <- httr:::request_build("POST",url,req,header)

response <- httr:::request_perform(req,new_handle())

I put in a request for httr to incorporate this new handling and would imagine that it will be more straightforward to use, i.e. a simple POST(url,header,body=body), once they get around to it.

Hope it helps!




回答2:


Here is a complete and working example of what the OP was trying to do.

library(httr)

# JSON formatted metadata
x <- list(a = 1, b = 2)

# PNG media
media <- tempfile()
png(media, width = 800, height = 600)
plot(cars)
dev.off()

# Construct multipart body
#   Note the use of form_data() and form_file() which allow 
#    the user to specify the content type of each part
body <- list(
  metadata = curl::form_data(toJSON(x), type = "application/json"),
  media = curl::form_file(media, type = mime::guess_type(media))
)

POST(url, body, encode = "multipart")


来源:https://stackoverflow.com/questions/42356834/how-to-post-multipart-binary-non-binary-with-httr-for-salesforce-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!