共计 7107 个字符,预计需要花费 18 分钟才能阅读完成。
不使用框架创建简单的 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();}); | |
} |
待续:
如何使用 Node 创建一个代理服务器
Node 的适用场景