Node.js服务器开发(下)

Posted 橘猫吃不胖~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js服务器开发(下)相关的知识,希望对你有一定的参考价值。

Node.js服务器开发(下)

1 HTTP请求与响应处理

1.1 GET请求参数

GET参数:浏览器地址栏或页面的超链接,被放置在浏览器地址栏中进行传输,示例URL地址如下。

在该URL地址中,地址和请求参数之间用“?”分隔,参数和参数之间用“&”分隔。

服务器端接收Get请求的参数,可以调用url.parse方法对req.url进行解析,从query中获取请求参数,示例代码如下:

// 引用系统模块
const http = require("http");
const url = require("url");

// 创建服务器对象
const app = http.createServer();

// 给服务器注册监听事件
app.on("request", (req, res) => 
    console.log(url.parse(req.url, true));
)

// 监听3000端口
app.listen(3000);
console.log("服务器已启动,监听3000端口,请访问localhost:3000");

在浏览器中输入localhost:3000?name=zhangsan&age=12,终端会显示以下信息:

从上面的结果可以看出Url对象中的query已经帮助我们解析出来了参数,因此我们可以从query中获取请求参数,这样就方便处理了。

示例代码:在服务器端获取请求参数

// 引用系统模块
const http = require("http");
const url = require("url");

// 创建服务器对象
const app = http.createServer();

// 给服务器注册监听事件
app.on("request", (req, res) => 
    // 使用解构赋值将query获取出来
    let query = url.parse(req.url, true);
    console.log("请求参数1:", query["name"]); //两种获取方法
    console.log("请求参数2:", query.age);
    console.log(query);
    res.end();
)

// 监听3000端口
app.listen(3000);
console.log("服务器已启动,监听3000端口,请访问localhost:3000");

启动服务器,在浏览器中输入:http://localhost:3000/?name=zhangsan&age=12,终端输出以下结果:

上面示例中undefined是在获取浏览器中的图标。

1.2 POST请求参数

Post请求参数被放在post请求体中进行传输。在客户端对post请求参数进行封装(方法之一放在form表单中),然后在服务器端获取post参数:导入querystring模块,将post请求体解析成对象格式

示例:从客户端发送POST请求参数到服务器端,编写form.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post请求参数</title>
</head>
<body>
<form action="http://localhost:3000" method="post">
    <label>用户名:
        <input type="text" name="username"/>
    </label>
    <br/><br/>
    <label>密码:
        <input type="password" name="pwd"/>
    </label>
    <br/><br/>
    <button type="submit">提交</button>
</form>
</body>
</html>


紧接着编写post.js文件:

const http = require("http");
const querystring = require("querystring");

const postServer = http.createServer();

postServer.on("request", (req, res) => 
    let postStr = "";
    // 当接收到数据时
    req.on("data", (params) => 
        postStr += params; // 接收一个参数拼接一个参数
        // 当请求结束之后对postStr进行解析
        req.on("end", () => 
            let postParams = querystring.parse(postStr);
            console.log(postParams);
            console.log("姓名:", postParams.username);
            console.log("密码:", postParams["pwd"]);
        )
    )
    res.end("OK");
)

postServer.listen(3000);

启动服务器,在刚刚编写的Html页面中输入用户名:zhangsan,密码:123,点击提交,页面进行了跳转:

同时在终端也将信息解析出来了:

1.3 路由

用户在浏览器地址栏中输入不同的请求地址,服务器端会为客户端响应不同的内容。

例如,客户端访问“http://localhost:3000/index”这个请求地址,服务器端要为客户端响应首页的内容,这是由网站应用中的路由实现的。

路由是指客户端请求地址与服务器端程序代码的对应关系。

示例:使用路由根据不同的客户端请求地址响应不同的内容

// 引用系统模块
const http = require("http");
const url = require("url");

// 创建服务器对象
const app = http.createServer();

// 为服务器对象添加请求事件
app.on("request", (req, res) => 
    // 获取客户端请求方式
    const method = req.method;
    // 获取请求地址
    const pathname = url.parse(req.url, true).pathname;
    // 设置响应头信息
    res.writeHead(200, 
        "content-type": "text/html;charset=utf8"
    )
    // 实现路由功能
    if (method == "GET") 
        if (pathname == "/" || pathname == "/index") 
            res.end("欢迎来到首页");
         else 
            res.end("页面不存在");
        
     else if (method == "POST") 
        res.end("您正在访问POST请求页面");
    
)

// 监听300端口
app.listen(3000);

启动服务器,在浏览器中输入localhost:3000,显示“欢迎来到首页”。

或者输入localhost:3000/index,也是一样的结果:

1.4 静态资源访问

静态资源服务是指客户端向服务器端请求的资源,服务器端不需要处理,可以直接响应给客户端的资源。静态资源主要包括CSS、javascript、image文件,以及HTML文件。

动态资源指的是相同的请求地址可以传递不同的请求参数,得到不同的响应资源,这种资源称为动态资源。

静态资源是存放在本地的,只能自己可以访问到,其他人不能访问。如果希望服务器端的静态资源能够被用户访问到,这就需要实现静态资源访问功能

在服务器端创建一个专门的目录,存放静态资源。当客户端请求某个静态资源文件时,服务器端将这个静态资源响应给客户端。

示例:如何实现静态资源的访问

第一步:在当前目录下面创建public目录,用于存放静态文件

第二步:创建app.js文件,文件代码如下

// 引用系统模块http
const http = require("http");

// 创建服务器对象
const app = http.createServer();

const url = require("url"); // 引用url地址模块
const path = require("path"); // 引用系统模块path,用于读取文件前拼接路径
const fs = require("fs"); // 引用系统模块fs,用于读取静态资源
const mime = require("mime"); // 引用第三方模块

// 为服务器对象添加请求事件
app.on("request", (req, res) => 
    // 处理请求,获取用户请求路径
    let pathname = url.parse(req.url, true).pathname;
    // 判断当前路径是否为:/,如果是路径换成/default.html,如果不是不做处理
    pathname = pathname == "/" ? "/default.html" : pathname;
    // 将用户的请求路径转换为实际的服务器硬盘路径
    // __dirname:当前正在执行的代码的目录名(路径)
    // path.join():将多个参数字符串合并成一个路径字符串
    let realPath = path.join(__dirname, "public" + pathname);
    // 利用mime模块根据路径返回资源的类型
    let type = mime.getType(realPath);
    // 读取文件
    // readFile("filename",fn):用于异步读取数据
    fs.readFile(realPath, ((err, data) => 
        if (err) throw err; // 文件读取失败抛异常
        res.writeHead(200, 
            "content-type": type
        )
        res.end(data);
    ))
)

// 监听3000端口
app.listen(3000);

第三步:在public文件夹下创建default.html,代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    用户名:<input type="text" name="username">
    密码:<input type="text" name="pwd">
    <button type="submit">提交</button>
</form>
</body>
</html>

启动服务器,打开浏览器输入:localhost:3000,浏览器就会跳出来以下页面:

2 Node.js异步编程

2.1 同步异步API的概念

同步API指只有当前API执行完成后,才能继续执行下一个API。

举例:到餐馆点餐时,一个指定的服务员被分配给你服务,当你点完餐时,服务员将订单送到厨房并在厨房等待厨师制作菜肴,当厨师将菜肴烹饪完成后,服务员将菜肴送到你的面前,服务完成,此时这个服务员才能服务另外的客人。也就是说,一个服务员同时只能服务于一个客人。

同步API的执行方式:代码从上到下一行一行执行,下一行的代码必须等待上一行代码执行完成以后才能执行。

console.log("before");
console.log("after");

异步API指当前API的执行不会阻塞后续代码的执行。

举例:到餐馆点餐时,在点餐后服务员将你的订单送到厨房,此时服务员没有在厨房等待厨师烹饪菜肴,而是去服务了其他客人,当厨师将你的菜肴烹饪好以后,服务员再将菜肴送到你的面前。也就是说,一个服务员同时可以服务多个客人。

异步API的执行方式:代码在执行过程中某行代码需要耗时,代码的执行不会等待耗时操作完成以后再去执行下一行代码,而是不等待直接向后执行。异步代码的执行结果需要通过回调函数的方式处理。

console.log("before");
setTimeout(() => 
    console.log("last");
, 2000);
console.log("after");

2.2 获取异步API的返回值

示例:Node.js中使用回调函数获取异步API的返回值

// getMsg函数定义
function getMsg(callback) 
    setTimeout(function () 
        // 调用callback
        callback(
            msg: "橘猫吃不胖"
        )
    , 2000);


// getMsg函数调用
getMsg(function (data) 
    // 在回调函数中获取异步API执行的结果
    console.log(data);
)

运行该代码,在终端2秒后,输出对象:

代码说明:
1、在上面的代码中,首先为getMsg()函数传入了一个参数,该参数同样是一个函数。
2、接收到参数后,getMsg()函数开始执行,这时callback就是function(data)console.log(data);
3、在getMsg()内部设置了一个定时器,定时器过2秒就会执行函数function()callback(msg:"橘猫吃不胖")
4、2秒后执行定时器内定义的函数,之前分析了callback就是函数function(data)console.log(data);,因此msg:"橘猫吃不胖"就是callback的参数,也就是说,该对象就是data
5、最后执行该函数,输出data,结果输出了对象:msg:“橘猫吃不胖”

2.3 异步编程中回调地狱的问题

异步API不能通过返回值的方式获取执行结果,异步API也不会阻塞后续代码的执行。

回调地狱:如果异步API后面代码的执行依赖当前异步API的执行结果,这就需要把代码写在回调函数中。一旦回调函数的嵌套层次过多,就会导致代码不易维护。

回调地狱案例:依次读取A文件、B文件、C文件。通常的做法是:使用fs.readFile()方法读取A文件,A文件读取完成之后,在读取A文件的回调函数中去读取B文件;B文件读取完成之后,在读取B文件的回调函数中去读取C文件。

示例:创建3个文件,分别是A.txt、B.txt、C.txt,文件内容分别是A、B、C,创建callbackhell.js文件,代码如下

const fs = require("fs");

fs.readFile("./A.txt", "utf8", (err, data1) => 
    console.log(data1);
    fs.readFile("./B.txt", "utf8", (err1, data2) => 
        console.log(data2);
        fs.readFile("./C.txt", "utf8", (err2, data3) => 
            console.log(data3);
        )
    )
)

运行该程序,终端依次输出A、B、C

2.4 利用Promise解决回调地狱

Promise本身是一个构造函数,如果要使用Promise解决回调地狱的问题,需要使用new关键字创建Promise构造函数的实例对象。

// 定义Promise
let promise = new Promise(((resolve, reject) => 
));
// 定义resove和reject参数函数
promise.then(result => console.log(result))
    .catch(error => console.log(error));

示例:使用Promise解决callbackhell.js文件中回调地狱的问题

const fs = require("fs"); // 调用文件系统(fs模块)

function p1() 
    return new Promise((resolve, reject) => 
        fs.readFile("./A.txt", "utf8", (err, data) => resolve(data));
    )


function p2() 
    return new Promise((resolve, reject) => 
        fs.readFile("./B.txt", "utf8", (err, data) => resolve(data));
    )


function p3() 
    return new Promise((resolve, reject) => 
        fs.readFile("./C.txt", "utf8", (err, data) => resolve(data));
    )


p1().then((r1) => 
    console.log(r1);
    // 使用return返回p2()函数的Promise对象,会在下一个then()中拿到这个Promise对象的结果
    return p2();
).then((r2) => 
    // 获取上一个Promise对象的结果
    console.log(r2);
    return p3();
).then((r3) => 
    // 获取上一个Promise对象的结果
    console.log(r3);
);

2.5 异步函数

异步函数实际上是在Promise对象的基础上进行了封装,它把一些看起来比较繁琐的代码封装起来,然后开放一些关键字供开发者来使用。

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

async关键字:异步函数需要在function前面加上async关键字。

async function fn() 
    throw "错误"; // throw代替reject()方法
    return 123; // return代替resolve()方法


fn().then(function (data) 
    // 获取到return的值
    console.log(data);
).catch(function (err) 
    // 捕获throw抛出的错误
    console.log(err);
)

await关键字:可以暂停异步函数的执行,等待Promise对象返回结果再向下执行函数。await关键字只能出现在异步函数中,await后面只能写Promise对象,不能写其他类型API。

示例:使用await关键字实现3个异步函数的有序执行

async function p1() 
    return "p1";

async function p2() 
    return "p2";

async function p3() 
    return "p3";


async function run() 
    let r1 = await p1();
    发明炒菜机器,不仅吸引了顾客,他还申请了17项专利

CodeForces - 589A(二分+贪心)

Nodejs 饭店

Node.js服务器开发(下)

[BZOJ 1823][JSOI2010]满汉全席(2-SAT)

Bzoj : 1823: [JSOI2010]满汉全席