Send multipart request from julia using HTTP.jl

How can I send multipart request using HTTP.jl where first part is json ans seocnd is csv file?

HTTP.request("POST"...
import HTTP

url = "http://localhost:8080";

open("file.json") do json; open("file.csv") do csv
    headers = []
    body = HTTP.Form([
        "json" => json,
        "csv" => csv
    ])
    HTTP.post(url, headers, body)
end end

(You can check the request with e.g. netcat -lp 8080)

See documentation for HTTP.Form (and HTTP.Multipart), and Julia HTTP POST request with image attachment - Stack Overflow

5 Likes

Thank you. I think it is working, but I need to add filename of csv. Please, do you know how to add filename for it?

Did you read the documentation I linked? It is explained there, and also in the SO answer.

2 Likes

I tried mentioned advice:

url="http://$hostname:$port/myEndPoint"
headers = ["X-User-Id" => "d5as4d654asd65a64s5dad6a"]

body = HTTP.Form(Dict(
            "json" => HTTP.Multipart("data.json", open("data.json"), "text/json"),
            "csv" => HTTP.Multipart("data.csv", open("data.csv"), "text/csv"),
        ))

res = HTTP.post(url, headers, body)
        
inputJson   = JSON.parse(String(req.body))

but I still getting an error with filename:

Got exception outside of a @test
  HTTP.ExceptionRequest.StatusError(400, "POST", "/myEndPoint", HTTP.Messages.Response:
  """
  HTTP/1.1 400 Bad Request
  Content-Type: application/json
  Transfer-Encoding: chunked
  
  {"status":"InvalidInputData","description":"Item fileName is not recognized."}""")
  Stacktrace:
    [1] request(::Type{ExceptionLayer{ConnectionPoolLayer{StreamLayer{Union{}}}}}, ::URIs.URI, ::Vararg{Any, N} where N; kw::Base.Iterators.Pairs{Symbol, Union{Nothing, Bool}, Tuple{Symbol, Symbol}, NamedTuple{(:iofunction, :reached_redirect_limit), Tuple{Nothing, Bool}}})
      @ HTTP.ExceptionRequest ~/.julia/packages/HTTP/dCYNB/src/ExceptionRequest.jl:22

Then you need to consult the documentation of the API you are using, maybe the names have to be something specific.

2 Likes

I have another issue related to multipart. It looks like PUT is not working correctly. I tried same example for POST and PUT. Same example worked for POST and didn’t work for PUT.

When I ran it in swagger or with CURL command, PUT option was working. When I used following code using HTTP.jl it was failing on side of server in function “HTTP.parse_multipart_form(req)”. I don’t understand why it is working with swagger, curl, but not working with HTTP.jl.

Working POST:

url     = "http://$hostname:$port/datasets/csv"
headers = ["X-User-Id" => "9997773e-7e42-40d1-896b-5659a6229066",
"Content-Type"=> "multipart/form-data"]

mpdt    = Vector{Pair}(undef, 2)
mpdt[1] = "configuration"  => open("Upload.json")
mpdt[2] = "csv"            => HTTP.Multipart("Upload.csv",  open("Update.csv"),  "text/csv")

body = HTTP.Form(mpdt)
res = HTTP.request("POST", url, headers, body)

Not working PUT:

url     = "http://$hostname:$port/datasets/$(datasetId)/csv"
headers = ["X-User-Id" => "9997773e-7e42-40d1-896b-5659a6229066",
"Content-Type"=> "multipart/form-data"]

mpdt    = Vector{Pair}(undef, 2)
mpdt[1] = "configuration"  => open("Update.json")
mpdt[2] = "csv"            => HTTP.Multipart("Update.csv",  open("Update.csv"),  "text/csv")

body = HTTP.Form(mpdt)
res = HTTP.request("PUT", url, headers, body)

Missing is boundary for HTTP.jl request. Is something wrong in my code or in HTTP.jl?

From swagger:

"""
PUT /datasets/d891d099-9e90-4b63-95cb-f889670989fc/csv HTTP/1.1
X-User-Id: 1127773e-7e42-40d1-896b-5659a6ce9066
Content-Type: multipart/form-data; boundary=---------------------------84871092641888763363568039506
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:8080/swagger-ui


Origin: http://localhost:8080
Content-Length: 584
Connection: keep-alive

-----------------------------84871092641888763363568039506
Content-Disposition: form-data; name="configuration"

{
    "att1": "va1",
    "att2": "va2l",
    "att3": "val3"
}
-----------------------------84871092641888763363568039506
Content-Disposition: form-data; name="file"; filename="Update.csv"
Content-Type: text/csv

Date;pA;pB;pC
2004-01-01 01:00:00;2;22;222
2004-01-01 03:00:00;4;44;444
2004-01-01 05:00:00;6;66;666
-----------------------------84871092641888763363568039506--
"""

From HTTP.jl

"""
PUT /datasets/d891d099-9e90-4b63-95cb-f889670989fc/csv HTTP/1.1
X-User-Id: 1127773e-7e42-40d1-896b-5659a6ce9066
Content-Type: multipart/form-data;
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:8080/swagger-ui


Origin: http://localhost:8080
Content-Length: 584
Connection: keep-alive

-----------------------------84871092641888763363568039506
Content-Disposition: form-data;

{
    "att1": "va1",
    "att2": "va2l",
    "att3": "val3"
}
-----------------------------84871092641888763363568039506
Content-Disposition: form-data; name="file"; filename="Update.csv"
Content-Type: text/csv

Date;pA;pB;pC
2004-01-01 01:00:00;2;22;222
2004-01-01 02:00:00;4;44;444
2004-01-01 03:00:00;6;66;666
-----------------------------84871092641888763363568039506--
"""

HTTP.jl only adds the Content-Type automatically for POST: https://github.com/JuliaWeb/HTTP.jl/blob/255a75907a06b1340e7deac659383f15f3aa55f0/src/MessageRequest.jl#L58-L61.

I added that code but didn’t know if it made sense for any other request types. You can probably make a PR and relax that to POST or PUT at least.

I added this, will be available in HTTP.jl 0.9.15.