HTTPSocket POST Multipart

In an iOS app I’m trying to send a couple of files to a web server using an HTTPSocket. This is what the website says about the multipart/form-data option:

One file is in plain text, and the other is in an XML format. Both files use CRLF for end-of-lines.

Here’s the data I’m sending:

[code]Content-Type: multipart/form-data; boundary=IMTU

–IMTU
Content-Disposition: form-data; name=“file”

Plain text data goes here

–IMTU
Content-Disposition: form-data; name=“metadata”

XML data goes here

–IMTU–[/code]
I’m getting a 400 response from the server and can’t see what I’m doing wrong. Any suggestions on this data format?

BTW, it’s mysterious to me what HTTPSocket is actually sending to the server.

This line is not part of the form. It should be one of the request headers. You can set the form contents and the header with the SetRequestContent method.
e.g.

myHTTPSocket.SetRequestContent(formdata, "multipart/form-data; boundary=IMTU")

Thanks, Andrew. Since this is an iOS App I’m using the new HTTPSocket which doesn’t have SetPostContent. I’m currently using:

SubsectorSocket.SetRequestContent(data, "multipart/form-data") SubsectorSocket.Send("POST", URLtext)

Should I add this?

SubsectorSocketRequest.Header("boundary") = "IMTU"

Sorry, I typed the wrong name. SetRequestContent is the replacement for SetPostContent.

[code]
Should I add this?

SubsectorSocketRequest.Header(“boundary”) = “IMTU”[/code]

No, the header will be set by the SetRequestContent method. Also, the boundary should come after the header value like so:

 SubsectorSocket.SetRequestContent(data, "multipart/form-data; boundary=IMTU")

Interesting. This feature is not documented in HTTPSocket, it just says the parameter is a Mime Type. So now my data is:

[code]–IMTU
Content-Disposition: form-data; name=“file”

Plain text data goes here

–IMTU
Content-Disposition: form-data; name=“metadata”

XML data goes here

–IMTU–[/code]

and I’m still getting a 400 response. Do I need to specify Content-Type for the parts?

Depending on the context it’s used in, a MIMEtype string may have a list of semicolon-delimited name=value directives after the main type/sub-type identifier. For multipart/form-data, the boundary directive is mandatory in all contexts.

[quote]
Do I need to specify Content-Type for the parts?[/quote]

Only for parts that should be interpreted as a file for upload. Such parts also include the suggested file name:

[code]–IMTU
Content-Disposition: form-data; name=“file”; filename=“SuggestedName.txt”
Content-Type: text/plain

File data goes here

–IMTU
Content-Disposition: form-data; name=“metadata”

XML data goes here

–IMTU–
[/code]

If the server is expecting one of your form fields to be a file then that may explain the error.

That had no effect. I’m still wondering if HTTPSocket builds the data in this format from a form. The website docs I quoted in my first post mentions input type = “file” elements. So I tried changing the data to this:

[code]

[/code]

But this still returns a 400 response. Any thoughts?

Maybe you need HTTP 1.1 with a new framework socket?

I’m using the new framework socket, aren’t I? It’s iOS, and I don’t think I have a choice.

Ah, okay! You are correct as well. I’ve been following, but totally forgot you’d mentioned it was iOS.

The HTTPSocket doesn’t know how to build a multipart form, you have to build it and then pass it to the socket with the SetRequestContent method (which you’re already doing.)

Shots in the dark:

  • Some servers require you to set a User-Agent header in the request
  • Some servers reject the form data if it has leading white space
  • Verify that the form data uses the correct end-of-line character (carriage return+line feed)
  • You must ensure that the boundary string does not appear anywhere in the data contained in the form

Confirmed. I used the Charles proxy app to find out what was being sent.

Final solution:

I used:

[code] Dim data As MemoryBlock
data = TextEncoding.UTF8.ConvertTextToData(t)

SubsectorSocket.SetRequestContent(data, “multipart/form-data; boundary=” + boundarytext)
SubsectorSocket.Send(“POST”, URLtext)
[/code]

Where (in my case):

URLtext = https://travellermap.com/api/poster?subsector=A&scale=72&style=poster&options=25595&sscords=1
boundarytext = "IMTUStrephonLives"

data:

--IMTUStrephonLives
Content-Disposition: form-data; name="file"; filename="sectordata.txt"
Content-Type: text/plain

contents of sectordata.text

--IMTUStrephonLives
Content-Disposition: form-data; name="metadata"; filename="sectormetadata.txt"
Content-Type: text/plain

contents of sectormetadata.text

--IMTUStrephonLives--

and what was sent:

[code]POST /api/poster?subsector=A&scale=72&style=poster&options=25595&sscords=1 HTTP/1.1
Host: travellermap.com
Content-Type: multipart/form-data; boundary= IMTUStrephonLives
Connection: keep-alive
Accept: /
User-Agent: SectorMaker.debug/1.0.0 CFNetwork/901.1 Darwin/17.7.0
Accept-Language: en-us
Accept-Encoding: br, gzip, deflate
Content-Length: 2288

–IMTUStrephonLives
Content-Disposition: form-data; name=“file”; filename=“sectordata.txt”
Content-Type: text/plain

contents of sectordata.text

–IMTUStrephonLives
Content-Disposition: form-data; name=“metadata”; filename=“sectormetadata.txt”
Content-Type: text/plain

contents of sectormetadata.text

–IMTUStrephonLives–[/code]