1111# ' Adding a body to a request will automatically switch the method to POST.
1212# '
1313# ' @inheritParams req_perform
14- # ' @param type MIME content type. Will be ignored if you have manually set
15- # ' a `Content-Type` header.
14+ # ' @param type MIME content type. The default, `""`, will not emit a
15+ # ' `Content-Type` header. Ignored if you have set a `Content-Type` header
16+ # ' with [req_headers()].
1617# ' @returns A modified HTTP [request].
1718# ' @examples
1819# ' req <- request(example_url()) |>
5354# ' @export
5455# ' @rdname req_body
5556# ' @param body A literal string or raw vector to send as body.
56- req_body_raw <- function (req , body , type = NULL ) {
57+ req_body_raw <- function (req , body , type = " " ) {
5758 check_request(req )
58- if (! is.raw(body ) && ! is_string(body )) {
59+ check_string(type )
60+
61+ if (is.raw(body )) {
62+ req_body(req , data = body , type = " raw" , content_type = type )
63+ } else if (is_string(body )) {
64+ req_body(req , data = body , type = " string" , content_type = type )
65+ } else {
5966 cli :: cli_abort(" {.arg body} must be a raw vector or string." )
6067 }
61-
62- req_body(
63- req ,
64- data = body ,
65- type = " raw" ,
66- content_type = type %|| % " "
67- )
6868}
6969
7070# ' @export
7171# ' @rdname req_body
7272# ' @param path Path to file to upload.
73- req_body_file <- function (req , path , type = NULL ) {
73+ req_body_file <- function (req , path , type = " " ) {
7474 check_request(req )
75+ check_string(path )
7576 if (! file.exists(path )) {
76- cli :: cli_abort(" {.arg path} ({.path {path}}) does not exist." )
77+ cli :: cli_abort(" Can't find file {.path {path}}." )
78+ } else if (dir.exists(path )) {
79+ cli :: cli_abort(" {.arg path} must be a file, not a directory." )
7780 }
81+ check_string(type )
7882
79- # Need to override default content-type "application/x-www-form-urlencoded"
80- req_body(
81- req ,
82- data = new_path(path ),
83- type = " raw-file" ,
84- content_type = type %|| % " "
85- )
83+ req_body(req , data = path , type = " file" , content_type = type )
8684}
8785
8886# ' @export
@@ -126,11 +124,11 @@ req_body_json <- function(
126124# ' @rdname req_body
127125req_body_json_modify <- function (req , ... ) {
128126 check_request(req )
129- if (req $ body $ type != " json" ) {
130- cli :: cli_abort(" Can only be used after {.fn req_body_json" )
127+ if (! req_body_type( req ) %in% c( " empty " , " json" ) ) {
128+ cli :: cli_abort(" Can only be used after {.fn req_body_json}. " )
131129 }
132130
133- req $ body $ data <- utils :: modifyList(req $ body $ data , list2(... ))
131+ req $ body $ data <- utils :: modifyList(req $ body $ data % || % list () , list2(... ))
134132 req
135133}
136134
@@ -159,12 +157,7 @@ req_body_form <- function(
159157
160158 dots <- multi_dots(... , .multi = .multi )
161159 data <- modify_list(.req $ body $ data , !!! dots )
162- req_body(
163- .req ,
164- data = data ,
165- type = " form" ,
166- content_type = " application/x-www-form-urlencoded"
167- )
160+ req_body(.req , data = data , type = " form" )
168161}
169162
170163# ' @export
@@ -174,12 +167,7 @@ req_body_multipart <- function(.req, ...) {
174167
175168 data <- modify_list(.req $ body $ data , ... )
176169 # data must be character, raw, curl::form_file, or curl::form_data
177- req_body(
178- .req ,
179- data = data ,
180- type = " multipart" ,
181- content_type = NULL
182- )
170+ req_body(.req , data = data , type = " multipart" )
183171}
184172
185173# General structure -------------------------------------------------------
@@ -188,10 +176,12 @@ req_body <- function(
188176 req ,
189177 data ,
190178 type ,
191- content_type ,
179+ content_type = NULL ,
192180 params = list (),
193181 error_call = parent.frame()
194182) {
183+ arg_match(type , c(" raw" , " string" , " file" , " json" , " form" , " multipart" ))
184+
195185 if (! is.null(req $ body ) && req $ body $ type != type ) {
196186 cli :: cli_abort(
197187 c(
@@ -211,94 +201,83 @@ req_body <- function(
211201 req
212202}
213203
214- req_body_info <- function (req ) {
215- if (is.null(req $ body )) {
216- " empty"
217- } else {
218- data <- req $ body $ data
219- if (is.raw(data )) {
220- glue(" {length(data)} bytes of raw data" )
221- } else if (is_string(data )) {
222- glue(" a string" )
223- } else if (is_path(data )) {
224- glue(" path '{data}'" )
225- } else if (is.list(data )) {
226- glue(" {req$body$type} encoded data" )
227- } else {
228- " invalid"
229- }
230- }
204+ req_body_type <- function (req ) {
205+ req $ body $ type %|| % " empty"
231206}
232207
208+ req_body_info <- function (req ) {
209+ switch (
210+ req_body_type(req ),
211+ empty = " empty" ,
212+ raw = glue(" a {length(req$body$data)} byte raw vector" ),
213+ string = " a string" ,
214+ file = glue(" a path '{req$body$data}'" ),
215+ json = " JSON data" ,
216+ form = " form data" ,
217+ multipart = " multipart data"
218+ )
219+ }
233220req_body_get <- function (req ) {
234- if (is.null(req $ body )) {
235- return (" " )
236- }
237221 switch (
238- req $ body $ type ,
222+ req_body_type(req ),
223+ empty = NULL ,
239224 raw = req $ body $ data ,
240- form = {
241- data <- unobfuscate(req $ body $ data )
242- url_query_build(data )
243- },
244- json = exec(jsonlite :: toJSON , req $ body $ data , !!! req $ body $ params ),
245- cli :: cli_abort(" Unsupported request body type {.str {req$body$type}}." )
225+ string = req $ body $ data ,
226+ file = readBin(req $ body $ data , " raw" , n = file.size(req $ body $ data )),
227+ json = unclass(exec(jsonlite :: toJSON , req $ body $ data , !!! req $ body $ params )),
228+ form = url_query_build(unobfuscate(req $ body $ data )),
229+ multipart = {
230+ # This is a bit clumsy because it requires a real request, which is
231+ # currently a bit slow and requires httpuv. But better than nothing.
232+ # Details at https://github.com/jeroen/curl/issues/388
233+ handle <- req_handle(req_body_apply(req ))
234+ echo <- curl :: curl_echo(handle , progress = FALSE )
235+ rawToChar(echo $ body )
236+ }
246237 )
247238}
248239
249240req_body_apply <- function (req ) {
250- if (is.null(req $ body )) {
251- return (req )
252- }
253-
254- data <- req $ body $ data
255- type <- req $ body $ type
256-
257- if (type == " raw-file" ) {
258- size <- file.info(data )$ size
259- # Only open connection if needed
260- delayedAssign(" con" , file(data , " rb" ))
261-
262- req <- req_policies(
263- req ,
264- done = function () close(con )
265- )
266- req <- req_options(
267- req ,
268- post = TRUE ,
269- readfunction = function (nbytes , ... ) readBin(con , " raw" , nbytes ),
270- seekfunction = function (offset , ... ) seek(con , where = offset ),
271- postfieldsize_large = size
272- )
273- } else if (type == " raw" ) {
274- req <- req_body_apply_raw(req , data )
275- } else if (type == " json" ) {
276- req <- req_body_apply_raw(req , req_body_get(req ))
277- } else if (type == " multipart" ) {
278- data <- unobfuscate(data )
279- req $ fields <- data
280- } else if (type == " form" ) {
281- req <- req_body_apply_raw(req , req_body_get(req ))
282- } else {
283- cli :: cli_abort(" Unsupported request body {.arg type}." , .internal = TRUE )
284- }
241+ req <- switch (
242+ req_body_type(req ),
243+ empty = req ,
244+ raw = req_body_apply_raw(req , req $ body $ data ),
245+ string = req_body_apply_string(req , req $ body $ data ),
246+ file = req_body_apply_connection(req , req $ body $ data ),
247+ json = req_body_apply_string(req , req_body_get(req )),
248+ form = req_body_apply_string(req , req_body_get(req )),
249+ multipart = req_body_apply_multipart(req , req $ body $ data ),
250+ )
285251
286- # Respect existing Content-Type if set
287- type_idx <- match(" content-type" , tolower(names(req $ headers )))
288- if (! is.na(type_idx )) {
289- content_type <- req $ headers [[type_idx ]]
290- req $ headers <- req $ headers [- type_idx ]
291- } else {
292- content_type <- req $ body $ content_type
252+ # Set Content-Type if not already set
253+ if (! is.null(req $ body $ content_type ) && is.null(req $ headers $ `Content-Type` )) {
254+ req <- req_headers(req , `Content-Type` = req $ body $ content_type )
293255 }
294- req <- req_headers(req , `Content-Type` = content_type )
295256
296257 req
297258}
259+ req_body_apply_raw <- function (req , data ) {
260+ req_options(req , post = TRUE , postfieldsize = length(data ), postfields = data )
261+ }
262+ req_body_apply_string <- function (req , data ) {
263+ req_body_apply_raw(req , charToRaw(enc2utf8(data )))
264+ }
265+ req_body_apply_connection <- function (req , data ) {
266+ size <- file.info(data )$ size
267+ # Only open connection if needed
268+ delayedAssign(" con" , file(data , " rb" ))
298269
299- req_body_apply_raw <- function (req , body ) {
300- if (is_string(body )) {
301- body <- charToRaw(enc2utf8(body ))
302- }
303- req_options(req , post = TRUE , postfieldsize = length(body ), postfields = body )
270+ req <- req_policies(req , done = function () close(con ))
271+ req <- req_options(
272+ req ,
273+ post = TRUE ,
274+ readfunction = function (nbytes , ... ) readBin(con , " raw" , nbytes ),
275+ seekfunction = function (offset , ... ) seek(con , where = offset ),
276+ postfieldsize_large = size
277+ )
278+ req
279+ }
280+ req_body_apply_multipart <- function (req , data ) {
281+ req $ fields <- unobfuscate(req $ body $ data )
282+ req
304283}
0 commit comments