我想允许用户从本地文件系统中选择图像,然后将此图像渲染到页面上的画布元素。
由于安全限制(“沙盒”浏览器安全模型),客户端 JavaScript 无法直接访问文件系统上的图像,因此它必须从文件上传中以“multipart/form-data”形式往返到服务器控制。
我不想实际将此图像保存在服务器上并将其提供出去,因为它仅用于一次性客户端操作目的。所以,我想知道是否可以将图像数据服务器端转换为可以发送回客户端的 Base64 编码表示形式。然后我可以轻松地将其作为数据 URL 绘制回客户端,而无需将图像保存在服务器上的任何位置。或者有更好的办法吗?
我在服务器上使用node.js。
我认为不需要将其保存在服务器上或产生 Base64 编码数据 URI 的成本;只需将其存储在内存中足够长的时间以供客户端下载即可。 所以你的序列将是这样的:
请注意,由于 Node.js 和 AJAX 请求的异步特性,第 1-4 项可以同时完成(尽管“废弃”URL 可能必须与表单发布分开商定,才能真正发挥作用)方式)。
转到 https://github.com/joyent/node/wiki/modules,获取一个 Base64 编码库并简单地回显
data:image/png;base64,
,然后是 Base-64 编码文件,将“png”替换为适当的内容(gif/jpeg)
“更好”的方法是将文件实际放入 tmp 文件夹中,回显该文件的链接,然后删除该目录中早于 X 分钟的所有文件。
虽然问题和答案都正确指出在这个特定的用例中不需要在服务器上存储文件,但人们在网上搜索如何做这件事时很容易找到这篇文章,所以:
在客户端,您需要将数据作为FormData 发送,您可以通过(毫不奇怪)使用表单来完成:
<form action="/upload" method="POST">
<input type="file" name="file">
<!-- buttons inside forms are submit buttons by default -->
<button>upload</button>
</form>
或者在JS端手动创建一个:
async function upload(fileName, type, content) {
// When content is a string, it's already "ready to post",
// but let's assume that your binary data takes the form
// of an ArrayBuffer, e.g. you got it from a drag-and-drop
// event and you used reader.readAsArrayBuffer() to get it:
if (content instanceof ArrayBuffer) {
content = new Blob([content], { type });
}
// Set up our form
const formData = new FormData();
formData.append(`fileName`, fileName);
formData.append(`content`, content);
// And then post it.
const response = await fetch(`/upload`, {
method: `POST`,
body: formData,
});
// And remember that can fail, of course
if (response.status !== 200) {
console.error(await response.text());
}
}
无论您如何发送它,服务器将得到的是,毫不奇怪,一个多部分消息,具有两种类型的标头:HTTP标头和正文标头,所有内容都使用CRLF作为行结尾,例如:
POST / HTTP/1.1\r\n
Host: localhost:1234\r\n
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20110101 Firefox/100.0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-Language: en-US,en;q=0.5\r\n
Accept-Encoding: brotli, gzip, deflate\r\n
Cookie: __abcde=ABCDE; permanent=0; _session_id=1234567890; request_method=GET\r\n
Connection: keep-alive\r\n
Content-Type: multipart/form-data; boundary=BndryVlu123\r\n
Content-Length: 1234\r\n
\r\n
--BndryVlu123\r\n
Content-Disposition: form-data; name="text"\r\n
\r\n
some text\r\n
--BndryVlu123\r\n
Content-Disposition: form-data; name="file1"; filename="a.txt"\r\n
Content-Type: text/plain\r\n
\r\n
Content of a.txt.\r\n
\r\n
--BndryVlu123\r\n
Content-Disposition: form-data; name="file2"; filename="a.html"\r\n
Content-Type: text/html\r\n
\r\n
<!DOCTYPE html><title>Content of a.html.</title>\r\n
\r\n
--BndryVlu123--\r\n
RFC 7578是关于如何解析多部分/表单数据的最新RFC(在本答案时),读起来有点困难,但可以归结为:
HTTPContent-Type
标头通过列出
boundary
值告诉我们分隔符是什么:在本例中,我们看到
multipart/form-data; boundary=BndryVlu123
,这意味着该消息的其余部分将使用
--BndryVlu123\r\n
作为部分分隔符(带有
--
)在前面),以及
--BndryVlu123--\r\n
(末尾有一个额外的
--
)作为消息标记的结束。还要特别注意这个分隔符前面有
--
:一些多部分帖子消息将使用已经有一大堆破折号的边界值,例如
-------------------US12H154ER657OUG
,因此分隔符可能看起来相同对于我们弱小的人眼来说,但它的前面仍然会有两个破折号。 鉴于此,我们可以通过一次处理一个部分来处理原始多部分帖子,从第一个
--BndryVlu123\r\n
实例之后开始(无论是在 POST 发送给我们时从流中执行此操作,还是从完全形成的有效负载中执行此操作) POST 消息最终确定后的消息基本上是无关紧要的):
\r\n
Content-Disposition
行,请阅读
name
和可选
filename
。
Content-Type
行,请解析它。
Content-Transfer-Encoding
行,肯定会解析它
\r\n
),则转到 (2)
\r\n--BndryVlu123
,表示本部分结束。我们可以将这部分视为已完成,但根据是否存在
Content-Transfer-Encoding
标头,我们可能需要在聚合字节构成有用数据之前对其进行解码。
--
,则已到达 POST 消息的末尾,并且应该停止(即使该位置与 HTTP
Content-Size
标头冲突。不解析垃圾数据,最好的情况是什么也没有发生,最坏的情况是允许利用的可能性)。如果回到(1)。