您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關怎么在node中對文件上傳接口進行轉發,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
js 中的文件
web 中的 Blob 、File 和 Formdate
一個 Blob ( Binary Large Object ) 對象表示一個不可變的, 原始數據的類似文件對象。Blob表示的數據不一定是一個JavaScript原生格式。 File 接口基于Blob,繼承 Blob 功能并將其擴展為支持用戶系統上的文件。
前端上傳文件的方式無非就是使用:1、表單自動上傳;2、使用 ajax 上傳。我們可以使用以下代碼創建一個 Form,并打印出 file
<form method="POST" id="uploadForm" enctype="multipart/form-data"> <input type="file" id="file" name="file" /> </form> <button id="submit">submit</button> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script> $("#submit").click(function() { console.log($("#file")[0].files[0]) }); </script>
從 F12 中可以看出 File 原型鏈上是 Blob。
簡單地說 Blob 可以理解為 Web 中的二進制文件。 而 File 是基于 Blob 實現的一個類,新增了關于文件有關的一些信息。
FormData對象的作用就類似于 Jq 的 serialize() 方法,不過 FormData 是瀏覽器原生的,且支持二進制文件。 ajax 通過 FormData 這個對象發送表單請求,無論是原生的 XMLHttpRequest 、jq 的 ajax 方法、 axios 都是在 data 里直接指定上傳 formData 類型的數據,fetch api 是在 body 里上傳。
forData 數據有兩種方式生成,如下 formData 和 formData2 的區別,而 formData2 可以通過傳入一個 element 的方式進行初始化,初始化之后依然可以調用 formData 的 append 方法。
<!DOCTYPE html> <html> <form method="POST" id="uploadForm" name="uploadFormName" enctype="multipart/form-data"> <input type="file" id="fileImag" name="configFile" /> </form> <div id="show"></div> <button id="submit">submit</button> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> </html> <script> $("#submit").click(function() { const file = $("#fileImag")[0].files[0]; const formData = new FormData(); formData.append("fileImag", file); console.log(formData.getAll("fileImag")); const formData2 = new FormData(document.querySelector("#uploadForm")); // const formData2 = new FormData(document.forms.namedItem("uploadFormName");); console.log(formData2.get("configFile")); }); </script>
console.log() 無法直接打印出 formData 的數據,可以使用 get(key) 或者 getAll(key)
如果是使用 new FormData(element) 的創建方式,上面 key 為 <input /> 上的 name 字段。
如果是使用 append 添加的數據,get/getAll 時 key 為 append 所指定的 key。
node 中的 Buffer 、 Stream 、fs
Buffer 和 Stream 是 node 為了讓 js 在后端擁有處理二進制文件而出現的數據結構。
通過名字可以看出 buffer 是緩存的意思。存儲在內存當中,所以大小有限,buffer 是 C++ 層面分配的,所得內存不在 V8 內。
stream 可以用水流形容數據的流動,在文件 I/O、網絡 I/O中數據的傳輸都可以稱之為流。
通過兩個 fs 的 api 看出,readFile 不指定字符編碼默認返回 buffer 類型,而 createReadStream 將文件轉化為一個 stream , nodejs 中的 stream 通過 data 事件能夠一點一點地拿到文件內容,直到 end 事件響應為止。
const fs = require("fs"); fs.readFile("./package.json", function(err, buffer) { if (err) throw err; console.log("buffer", buffer); }); function readLines(input, func) { var remaining = ""; input.on("data", function(data) { remaining += data; var index = remaining.indexOf("\n"); var last = 0; while (index > -1) { var line = remaining.substring(last, index); last = index + 1; func(line); index = remaining.indexOf("\n", last); } remaining = remaining.substring(last); }); input.on("end", function() { if (remaining.length > 0) { func(remaining); } }); } function func(data) { console.log("Line: " + data); } var input = fs.createReadStream("./package.json"); input.setEncoding("binary"); readLines(input, func);
fs.readFile() 函數會緩沖整個文件。 為了最小化內存成本,盡可能通過 fs.createReadStream() 進行流式傳輸。
使用 nodejs 創建 uoload api
http 協議中的文件上傳
在 http 的請求頭中 Content-type 是 multipart/form-data 時,請求的內容如下:
POST / HTTP/1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoMwe4OxVN0Iuf1S4 Origin: http://localhost:3000 Referer: http://localhost:3000/upload Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 ------WebKitFormBoundaryoqBx9oYBhx4SF1YQ Content-Disposition: form-data; name="upload" http://localhost:3000 ------WebKitFormBoundaryoMwe4OxVN0Iuf1S4 Content-Disposition: form-data; name="upload"; filename="IMG_9429.JPG" Content-Type: image/jpeg ????JFIF??C // 文件的二進制數據 …… --------WebKitFormBoundaryoMwe4OxVN0Iuf1S4--
根據 WebKitFormBoundaryoMwe4OxVN0Iuf1S4 可以分割出文件的二進制內容
原生 node
使用原生的 node 寫一個文件上傳的 demo
const http = require("http"); const fs = require("fs"); const util = require("util"); const querystring = require("querystring"); //用http模塊創建一個http服務端 http .createServer(function(req, res) { if (req.url == "/upload" && req.method.toLowerCase() === "get") { //顯示一個用于文件上傳的form res.writeHead(200, { "content-type": "text/html" }); res.end( '<form action="/upload" enctype="multipart/form-data" method="post">' + '<input type="file" name="upload" multiple="multiple" />' + '<input type="submit" value="Upload" />' + "</form>" ); } else if (req.url == "/upload" && req.method.toLowerCase() === "post") { if (req.headers["content-type"].indexOf("multipart/form-data") !== -1) parseFile(req, res); } else { res.end("pelease upload img"); } }) .listen(3000); function parseFile(req, res) { req.setEncoding("binary"); let body = ""; // 文件數據 let fileName = ""; // 文件名 // 邊界字符串 ----WebKitFormBoundaryoMwe4OxVN0Iuf1S4 const boundary = req.headers["content-type"] .split("; ")[1] .replace("boundary=", ""); req.on("data", function(chunk) { body += chunk; }); req.on("end", function() { const file = querystring.parse(body, "\r\n", ":"); // 只處理圖片文件; if (file["Content-Type"].indexOf("image") !== -1) { //獲取文件名 var fileInfo = file["Content-Disposition"].split("; "); for (value in fileInfo) { if (fileInfo[value].indexOf("filename=") != -1) { fileName = fileInfo[value].substring(10, fileInfo[value].length - 1); if (fileName.indexOf("\\") != -1) { fileName = fileName.substring(fileName.lastIndexOf("\\") + 1); } console.log("文件名: " + fileName); } } // 獲取圖片類型(如:image/gif 或 image/png)) const entireData = body.toString(); const contentTypeRegex = /Content-Type: image\/.*/; contentType = file["Content-Type"].substring(1); //獲取文件二進制數據開始位置,即contentType的結尾 const upperBoundary = entireData.indexOf(contentType) + contentType.length; const shorterData = entireData.substring(upperBoundary); // 替換開始位置的空格 const binaryDataAlmost = shorterData .replace(/^\s\s*/, "") .replace(/\s\s*$/, ""); // 去除數據末尾的額外數據,即: "--"+ boundary + "--" const binaryData = binaryDataAlmost.substring( 0, binaryDataAlmost.indexOf("--" + boundary + "--") ); // console.log("binaryData", binaryData); const bufferData = new Buffer.from(binaryData, "binary"); console.log("bufferData", bufferData); // fs.writeFile(fileName, binaryData, "binary", function(err) { // res.end("sucess"); // }); fs.writeFile(fileName, bufferData, function(err) { res.end("sucess"); }); } else { res.end("reupload"); } }); }
通過 req.setEncoding("binary"); 拿到圖片的二進制數據。可以通過以下兩種方式處理二進制數據,寫入文件。
fs.writeFile(fileName, binaryData, "binary", function(err) { res.end("sucess"); });
fs.writeFile(fileName, bufferData, function(err) { res.end("sucess"); });
koa
在 koa 中使用 koa-body 可以通過 ctx.request.files 拿到上傳的 file 對象。下面是例子。
'use strict'; const Koa = require('koa'); const app = new Koa(); const router = require('koa-router')(); const koaBody = require('../index')({multipart:true}); router.post('/users', koaBody, (ctx) => { console.log(ctx.request.body); // => POST body ctx.body = JSON.stringify(ctx.request.body, null, 2); } ); router.get('/', (ctx) => { ctx.set('Content-Type', 'text/html'); ctx.body = ` <!doctype html> <html> <body> <form action="/" enctype="multipart/form-data" method="post"> <input type="text" name="username" placeholder="username"><br> <input type="text" name="title" placeholder="tile of film"><br> <input type="file" name="uploads" multiple="multiple"><br> <button type="submit">Upload</button> </body> </html>`; }); router.post('/', koaBody, (ctx) => { console.log('fields: ', ctx.request.body); // => {username: ""} - if empty console.log('files: ', ctx.request.files); /* => {uploads: [ { "size": 748831, "path": "/tmp/f7777b4269bf6e64518f96248537c0ab.png", "name": "some-image.png", "type": "image/png", "mtime": "2014-06-17T11:08:52.816Z" }, { "size": 379749, "path": "/tmp/83b8cf0524529482d2f8b5d0852f49bf.jpeg", "name": "nodejs_rulz.jpeg", "type": "image/jpeg", "mtime": "2014-06-17T11:08:52.830Z" } ]} */ ctx.body = JSON.stringify(ctx.request.body, null, 2); } ) app.use(router.routes()); const port = process.env.PORT || 3333; app.listen(port); console.log('Koa server with `koa-body` parser start listening to port %s', port); console.log('curl -i http://localhost:%s/users -d "user=admin"', port); console.log('curl -i http://localhost:%s/ -F "source=@/path/to/file.png"', port);
我們來看一下 koa-body 的實現
const forms = require('formidable'); function requestbody(opts) { opts = opts || {}; ... opts.multipart = 'multipart' in opts ? opts.multipart : false; opts.formidable = 'formidable' in opts ? opts.formidable : {}; ... // @todo: next major version, opts.strict support should be removed if (opts.strict && opts.parsedMethods) { throw new Error('Cannot use strict and parsedMethods options at the same time.') } if ('strict' in opts) { console.warn('DEPRECATED: opts.strict has been deprecated in favor of opts.parsedMethods.') if (opts.strict) { opts.parsedMethods = ['POST', 'PUT', 'PATCH'] } else { opts.parsedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE'] } } opts.parsedMethods = 'parsedMethods' in opts ? opts.parsedMethods : ['POST', 'PUT', 'PATCH'] opts.parsedMethods = opts.parsedMethods.map(function (method) { return method.toUpperCase() }) return function (ctx, next) { var bodyPromise; // only parse the body on specifically chosen methods if (opts.parsedMethods.includes(ctx.method.toUpperCase())) { try { if (opts.json && ctx.is(jsonTypes)) { bodyPromise = buddy.json(ctx, { encoding: opts.encoding, limit: opts.jsonLimit, strict: opts.jsonStrict, returnRawBody: opts.includeUnparsed }); } else if (opts.multipart && ctx.is('multipart')) { bodyPromise = formy(ctx, opts.formidable); } } catch (parsingError) { if (typeof opts.onError === 'function') { opts.onError(parsingError, ctx); } else { throw parsingError; } } } bodyPromise = bodyPromise || Promise.resolve({}); /** * Check if multipart handling is enabled and that this is a multipart request * * @param {Object} ctx * @param {Object} opts * @return {Boolean} true if request is multipart and being treated as so * @api private */ function isMultiPart(ctx, opts) { return opts.multipart && ctx.is('multipart'); } /** * Donable formidable * * @param {Stream} ctx * @param {Object} opts * @return {Promise} * @api private */ function formy(ctx, opts) { return new Promise(function (resolve, reject) { var fields = {}; var files = {}; var form = new forms.IncomingForm(opts); form.on('end', function () { return resolve({ fields: fields, files: files }); }).on('error', function (err) { return reject(err); }).on('field', function (field, value) { if (fields[field]) { if (Array.isArray(fields[field])) { fields[field].push(value); } else { fields[field] = [fields[field], value]; } } else { fields[field] = value; } }).on('file', function (field, file) { if (files[field]) { if (Array.isArray(files[field])) { files[field].push(file); } else { files[field] = [files[field], file]; } } else { files[field] = file; } }); if (opts.onFileBegin) { form.on('fileBegin', opts.onFileBegin); } form.parse(ctx.req); }); }
代碼中刪除了影響有關文件上傳的相關邏輯
首先 multipart 為 true 是開啟文件上傳的關鍵。
然后 formy
函數處理了 http 解析和保存的一系列過程,最終將 files 拋出進行統一處理。代碼中依賴了 formidable
這個庫,我們其實也可以直接使用這個庫對文件進行處理。(上面的原生 node upload 只是簡單地處理了一下)
opts.formidable 是 formidable 的 config 可以設置文件大小,保存的文件路徑等等。
eggjs
使用 eggjs 進行文件上傳需要現在配置文件中開啟
config.multipart = { mode: "file", fileSize: "600mb" };
然后通過 ctx.request.files[0]
就能取到文件信息。
文件上傳接口的轉發
一千個觀眾眼中有一千個哈姆雷特,通過以上知識點的梳理,我相信你也有了自己得想法。在這里說一下我是怎么處理的。 在 egg 中我使用了 request-promise 去做接口轉發,通過查看 api 和 ctx.request.files[0] 拿到的信息,我做了以下處理。
if (method === "POST") { options.body = request.body; options.json = true; if (url === uploadeUrl) { delete options.body; options.formData = { // Like <input type="text" name="name"> name: "file", // Like <input type="file" name="file"> file: { value: fs.createReadStream(ctx.request.files[0].filepath), options: { filename: ctx.request.files[0].filename, contentType: ctx.get("content-type") } } }; } } else { options.qs = query; }
以上就是怎么在node中對文件上傳接口進行轉發,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。