相关笔记
学习内容
学习笔记
1. 以非阻塞操作进行请求响应
直接借用原标题,这部分我觉得属于NodeJs的特点。毕竟,其以异步非阻塞而闻名,虽然到现在为止,我还不太了解到底是什么意思,最直接的感受就是,在此之前,我一直以为非阻塞属于nodejs必然的,并没有想到,需要自己去实现,不去通过回调函数实现,nodejs一样会阻塞。
[Node入门] 现在我们采用如下这种新的实现方式:相对采用将内容传递给服务器的方式,我们这次采用将服务器“传递”给内容的方式。 从实践角度来说,就是将response对象(从服务器的回调函数onRequest()获取)通过请求路由传递给请求处理程序。 随后,处理程序就可以采用该对象上的函数来对请求作出响应。
简单来说,将response作为参数从服务器模块(server.js)传递给路由模块(router.js),再进入请求处理模块(requestHandlers.js)生成对应的response。
如下代码:
/* [index.js] 主文件 */
var server = require('./server.js');
var router = require('./router.js');
var requestHandlers = require('./requestHandlers.js');
var handles = {};
handles['/'] = requestHandlers.start;
handles['/start'] = requestHandlers.start;
handles['/upload'] = requestHandlers.upload;
handles['/sleep'] = requestHandlers.sleep;
server.start(router.route, handles);
/* [server.js] 服务器模块 */
const http = require('http'); // 使用require指令载入http模块
const url = require('url'); // 载入url模块
const hostname = '127.0.0.1'; // 创建主机名
const port = 1337; // 创建端口
function start(route, handles) {
http.createServer((request, response) => {
var pathname = url.parse(request.url).pathname;
console.log(`Request for ${pathname} received.`);
route(handles, pathname, response);
}).listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
}
exports.start = start;
/* [router.js] 路由模块 */
function route(handle, pathname, response) {
console.log(`About to route a request for ${pathname}`);
// 判断是否存在路径名,不存在则返回404错误
if (typeof handle[pathname] === 'function') {
handle[pathname](response);
} else {
console.log(`No request handler found for ${pathname}`);
response.writeHead(404, {'Context-Type' : 'text/plain'});
response.end('404 NOT FOUND');
}
}
exports.route = route;
/* [requestHandlers.js] 请求处理模块 */
// 导入child_process模块的exec方法,使用该方法尝试非阻塞操作
const exec = require('child_process').exec;
// 非阻塞操作
function start(response) {
console.log('Request handler "start" was called!');
// 异步非阻塞操作
exec('find /',
// 提供十秒的延迟
{ timeout: 10000, maxBuffer: 20000 * 1024 },
// 异步非阻塞的核心,回调函数
(error, stdout, stderr) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end(stdout);
});
}
function upload(response) {
console.log('Request handler "upload" was called!');
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello Upload!');
}
// 阻塞操作
function sleep(response) {
var sleepTime = 10000, // 休眠时间
startTime = new Date().getTime();
// 休眠sleepTime毫秒,将会阻塞服务器10秒
while (new Date().getTime() < startTime + sleepTime);
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end(`Hello sleep ${sleepTime / 1000} seconds!`);
}
exports.start = start;
exports.upload = upload;
exports.sleep = sleep;
解释:在requestHandlers.js中,start()与sleep()方法的对比,可以通过进行对照试验了解。
1. 先开http://127.0.0.1:1337/start,后开http://127.0.0.1:1337/upload;
2. 先开http://127.0.0.1:1337/sleep, 后开http://127.0.0.1:1337/upload。
第一组中,start网页需要等待十秒,而upload网页能够立即得到响应;
第二组中,sleep网页同样需要等待十秒,同时阻塞了upload网页的响应。
2. 处理POST请求
实现这样一个需求:我们显示一个文本区(textarea)供用户输入内容,然后通过POST请求提交给服务器。最后,服务器接受到请求,通过处理程序将输入的内容展示到浏览器中。
/* [index.js] 主文件 */
const server = require('./server.js'); // 导入服务器模块
const router = require('./router.js'); // 导入路由模块
const requestHandlers = require('./requestHandlers.js'); // 导入请求处理模块
const handle = {}; // 创建请求对象
handle['/'] = requestHandlers.start;
handle['/start'] = requestHandlers.start;
handle['/upload'] = requestHandlers.upload;
server.start(router.route, handle);
服务器模块:与上一节对比,多了一项对于返回数据的处理,这是因为
[Node入门] 为了使整个过程非阻塞,Node.js会将POST数据拆分成很多小的数据块,然后通过触发特定的事件,将这些小数据块传递给回调函数。这里的特定的事件有data事件(表示新的小数据块到达了)以及end事件(表示所有的数据都已经接收完毕)。
/* [server.js] 服务器模块 */
const http = require('http'); // 使用require指令载入http模块
const url = require('url'); // 载入url模块
const hostname = '127.0.0.1'; // 创建主机名
const port = 1337; // 创建端口
function start(route, handle) {
http.createServer((request, response) => {
var postData = '';
// 获取请求中的url路径
var pathname = url.parse(request.url).pathname;
console.log(`Request for ${pathname} received.`);
// 设置接受的数据编码格式为UTF-8
request.setEncoding('utf8');
// 监听请求的‘data’事件
// 因为接受的数据可能会很大,为了使整个过程非阻塞,Node.js会将POST数据拆分成很多小的数据块
request.on('data', postDataChunk => postData += postDataChunk);
// 监听请求的‘end’事件,确保它只会当所有数据接受完毕后才触发
request.on('end', () => route(handle, pathname, response, postData));
}).listen(port, hostname, () => { // 监听主机,端口
console.log(`Server running at http://${hostname}:${port}/`);
});
}
exports.start = start;
请求处理模块: /start 返回了一个包含文本区的html,并在点击上传后,将数据显示到 /upload。
/* [requestHandlers.js] 请求处理模块 */
const querystring = require('querystring');
function start(response, postData) {
console.log('Request handler "start" was called!');
var body = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Node入门-图片上传</title>
</head>
<body>
<form action="/upload" method="post">
<textarea name="text" rows="20" cols="60"></textarea>
<input type="submit" value="上传"></input>
</form>
</body>
</html>
`;
response.writeHead(200, { 'Content-Type' : 'text/html'});
response.end(body);
}
function upload(response, postData) {
console.log('Request handler "upload" was called!');
response.writeHead(200, {
'Content-Type': 'text/html;charset=utf-8'
});
response.end(`您刚刚发送的内容为:${querystring.parse(postData).text}`);
}
exports.start = start;
exports.upload = upload;
/* [router.js] 路由模块 */
function route(handle, pathname, response, postData) {
console.log(`About to route a request for ${pathname}`);
// 判断是否存在路径名,不存在则返回404错误
if (typeof handle[pathname] === 'function') {
handle[pathname](response, postData);
} else {
console.log(`No request handler found for ${pathname}`);
response.writeHead(404, {'Context-Type' : 'text/plain'});
response.end('404 NOT FOUND');
}
}
exports.route = route;
注意:这里有两点需要注意:
1. request的编码格式要设置成”utf8”;
2. 因为post请求是通过URL,因此需要对传入的postData进行URL解码,使用querystring模块的parse()方法(NodeAPI)。