diff --git a/CHANGES.md b/CHANGES.md index de8bc22f59..fd74f5eb6a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,7 @@ New: * Added `string.char`, `string.getter.flush` and `string.getter.concat`. -* Added `http.post.multipart_form_data` and `http.post.file`. +* Added `http.multipart_form_data` and `http.{post,put}.file`. Changed: * Allow sub-second values in `sleep()` (#2610) diff --git a/libs/http.liq b/libs/http.liq index 3d194f5fdd..67eced576b 100644 --- a/libs/http.liq +++ b/libs/http.liq @@ -228,7 +228,7 @@ end # @category Interaction # @param ~boundary Specify boundary to use for multipart/form-data. # @param data data to insert -def http.post.multipart_form_data(~boundary=null(), data) +def http.multipart_form_data(~boundary=null(), data) def default_boundary() range = [...string.char.ascii.alphabet, ...string.char.ascii.number] l = list.init(12, fun(_) -> string.char.ascii.random(range)) @@ -269,23 +269,11 @@ def http.post.multipart_form_data(~boundary=null(), data) { contents = string.getter.concat(contents), boundary = boundary } end +# @flag hidden stdlib_file = file -# Send a file via POST request encoded in multipart/form-data. The contents can -# either be directly specified (with the `contents` argument) or taken from a -# file (with the `file` argument). -# @category Interaction -# @param ~name Name of the field field -# @param ~content_type Content-type (mime) for the file. -# @param ~headers Additional headers. -# @param ~boundary Specify boundary to use for multipart/form-data. -# @param ~filename File name sent in the request. -# @param ~file File whose contents is to be sent in the request. -# @param ~contents Contents of the file sent in the request. -# @param ~timeout Timeout in ms. -# @param ~redirect Follow reidrections. -# @param url URL to post to. -def http.post.file(~name="file", ~content_type=null(), ~headers=[], ~boundary=null(), ~filename=null(), ~file=null(), ~contents=null(), ~timeout=null(), ~redirect=true, url) +# @flag hidden +upload_file_fn = fun (~name, ~content_type, ~headers, ~boundary, ~filename, ~file, ~contents, ~timeout, ~redirect, url, fn) -> begin if not null.defined(filename) and not null.defined(file) then error.raise(error.http, "At least one of: `file` or `filename` must be defined!") end @@ -300,15 +288,9 @@ def http.post.file(~name="file", ~content_type=null(), ~headers=[], ~boundary=nu # Create query - content_type = - if null.defined(content_type) then - null.get(content_type) - else - mime = stdlib_file.mime_default(filename) - mime == "" ? "application/octet-stream" : mime - end + content_type = content_type ?? "application/octet-stream" - data = http.post.multipart_form_data(boundary=boundary, [{ + data = http.multipart_form_data(boundary=boundary, [{ name=name, attributes=[("filename", filename)], headers=[("Content-Type",content_type)], @@ -316,5 +298,43 @@ def http.post.file(~name="file", ~content_type=null(), ~headers=[], ~boundary=nu }]) headers = ("Content-Type", "multipart/form-data; boundary=#{data.boundary}")::headers - http.post(headers=headers, timeout_ms=timeout, redirect=redirect, data=data.contents, url) + fn(headers=headers, timeout_ms=timeout, redirect=redirect, data=data.contents, url) +end + +# Send a file via POST request encoded in multipart/form-data. The contents can +# either be directly specified (with the `contents` argument) or taken from a +# file (with the `file` argument). +# @category Interaction +# @param ~name Name of the field field +# @param ~content_type Content-type (mime) for the file. +# @param ~headers Additional headers. +# @param ~boundary Specify boundary to use for multipart/form-data. +# @param ~filename File name sent in the request. +# @param ~file File whose contents is to be sent in the request. +# @param ~contents Contents of the file sent in the request. +# @param ~timeout Timeout in ms. +# @param ~redirect Follow reidrections. +# @param url URL to post to. +def http.post.file(~name="file", ~content_type=null(), ~headers=[], ~boundary=null(), ~filename=null(), ~file=null(), ~contents=null(), ~timeout=null(), ~redirect=true, url) + upload_file_fn(name=name, content_type=content_type, headers=headers, boundary=boundary, filename=filename, + file=file, contents=contents, timeout=timeout, redirect=redirect, url, http.post) +end + +# Send a file via PUT request encoded in multipart/form-data. The contents can +# either be directly specified (with the `contents` argument) or taken from a +# file (with the `file` argument). +# @category Interaction +# @param ~name Name of the field field +# @param ~content_type Content-type (mime) for the file. +# @param ~headers Additional headers. +# @param ~boundary Specify boundary to use for multipart/form-data. +# @param ~filename File name sent in the request. +# @param ~file File whose contents is to be sent in the request. +# @param ~contents Contents of the file sent in the request. +# @param ~timeout Timeout in ms. +# @param ~redirect Follow reidrections. +# @param url URL to put to. +def http.put.file(~name="file", ~content_type=null(), ~headers=[], ~boundary=null(), ~filename=null(), ~file=null(), ~contents=null(), ~timeout=null(), ~redirect=true, url) + upload_file_fn(name=name, content_type=content_type, headers=headers, boundary=boundary, filename=filename, + file=file, contents=contents, timeout=timeout, redirect=redirect, url, http.put) end diff --git a/src/builtins/builtins_http.ml b/src/builtins/builtins_http.ml index 5cf7750034..1d770ccd0f 100644 --- a/src/builtins/builtins_http.ml +++ b/src/builtins/builtins_http.ml @@ -54,11 +54,11 @@ let add_http_request ~stream_body ~descr ~request name = else [ ( "data", - Lang.getter_t (Lang.nullable_t Lang.string_t), + Lang.getter_t Lang.string_t, Some (Lang.string ""), Some - "POST data. Use a `string?` getter to stream data and return \ - `null` when all data has been passed." ); + "POST data. Use a `string` getter to stream data and return `\"\"` \ + when all data has been passed." ); ] in let params = diff --git a/src/tools/liqcurl.ml b/src/tools/liqcurl.ml index 99c370a96a..f202326a30 100644 --- a/src/tools/liqcurl.ml +++ b/src/tools/liqcurl.ml @@ -146,12 +146,6 @@ let rec http_request ?headers ?http_version ~follow_redirect ~timeout ~url connection#set_writefunction (fun s -> on_body_data s; String.length s); - ignore - (Option.map - (fun headers -> - connection#set_httpheader - (List.map (fun (k, v) -> Printf.sprintf "%s: %s" k v) headers)) - headers); connection#set_httpversion (match http_version with | None -> Curl.HTTP_VERSION_NONE @@ -176,6 +170,12 @@ let rec http_request ?headers ?http_version ~follow_redirect ~timeout ~url connection#set_readfunction get_data | `Head -> connection#set_nobody true | `Delete -> connection#set_customrequest "DELETE"); + ignore + (Option.map + (fun headers -> + connection#set_httpheader + (List.map (fun (k, v) -> Printf.sprintf "%s: %s" k v) headers)) + headers); let response_headers = Buffer.create 1024 in connection#set_headerfunction (fun s -> Buffer.add_string response_headers s;