本篇文章,讲述了一个很简单的上传图片(/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入门