问题
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