不使用框架创建简单的Node.js web应用
需求: 创建一个可以上传图片的web应用。用户可以浏览应用,有一个文件上传的表单。选择图片上传,上传完成之后可以预览图片。上传的图片信息需要入库(mysql)。
一个简单的http服务
const http = require('http');
// 创建一个服务
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
});
// 服务错误监听
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
// 当前服务监听的端口
server.listen(8888);
console.log("Server has started.");
涉及到的api:
http.createServer
res.writeHead
res.end
当我们在编写node服务的时候,如果服务器有异常,那node服务就直接挂掉了。那如何保证我们的node服务不会关闭,并且会自动重启呢?
或者是我们修改了代码,如何实现修改的代码直接生效而不用重新手动重启node服务呢?
npm install -g nodemon
在生产环境我一般使用pm2来管理node服务。
自定义模块
刚才我们定义了一个简单的http服务。其中http是一个内置的模块。那其实我们的服务都会有一个入口文件,在这里我们定义为index.js。那我们如何像引用http内置模块一样,在index.js里使用server.js呢?
exports 与 module.exports 的区别:
exports是module.exports的简写。如果修改了exports的引用,也就是重新给exports赋值,则exports只是在当前文件级作用域内可用,并且exports的修改不会影响到module.exports的值。
server.js
const http = require('http');
function start() {
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
});
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.listen(8888);
console.log("Server has started.");
}
// 通过给exports添加【属性值】的方式导出方法
// 或者通过给module.exports添加属性值
exports.start = start;
index.js
const server = require('./server');
server.start();
node index.js
路由处理
我们知道,访问一个web网站会有不同的页面或者会调用不同的接口,那这些就对应这不同的请求路径,同时这些请求还会对应不同的请求方法(GET, POST等)。那node如何针对这些不同的请求路径去匹配对应的处理函数呢?
为了处理http请求的参数,我们需要获取到http请求的request,从中获取到请求方式以及请求路径。在这里会依赖 url内置模块。
首先建立routes文件夹存放路由处理
routes/index.js
module.exports = (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
}
routes/upload.js
module.exports = (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('upload file');
}
新建route.js文件处理路由
function route(handle, pathname, req, res) {
if (typeof handle[pathname] === 'function') {
handle[pathname](req, res);
} else {
console.log("No request handler found for " + pathname);
res.end('404 Not found');
}
}
exports.route = route;
修改server.js
const http = require('http');
const url = require("url");
const routes = {};
function use(path, routeHandler) {
routes[path] = routeHandler;
}
function start(route) {
function handleRequest(req, res) {
const pathname = url.parse(req.url).pathname;
route(routes, pathname, req, res)
}
const server = http.createServer(handleRequest);
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.listen(8888);
console.log("Server has started.");
}
module.exports = {
start,
use
};
修改index.js
const server = require('./server');
const index = require('./routes/index');
const upload = require('./routes/upload');
const router = require('./route');
server.use('/', index);
server.use('/upload', upload);
server.start(router.route);
处理POST请求
我们显示一个文本区(textarea)供用户输入内容,然后通过POST请求提交给服务器。最后,服务器接受到请求,通过处理程序将输入的内容展示到浏览器中。
给request注册监听事件
request.addListener("data", function(chunk) {
// called when a new chunk of data was received
});
request.addListener("end", function() {
// called when all chunks of data have been received
});
querystring登场,解析上传数据
修改server.js里的handleRequest方法
function handleRequest(req, res) {
const pathname = url.parse(req.url).pathname;
let postData = '';
// 设置编码
req.setEncoding("utf8");
req.addListener("data", function(postDataChunk) {
postData += postDataChunk;
console.log("Received POST data chunk '"+
postDataChunk + "'.");
});
req.addListener("end", function() {
route(routes, pathname, req, res, postData);
});
}
route.js多加一个参数postData
function route(handle, pathname, req, res, postData) {
if (typeof handle[pathname] === 'function') {
handle[pathname](req, res, postData);
} else {
console.log("No request handler found for " + pathname);
res.end('404 Not found');
}
}
exports.route = route;
index.js
const exec = require("child_process").exec;
module.exports = (req, res, postData) => {
// 可以使用node模板
const body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html; '+
'charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" method="post">'+
'<textarea name="text" rows="20" cols="60"></textarea>'+
'<input type="submit" value="Submit text" />'+
'</form>'+
'</body>'+
'</html>';
res.end(body);
}
upload.js 修改
const querystring = require("querystring");
module.exports = (req, res, postData) => {
const content = querystring.parse(postData).text;
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8'});
res.end(content || 'Empty');
}
处理文件上传
npm install formidable
server.js修改
function handleRequest(req, res) {
const pathname = url.parse(req.url).pathname;
route(routes, pathname, req, res);
}
routes/index.js
const exec = require("child_process").exec;
module.exports = (req, res) => {
// 可以使用node模板
const body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" '+
'content="text/html; charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" enctype="multipart/form-data" '+
'method="post">'+
'<input type="file" name="upload">'+
'<input type="submit" value="Upload file" />'+
'</form>'+
'</body>'+
'</html>';
res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
res.end(body);
}
showFile.js获取磁盘图片信息
const fs = require('fs');
const path = require('path');
function show(req, res) {
const rootPath = path.resolve();
fs.readFile(`${rootPath}/tmp/test.png`, "binary", function(error, file) {
if(error) {
res.writeHead(500, {"Content-Type": "text/plain"});
res.write(error + "\n");
res.end();
} else {
res.writeHead(200, {"Content-Type": "image/png"});
res.write(file, "binary");
res.end();
}
});
}
module.exports = show;
routes/uoload.js
const fs = require('fs');
const formidable = require("formidable");
const path = require('path');
module.exports = (req, res) => {
const form = new formidable.IncomingForm();
const rootPath = path.resolve();
form.parse(req, function(error, fields, files) {
console.log("parsing done");
fs.renameSync(files.upload.path, `${rootPath}/tmp/test.png`);
res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
res.write("received image:<br/>");
res.write("<img src='/show' />");
res.end();
});
}
同时在index.js中添加相应的路由。
处理数据存储
本地安装mysql服务
安装mysql客户端/使用命令行
npm install mysql
CREATE SCHEMA `nodestart` ;
CREATE TABLE `nodestart`.`file` (
`id` INT NOT NULL AUTO_INCREMENT,
`filename` VARCHAR(300) NULL,
`path` VARCHAR(500) NULL,
`size` VARCHAR(45) NULL,
`type` VARCHAR(45) NULL,
`uploadtime` VARCHAR(45) NULL,
PRIMARY KEY (`id`));
新建db.js文件
const mysql = require('mysql');
const connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'nodestart'
});
function query(sql, params) {
return new Promise((resolve, reject) => {
connection.connect();
connection.query(sql, params, function (error, results, fields) {
if (error) reject(error);
console.log('The solution is: ', results);
resolve(fields);
});
connection.end();
});
}
exports.query = query;
修改routes/upload.js
const fs = require('fs');
const formidable = require("formidable");
const path = require('path');
const db = require('../db');
module.exports = (req, res) => {
const form = new formidable.IncomingForm();
const rootPath = path.resolve();
form.parse(req, function(error, fields, files) {
console.log("parsing done");
fs.renameSync(files.upload.path, `${rootPath}/tmp/test.png`);
const fileObj = {
size: files.upload.size,
path: `${rootPath}/tmp/test.png`,
filename: files.upload.name,
type: files.upload.type,
uploadtime: Date.now()
}
db.query('insert into nodestart.file set ?', fileObj).then((res) => {
console.log(res)
}).catch((err) => {
console.log(err);
})
res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
res.write("received image:<br/>");
res.write("<img src='/show' />");
res.end();
});
}
待续: