本篇文章,講述了一個很簡單的上傳圖片(/start)到本地服務器,然后路由跳轉到/upload.
寫這個程序的目的是為了幫助理解HTTP的一些基本概念及node對于http api的實現以及程序的設計模式.
IP: 計算機之間的通信
TCP: 應用程序之間的通信
HTTP: 基于TCP實現的應用層協議,設計之初是為了提供一種HTML頁面的發布和接收的方法,就是傳輸一些超文本(HyperText)的規則.
// 注:一些用到的基礎,會在對應的代碼前說明。下面先簡單的介紹一下功能
當在瀏覽器中輸入 http://localhost:8888/start 時,顯示如下內容(有點丑,沒寫CSS…將就看吧):
選擇文件:
點擊Upload file
要解決如下幾個問題:
1.服務器監聽localhost,端口8888下,路徑為/start的url
2.服務器處理該請求,并返回內容(通過http)
3.返回的內容對選擇文件和Upload File做出反應
在此之前,需要做如下的準備: 根據依賴注入的思想,我們需要一個index.js文件來處理服務器和路由之間的關系.
index.js: 負責啟動服務器,并向服務器傳入路由和事件處理程序(事件處理程序可以參考《JavaScript高級程序設》P345)
// index.js
var server = require('./server'); // 服務器,監聽瀏覽器發送的http請求
var route = require('./route'); // 路由,對不同路徑進行不同的處理
var requestHandlers = require('./requestHandlers'); // 事件處理程序// 在index這一層,可以將requestHandlers做一些簡單的處理
// 將事件處理程序對應到handle中,路徑作為屬性
var handle = {};
handle["/"] = requestHandlers.start; // 如果值輸入start就調用start的事件處理器
handle["/start"] = requestHandlers.start;
handle["/uoload"] = requestHandlers.uoload;
handle["/show"] = requestHandlers.show; // 調用啟動服務器..
server.start(route, handle);
server服務器: 監聽端口8888(會根據瀏覽器的請求HTTP生成一個請求路徑),將請求路徑(request.url)、求報文(resquest)、響應報文(response)、以及事件處理程序交給路由層
// server.js
var http = require('http');function start(route, handle) {// 服務器的事件處理程序 onRequestfunction onRequest(request , response) {var pathname = request.url;// 因為會多請求一個/favicon.ico(具體原因百度) , 故過濾掉它if (pathname === '/favicon.ico' ) {// 過濾掉...} else { // 其他路由// 將請求路徑交給路由層route(handle, pathname, response, request);}}// 創建服務器http.createServer(onRequest).listen(8888);
}// 到處start方法,供其他js使用
exports.start = start;// 注:在JS中函數可以作為參數,可以作為返回值,還可以作為變量賦值.具體可以參考函數式編程
route層: 根據服務器傳入的檢查事件處理程序中是否含有該路徑,如果有則交給請求處理層,否則返回404響應報文
// route.js
function route(handle, pathname, response, request) {if( typeof handle[path] === 'function') {handle[pathname](response, request);} else {response.writeHead(404,{'Content-Type' : "text/plain" })response.write('404 Not Found'); // 可以自定義html的404頁面返回response.end();}
}
exports.route = route;
請求處理層(requestHandlers): 對不同路勁的具體處理方式:start、upload、show。(后續可以根據需求拓展)
先分開來說明具體需求和實現,最后匯總
1.start: 顯示一個上傳的input框,和一個提交的按鈕
// 注:上傳的框引入了一個外部模塊formidable 具體了解formidable
// start(這是講解,后面匯總)
// start 會顯示一個html頁面,可以使用fs來寫一個讀取Html頁面的函數getHTML
function getHTML(url) {let promise = new Promise(resolve, reject) {fs.readFile(url, (err, data) {if (!err) {resolve(data);} else {reject(err)}})return promise
}// 可以使用上面定義函數寫start函數
function start(request, response) {let url = "./start.html" // 最后會給出let html = getHTML(url);html.then((data) => {response.writeHead(200,{'Content-Type' : 'text/plain'});response.write(data); // 可以接收buffer , stringresponse.end();}, (err) => {console.log(err);})
}
2.upload: 用于處理start頁面傳入的圖片,將圖片放入/temp文件夾下.重新命名為test.png,并將圖片作為http響應報文的body部分傳遞給瀏覽器.瀏覽器根據img的src 訪問找到圖片顯示出來.
function upload(request , response) {var form = new formidable.IncomingForm();// 這里涉及到一個cross-device問題.在結尾的參考文獻中會給出form.uploadDir = "temp";form.parse(request, (err, fields, files) => {fs.renameSync(files.upload.path, "/temp/test.png");response.writeHead(200,{ 'Content-Type' : 'text/html'});response.write("received image:<br /> ");response.write("<img src='/show' />");response.end();})
}
3.show: 用于將本地的/temp/test.png顯示在瀏覽器上.觸發條件是<img src=’/show’ />.即觸發了(/show路由,但是在請求url中還是localhost:8888/upload)
function show (request, response) {let url = "./temp/test.png";fs.readFile(url, 'binary', (err, file) => {if(err ) {response.writeHead(500, {'Content-Type' : 'text/plain'});response.write(err + '\n' );response.end();} else {response.writeHead(200, {'Content-Type' : 'image/jpg' }); // 告訴瀏覽器是個圖片類型response.write(file, 'binary');response.end();}})
}
1+2+3 = requestHandlers.js:
// requestHandlers.js (這樣寫的可維護性,拓展性更高)
var fs = require('fs'),formidable = require('formidable');// 根據url獲取本地的html(buffer)
function getHTML(url) {let promise = new Promise((resolve, reject) => {fs.readFile(url, (err, data) => {if (!err) {resolve(data)} else {reject(data)}})})return promise;
}function start(response) {let url = './start.html';let html = getHTML(url);html.then((body) => {response.writeHead(200, { 'Content-Type': 'text/html' });response.write(body);response.end();}, (err) => {console.log(err);})
}function upload(response, request) {var form = new formidable.IncomingForm();form.uploadDir = 'temp';form.parse(request, (err, fields, files) => {fs.renameSync(files.upload.path, "./temp/test.png");response.writeHead(200, { "Content-Type": "text/html" });response.write("received image:<br />");response.write("<img src='/show' />");response.end();})
}function show(response) {let url = "./temp/test.png";fs.readFile(url, "binary", (err, file) => {if (err) {response.writeHead(500, { "Content-Type": "text/plain" });response.write(err + '\n');response.end();} else {response.writeHead(200, { "Content-Type": "image/jpg" });response.write(file, "binary");response.end();}})
}exports.start = start;
exports.upload = upload;
exports.show = show;
start.html
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html" charset="utf-8">
</head><body><!-- 使用post方法將表單中的元素提交到相對路徑下的upload中 --><form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="upload"><input type="submit" value="Upload file" /></form>
</body></html>
參考1: fs.renameSync報錯的問題
參考2: Node入門