1. 创建web服务器

// 创建web服务器

// 用于创建网站服务器的模块
// 引用系统模块
const http = require('http');

// 创建web服务器
// app对象就是网站服务器对象
const app = http.createServer();

// 当客户端发送请求的时候
app.on('request', (req, res) => {
    // 响应
    res.end('<h1>hi ,user</h1>');
});
// 监听3000端口
app.listen(3000); // 这里的端口号可以随便写,不一定是3000
console.log('服务器已启动,监听3000端口,请访问localhost:3000');
// 在powerShall中输入 nodemon app.js启动服务器,再在浏览器中输入localhost:3000

2. HTTP协议

2.1 http协议的概念

超文本传输协议(http)规定了如何从网站服务器传输超文本到本地浏览器,它是基于客户端服务器架构工作,是客户端(用户)和服务器(网站)请求和应答的标准。

网络服务器架构 网络服务器构建_网络服务器架构

2.2 报文

在HTTP请求和响应的过程中传递的数据块就是报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式

网络服务器架构 网络服务器构建_客户端_02

网络服务器架构 网络服务器构建_服务器_03

2.3 请求报文

获取请求方式:req.method

  1. 请求方式(Request Method)
  • GET 请求数据
    在浏览器中输入网址使用的是get请求
  • 网络服务器架构 网络服务器构建_网络服务器架构_04

  • POST 发送数据
    如何发送post请求:使用表单方式
<body>
    <!-- 
        method: 指定当前表单提交的方式
        action:指定当前表单提交的地址
     -->
    <form method="post" action="http://localhost:3000">
        <input type="submit">
    </form>
</body>

网络服务器架构 网络服务器构建_网络服务器架构_05

  1. 请求地址(Request URL)
// 获取请求地址
    // req.url

2.4 响应报文

  1. HTTP状态
  • 200 请求成功
  • 404 请求的资源没有被找到
  • 500 服务器端错误
  • 400 客户端请求有语法错误

3. HTTP请求与响应处理

3.1 请求参数

客户端向服务器发送请求时,有时需要携带一些卡用户信息,客户信息需要通过请求参数的形式传递服务器端,比如登录操作。

3.2 GET请求参数

  • 参数被放置在浏览器地址栏中,例如:http://localhost:3000/index?name=zhangshan&&age=19
  • 参数获取需要借助系统模块url, url 模块用来处理url地址
// 用于创建网站服务器的模块
// 引用系统模块
const http = require('http');
// 用于处理url地址
const url = require('url');

// 创建web服务器
// app对象就是网站服务器对象
const app = http.createServer();

// 当客户端发送请求的时候
app.on('request', (req, res) => {
    console.log(req.url);
    // url.parse的参数:1)要解析的url地址 2)将查询参数解析成对象形式
    let { query, pathname } = url.parse(req.url, true);
    console.log(query.name);
    console.log(query.age);

    if (pathname == '/index' || pathname == '/') {
        return res.end('<h2>欢迎来到首页</h2>');
    } else if (pathname == '/list') {
        return res.end('welcome to listpage');
    } else {
        return res.end('no find');
    }
});
// 监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000');
// 在powerShall中输入 nodemon app.js启动服务器,再在浏览器中输入localhost:3000

3.3 POST请求参数

  • 参数被放置在请求体中进行传输
  • 获取POST参数需要使用data事件和end事件
  • 使用querystring系统模块将参数转换为对象格式
// 导入系统模块querystring 用于将HTTP参数转换为对象格式
const querystring = require('querystring');
app.on('request', (req, res) => {
    let postData = '';
    // 监听参数传输事件
    // post参数是通过事件的方式接受的
    // data: 但请求参数传递的时候触发data事件
    // end:当参数传递完成的时候触发end事件

    req.on('data', (chunk) => postData += chunk;);
    // 监听参数传输完毕事件
    req.on('end', () => { 
        console.log(querystring.parse(postData)); 
    }); 
});

3.4 路由

http://localhost:3000/index 首页
http://localhost:3000/login 登录页面
路由是指客户端请求地址与服务器端程序代码的对应关系。简单来说,就是请求什么响应什么

网络服务器架构 网络服务器构建_客户端_06

// 1. 引入系统模块http
// 2. 创建网站服务器
// 3. 为网站服务器对象添加请求事件
// 4. 实现路由功能
// 1) 获取客户端的请求方式
// 2) 获取客户端的请求地址
const http = require('http');
const url = require('url')
const app = http.createServer();

app.on('request', (req, res) => {

    // 获取请求方式
    const method = req.method.toLowerCase();

    // 获取请求地址
    const pathname = url.parse(req.url).pathname;

    res.writeHead(200, {
        'content-type': 'text/html;charset=utf8'
    })

    if (method == 'get') {
        if (pathname == '/' || pathname == '/index') {
            return res.end('欢迎来到首页')
        } else if (pathname == '/list') {
            return res.end('欢迎来到列表页');
        } else {
            return res.end('您访问的页面不存在');
        }
    } else if (method == 'post') {
        if (pathname == '/' || pathname == '/index') {
            return res.end('欢迎来到首页')
        } else if (pathname == '/list') {
            return res.end('欢迎来到列表页');
        } else {
            return res.end('您访问的页面不存在');
        }
    }
});

app.listen(3000);
console.log('服务器启动成功');

3.5 静态资源

服务器端不需要处理,可以直接响应客户端的资源就是静太资源,例如CSS、JavaScript、image文件。

3.6 动态资源

相同的请求地址不同的响应资源,这种资源就是动态资源。

例如:

http:www.itcast.cn/article?id=1

http:www.itcast.cn/article?id=2

网络服务器架构 网络服务器构建_API_07

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const app = http.createServer();


app.on('request', (req, res) => {
    // 获取用户的请求路劲
    let pathname = url.parse(req.url).pathname;

    pathname = pathname == '/' ? 'default.html' : pathname;

    // res.writeHead(200, {
    //     'content-type': 'text/html;charset=utf8'
    // })

    // 将用户的请求路劲转换为实际的服务器硬盘路劲
    let realPath = path.join(__dirname, 'public' + pathname);

    let type = mime.getType(realPath);

    // 读取文件
    // 如果文件读取成功 error为空,result 就是文件的内容
    // 如果文件读取失败 error里面存储的是失败信息, result为空
    fs.readFile(realPath, (error, result) => {
        // 文件读取失败
        if (error != null) {
            res.writeHead(404, {
                'content-type': 'text/html;charset=utf8'
            })
            res.end('文件读取失败');
            return;
        }
        res.writeHead(200, {
            'content-type': type
        });
        res.end(result);
    })
});

app.listen(3000);
console.log('服务器启动成功');

4. Node.js异步编程

4.1 同步API,异步API

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

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

console.log('before');

setTimeout(function() {
    console.log('last');
}, 2000);

console.log('after');

// 输出结果为:before after last

4.2 同步API,异步API的区别(获取返回值)

同步API可以返回值中拿到API执行的结果,但是异步API是不可以的

// 同步
function sum(n1, n2) {
    return n1 + n2;
};
console.log(sum(2, 3));

// 返回值为:5

// 异步
function getMsg() {
    setTimeout(function() {
        return {
            msg: 'hello node.js'
        }
    }, 2000);
    // 由于在执行getMsg时,遇到定时器所以直接跳到定时器的后面,添加return undefined执行,将其值返回给msg
    // return undefined
}
const msg = getMsg();

console.log(msg);

// 返回值为undefined

4.3 回调函数

自己定义函数让别人去调用

// 异步
function getMsg(callback) {
    setTimeout(function() {
        callback({
            msg: 'hello node.js'
        });
    }, 2000);

}
getMsg(function(date) {
    console.log(date);
});

4.4 同步API,异步API的区别(代码执行顺序)

同步API从上到下依次执行,前面代码会阻塞后面代码的执行

for (var i = 0; i < 10000; i++) {
    console.log(i);
}
console.log('执行后面的代码');

异步API不会等待API执行完成后再向下执行代码

console.log('代码开始执行');
setTimeout(() => {
    console.log('2秒后执行的代码');
}, 2000);
setTimeout(() => {
    console.log('0秒后执行的代码');
}, 0);
console.log('代码结束执行');
// 代码的执行结果
// 代码开始执行
// 代码结束执行
// 0秒后执行的代码
// 2秒后执行的代码

4.5 代码执行顺序分析

网络服务器架构 网络服务器构建_服务器_08

console.log('代码开始执行');
setTimeout(() => {
    console.log('2秒后执行的代码');
}, 2000); 
setTimeout(() => {
    console.log('"0秒"后执行的代码');
}, 0);
console.log('代码结束执行');

4.6 Node.js中的异步API

const fs = require('fs');
fs.readFile('1.txt', 'utf-8', (err, result1) => {
    console.log(result1);
    fs.readFile('2.txt', 'utf-8', (err, result2) => {
        console.log(result2);
        fs.readFile('3.txt', 'utf-8', (err, result3) => {
            console.log(result3);
        })
    })
})

如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题怎么解决呢?

const fs = require('fs');

let promise = new Promise((resolve, reject) => {
    fs.readFile('100.txt', 'utf8', (err, result) => {
        if (err != null) {
            return reject(err);
        } else {
            return resolve(result);
        }
    });
});

promise.then((result) => {
        console.log(result);
    })
    .catch((err) => {
        console.log(err);
    })

4.7 Promise

Promise出现的目的是解决Node.js异步编程中回调地狱的问题。

网络服务器架构 网络服务器构建_网络服务器架构_09

4.8 异步函数

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

const fn = async () => {}
async function fn() {}

async关键字

  1. 普通函数定义前家async关键字 普通函数变成异步函数
  2. 异步函数默认返回promise对象
  3. 在一部函数内部使用return关键字进行结果返回,结果会包裹的promise对象中return关键字代替了resolve方法
  4. 在一部函数内部使用throw关键字抛出程序异常
  5. 调用异步函数再链式调用then方法获取函数执行结果
  6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息

await

  1. await关键字只能出现在异步函数中
  2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
  3. await关键字可是暂停异步函数向下执行 直到promise返回结果
// 1. 在普通函数定义的前面加上async关键字 普通函数就变成了异步函数
// 2. 异步函数默认的返回值使promise对象
// 3. 在异步函数内保护使用throw关键字进行错误抛出



// 4. await关键字
// 它只能出现在异步函数中
// awite后面跟promise对象 它可以暂停异步函数的执行,等待promise对象返回后再向下执行函数

// async function fn() {
//     throw '发生了一些错误';
//     return 123;
// }
// // console.log(fn());
// fn().then(function(data) {
//         console.log(data);
//     })
//     .catch(function(err) {
//         console.log(err);
//     })

async function p1() {
    return 'p1';
}
async function p2() {
    return 'p2';
}
async function p3() {
    return 'p3';
}
async function run() {
    let r1 = await p1();
    let r2 = await p2();
    let r3 = await p3();
    console.log(r1);
    console.log(r2);
    console.log(r3);
}
run();
const fs = require('fs');

const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
async function run() {
    let r1 = await readFile('1.txt', 'utf8');
    let r2 = await readFile('2.txt', 'utf8');
    let r3 = await readFile('3.txt', 'utf8');
    console.log(r1);
    console.log(r2);
    console.log(r3);
}
run();

4.9 Node.js全局对象global

在浏览器中全局对象是window, 在Node中全局对象是global。

Node中全局对象有以下方法, 在任何地方使用, global可以省略。

  1. console.log(); 在控制台中输出
  2. setTimeout() 设置超时定时器
  3. clearTimeout() 清除超时时定时器
  4. setInterval() 设置间歇定时器
  5. clearInterval() 清除间歇定时器