Browser support of multipart responses - http

I would like to create a HTTP response, using multipart/mixed, but I'm not sure which browsers support it; and if it's as convenient as it sounds, from the client's point of view.
To be honest, I do not need specifically that content type. I just want to transmit more than one file in the same response; maybe there's another content-type more used.

I've tested it, with a home-made server and a simple response. Not sure if the response is well-formed because no browser understands it 100% OK. But here are the results:
Firefox 67.0.1 (64-bit): Renders only the last part, others are ignored.
IE 11.503: Saves all the content in a single file (including the boundaries), nothing is rendered.
Chrome May 2019: Saves all the content in a single file, nothing is rendered.
Safari 4: Saves all the content in a single file, nothing is rendered.
Opera 10.10: Something weird. Starts rendering the first part as plain/text, and then clears everything. The loading progress bar hangs on 31%.
Here's the complete response, if anyone finds any error, please tell me and I'll try again:
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2009 23:27:30 GMT
Vary: Accept-Encoding,User-Agent
Content-Length: 681
Content-Type: Multipart/mixed; boundary="sample_boundary";
Multipart not supported :(
--sample_boundary
Content-Type: text/css; charset=utf-8
Content-Location: http://localhost:2080/file.css
body
{
background-color: yellow;
}
--sample_boundary
Content-Type: application/x-javascript; charset=utf-8
Content-Location: http://localhost:2080/file.js
alert("Hello from a javascript!!!");
--sample_boundary
Content-Type: text/html; charset=utf-8
Content-Base: http://localhost:2080/
<html>
<head>
<link rel="stylesheet" href="http://localhost:2080/file.css">
</head>
<body>
Hello from a html
<script type="text/javascript" src="http://localhost:2080/file.js"></script>
</body>
</html>
--sample_boundary--

In my experience, multipart responses work in Firefox but not in Internet Explorer. This was 2 years ago, using the browsers of the time.
I have had HTTP multipart responses working for a stream of JPEG images. For example, Axis IP cameras use for their motion JPEG stream for Firefox. For Internet explorer, Axis require the use of a plugin.
If Firefox-only support meets your requirements, then I recommend setting the content-length header in each part of the multi-part response. It might help to make the boundary string identical in the original HTTP header and the multi-part response (the '--' is missing in the HTTP header).

Two ideas:
Formatting: I think "multipart" should be in lower case, and I don't think a semicolon is expected at the end of the Content-type header (although it's doubtful that it will make a difference, it's possible that it might).
Have you tried replace mode? Just use: Content-type: multipart/x-mixed-replace -- everything else should stay the same.

 Multi part it yourself
(A good option)
A multipart response can be made manually!
So one can write a no multipart response! Let's say in chunked mode! There it make sense!
So you are streaming the data!
Send all as blunt text!
Make your own separators! Between each part!
In the browser! Extract and parse the data! Split to get each part separately!
And parse each appart! Depending on what type of data it hold!
So if a part is json! You parse it as so!
Quick illustration! Let say we want to send a csv file! Or some other type of file! Along that we want to send too a json object!
And that by streaming it by chunk
Here a code that illustrate that in express:
const data = {
initCapital: fileState.mappingObj.initialCapital
};
res.write(JSON.stringify(data, undefined, 0));
res.write('>>>>'); // parts separator
fileState.readStream.pipe(res); // continue streaming the file (chunk by chunk)
And in the client
export function parseBackTestFetchedData(data: string) {
const [_data, csvData] = data.split('>>>>');
return {
data: JSON.parse(_data),
backTestStatesData: parseCsv(csvData)
};
}
That way! it doesn't matter who the client is!

Related

Get Hunchentoot to output no headers at all

I want to return a TSV file from a web call in Hunchentoot (SBCL), but want the user to just save the raw result blatted to the page, rather than use a separate file and download link (which is hard because of local firewall complexities).
I can't figure out how to output the page without any headers at all, i.e., to make it just plain raw text. (I know that the browser would make a mess w/o headers in the DOM, but don't care; the goal is just to have the user save the page, not read it.)
I've tried various combinations of
(setf (hunchentoot:content-type*) "text/plain")
and
(cl-who:with-html-output-to-string
(*standard-output* nil :prologue nil)
and setting the content-type* inside, outside, and around the with... but I always get header junk.
Writing a string directly
I tried defining a handler as follows:
(define-easy-handler (text :uri "/text") ()
(setf (content-type*) "text/csv")
"a,b,c")
When I visit the page locally, the browser automatically downloads a text file without even displaying (this is probably a setting we can change in Chrome, I don't know).
When I enable the browser developer mode, here are the response headers I receive as part of the HTTP protocol:
HTTP/1.1 200 OK
Server: ...
Date: ...
Content-Type: text/csv; charset=utf-8
Content-Length: 5
Connection: keep-alive
But the file itself is just the string a,b,c.
If I change the content-type to "text/plain", then the browser successfully displays the text, and nothing else (the HTTP headers are the same).
Remarks
You don't need to use the cl-who macros if you do not intend to build an HTML document, in fact its better not to. In any case, you can supply your own REPLY-CLASS when initializing the acceptor (see https://edicl.github.io/hunchentoot/#replies) and have a very low-level control about what you emit as a reply, headers included. But I don't think this is necessary in your case. I don't clearly understand where your problem comes from, but sending back a plain text is something the framework is supposed to be able to do out of the box. Please add more details if you can.
Is the correct answer not to use the Content-Disposition header?

What is the process by which sinatra's send_file decides what content-type to use?

What is the process by which sinatra's send_file decides what content-type to use?
For example, it seems that it works by the extension of the file passed to send_file, so if it is send_file blah.txt . then when I http to the route, I will get/ the response header will be, content-type: text/plain, so any html in the txt file will be interpreted by the web browser as plain text. Whereas if the file is blah.html then the server will respond with content-type: text/html.(and any html in the file is rendered as such)
And of course the route name is irrelevant so you could go to http://127.0.0.1:4567/zzz.html and it could lead to send_file a.txt and a.txt may contain html tags but since it's a .txt file send_file will cause sinatra to respond with content-type: text/plain and the browser won't render any html sent and will show it as plain text. I may be wrong but that seems to be what my quick tests indicate. Where I tried different routes, different filename extensions(.txt, and .html), sometimes files with html in them sometimes not, seeing whether the browser renders the html or not, and seeing what the content-type header was, with wget -d.
So then my question related to that is, is there a list that sinatra's send_file function uses, that relates file extension to content-type? I would like to see that list. And if not, then what is the process it is using.
Note- I understand there is a way to pass in a content-type Sinatra: How to respond with an image with headers "content-type" => "image/jpeg"
but i'm asking how/ by what method, send_file determines content-type when no content-type is passed in.
This is the send_file method in the Sinatra framework (currently v2.0.5), notice it hands off finding out the content type straight away if none has been set:
if opts[:type] or not response['Content-Type']
content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
end
The content_type method will either return immediately or hand off to mime_type, which is a delegate of Rack's mime_type method (currently v2.0.7). This uses a well known list of extensions to check against.
def mime_type(ext, fallback='application/octet-stream')
MIME_TYPES.fetch(ext.to_s.downcase, fallback)
end
The list begins on line 49:
MIME_TYPES = {
".123" => "application/vnd.lotus-1-2-3",
".3dml" => "text/vnd.in3d.3dml",
".3g2" => "video/3gpp2",
".3gp" => "video/3gpp",
# <snip>
As you can see from the content_type snippet, the default it falls back on is application/octet-stream.

Firefox and chrome get different header than IE

My .mvc application tries to show a pdf file. It works properly on IE, but fails on Chrome and FF, giving ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION According to similar questions asked here and on other websites, it seems to be a http header problem? When I tried looking at the headers directly, I've found that that responses sent to IE and Firefox differ, as FF gets 3 responses while IE gets 1:
IE response header:
Response HTTP/1.1 200 OK
Cache-Control no-cache, no-store, must-revalidate
Pragma no-cache
Content-Type application/pdf
Expires 0
Server Microsoft-IIS/7.5
X-AspNetMvc-Version 4.0
Content-Disposition inline; filename="Invoice Number US123412.pdf#toolbar=1&view=FitV"
Content-Disposition attachment; filename="Invoice Number US123412.pdf"
X-AspNet-Version 4.0.30319
Persistent-Auth true
X-Powered-By ASP.NET
Date Wed, 19 Apr 2017 12:00:00 GMT
Content-Length 77107
The solution that was given by many people was to add qutation marks ("") around the filename, which I did, but it doesn't seem to help in any way. Here is my header editing code:
Response.AddHeader("Content-Disposition", "inline; filename=\"" + fileName + "#toolbar=1&view=FitV\"");
Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
Response.AppendHeader("Pragma", "no-cache"); // HTTP 1.0.
Response.AppendHeader("Expires", "0"); // Proxies.
invoiceHelper.PDF = new InvoicePDF { FileBytes = renderedBytes, FileName = fileName };
return File(renderedBytes, "application/pdf", fileName);
It seems I might be misunderstanding the solution suggested in other threads? Any help would be appreciated.
EDIT:
I should note that I also tried changing the file name to contain no spaces, so that the current response would have Invoice_Number_somenuber12121
EDIT2:
It seems that when I remove my header code, and instead add esponse.ClearHeaders();, the website loads but the pdf is downloaded instead of being shown
EDIT3:
I've found the answer, and so I removed a few parts of this question that weren't significant after all.
a) There can only be one response; maybe what you see is a redirect and a new request?
b) In any case, the error code is pretty clear: you are sending multiple Content-Disposition response header fields. Don't. (Note: it's possible that one of those was added by the framework you're using).
I've found the answer. As is often the case, I found it AFTER i posted on stack overflow, not the whole day I was searching for it before that. As seen in the comment to the answer here: Returning a file to View/Download in ASP.NET MVC , the problem lies with the return File(renderedBytes, "application/pdf", fileName); . The moment you return 3 parameters, the return statement will create its own header. In my case, I just removed the third parameter.
Hint to other people who might have similar problem: try modifying the fileName variable, or some other variable that is used in your header, to pinpoint the more precise location of the problem.

What's the difference between 'multipart/related' and 'multipart/form-data' and when should I use each?

I was able to find a lot of information about multipart/form-data but not much about multipart/related. In terms of the protocol / request format, can someone explain the differences between these two http specifications when it comes to file uploading?
multipart/form-data is used to upload files of MIME-compatible representation, such as pictures and video files, and related metadata a single POST request. That's what happens when you fill in a form online with attached pictures and then press the "Submit" button.
multipart/related is used for compound documents and you would need to combine the separate body parts to provide the full meaning of the message. One use case would be submitting some Base64-encoded images together with the associated metadata.
One POST request sample is (https://cloud.google.com/storage/docs/json_api/v1/how-tos/multipart-upload):
POST https://www.googleapis.com/upload/storage/v1/b/myBucket/o?uploadType=multipart HTTP/1.1
Authorization: Bearer [YOUR_AUTH_TOKEN]
Content-Type: multipart/related; boundary=foo_bar_baz
Content-Length: [NUMBER_OF_BYTES_IN_ENTIRE_REQUEST_BODY]
--foo_bar_baz
Content-Type: application/json; charset=UTF-8
{
"name": "myObject"
}
--foo_bar_baz
Content-Type: image/jpeg
[JPEG_DATA]
--foo_bar_baz--
You can find more details at https://msdn.microsoft.com/en-us/library/ms527355(v=exchg.10).aspx

How can I find out whether a server supports the Range header?

I have been trying to stream audio from a particular point by using the Range header values but I always get the song right from the beginning. I am doing this through a program so am not sure whether the problem lies in my code or on the server.
How can I find out whether the server supports the Range header param?
Thanks.
The way the HTTP spec defines it, if the server knows how to support the Range header, it will. That in turn, requires it to return a 206 Partial Content response code with a Content-Range header, when it returns content to you. Otherwise, it will simply ignore the Range header in your request, and return a 200 response code.
This might seem silly, but are you sure you're crafting a valid HTTP request header? All too commonly, I forget to specify HTTP/1.1 in the request, or forget to specify the Range specifier, such as "bytes".
Oh, and if all you want to do is check, then just send a HEAD request instead of a GET request. Same headers, same everything, just "HEAD" instead of "GET". If you receive a 206 response, you'll know Range is supported, and otherwise you'll get a 200 response.
This is for others searching how to do this. You can use curl:
curl -I http://exampleserver.com/example_video.mp4
In the header you should see
Accept-Ranges: bytes
You can go further and test retrieving a range
curl --header "Range: bytes=100-107" -I http://exampleserver.com/example_vide0.mp4
and in the headers you should see
HTTP/1.1 206 Partial Content
and
Content-Range: bytes 100-107/10000000
Content-Length: 8
[instead of 10000000 you'll see the length of the file]
Although I am a bit late in answering this question, I think my answer will help future visitors. Here is a python method that detects whether a server supports range queries or not.
def accepts_byte_ranges(self, effective_url):
"""Test if the server supports multi-part file download. Method expects effective (absolute) url."""
import pycurl
import cStringIO
import re
c = pycurl.Curl()
header = cStringIO.StringIO()
# Get http header
c.setopt(c.URL, effective_url)
c.setopt(c.NOBODY, 1)
c.setopt(c.HEADERFUNCTION, header.write)
c.perform()
c.close()
header_text = header.getvalue()
header.close()
verbose_print(header_text)
# Check if server accepts byte-ranges
match = re.search('Accept-Ranges:\s+bytes', header_text)
if match:
return True
else:
# If server explicitly specifies "Accept-Ranges: none" in the header, we do not attempt partial download.
match = re.search('Accept-Ranges:\s+none', header_text)
if match:
return False
else:
c = pycurl.Curl()
# There is still hope, try a simple byte range query
c.setopt(c.RANGE, '0-0') # First byte
c.setopt(c.URL, effective_url)
c.setopt(c.NOBODY, 1)
c.perform()
http_code = c.getinfo(c.HTTP_CODE)
c.close()
if http_code == 206: # Http status code 206 means byte-ranges are accepted
return True
else:
return False
One way is just to try, and check the response. In your case, it appears the server doesn't support ranges.
Alternatively, do a GET or HEAD on the URI, and check for the Accept-Ranges response header.
You can use GET method with 0-0 Range request header, and check whether the response code is 206 or not, which will respond with
the first and last bytes of the response body
You also can use HEAD method do the same thing as the first session which will get the same response header and code without response body
Furthermore, you can check Accept-Ranges on the response header to judge whether it can support range, but please notice if the value is none on Accept-Ranges field, it means it can't support range, and if the response header doesn't have Accept-Ranges field you also can't finger out it can't support range from it.
There is another thing you have to know if you are using 0- Range on the request header with GET method to check the response code, the response body message will be cached automatically on the TCP receive window until the cache is full.

Resources