Node.js第三篇:服务端编程

Node.js服务端编程

目录

第一章:服务端基础概念

1.1-网站的组成

网站应用程序主要分为两大部分:客户端和服务器端。

  • 客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。
  • 服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。

![]()

1.2-Node网站服务器

能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应。

![]()

1.3-IP地址

互联网中设备的唯一标识。

IP是Internet Protocol Address的简写,代表互联网协议地址。

![]()

1.4-域名

由于IP地址难于记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址。

<http://www.baidu.com&gt; => <http://39.156.66.14/&gt;

虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为ip才能访问到指定的网站服务器。

1.5-端口

端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同的服务。

![]()

1.6-URL

统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL。

URL的组成:传输协议://服务器IP或域名:端口/资源所在位置标识

<https://www.baidu.com/index.html&gt;

1.7-开发过程中客户端和服务器端

在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。

![]()

本机域名:localhost

本地IP :127.0.0.1

第二章:创建web服务器

我们要开发后端程序(服务端),必须要有web服务器,才能提供服务。

那Node.js创建服务器的步骤如下:

// 1. 导入Node.js系统模块http模块
const http = require(&quot;http&quot;);
// 2. 创建web服务器对象
const app = http.createServer();
// 3. 给服务器注册request事件,监听用户请求,并作出响应
app.on(&quot;request&quot;, (req, res) =&gt; { 
  // 响应客户端
  res.end(&quot;Hello!&quot;)
});
// 4. 监听端口4000
app.listen(4000);

在本地浏览器总:输入地址→localhost:4000

第三章:HTTP协议

3.1-概念

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。
![]()

3.2-报文

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

![]()

3.3-请求报文

请求方式

  • get
  • post

请求地址

const http = require(&quot;http&quot;);
const app = http.createServer();
app.on(&quot;request&quot;, (req, res) =&gt; { 
  // 获取请求报文
  console.log(req.headers);
  // 获取请求的路径
  console.log(req.url);
  // 获取请求的方式
  console.log(req.method);
});
app.listen(4000);

3.4-响应报文

> HTTP状态码

200 请求成功

404 请求的资源没有被找到

500 服务器端错误

400 客户端请求有语法错误

> 内容类型

text/html

text/css

application/javascript

image/jpeg

application/json

> 代码演示

const http = require(&quot;http&quot;);
const app = http.createServer();
app.on(&quot;request&quot;, (req, res) =&gt; { 
  // 设置响应报文(状态码和响应的内容类型与编码)
  res.writeHead(200, {
    &quot;Content-Type&quot;:&quot;text/html;charset=utf-8&quot;
  })
  res.end(&quot;&lt;h1&gt;你好!&lt;/h1&gt;&quot;);
});
app.listen(4000);

第四章:HTTP请求与响应处理

4.1-请求参数

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

![]()

https://www.baidu.com/s?wd=程序员是猴子吗?

4.2-GET请求参数

参数被放置在浏览器地址栏中,例如:<http://localhost:3000/?id=1&amp;type=2&gt;

参数获取需要借助系统模块url,url模块用来处理url地址

const http = require(&quot;http&quot;);
// 【导入url模块】
const url = require(&quot;url&quot;);
const app = http.createServer();
app.on(&quot;request&quot;, (req, res) =&gt; { 
  // 【将req.url转换为对象,并解构出 [请求路径] 和 [对象格式的请求参数]】
  let {query,pathname} = url.parse(req.url, true);
  // 请求路径
  console.log(pathname);
  // 请求参数对象
  console.log(query);
});
app.listen(3000);

4.3-POST请求参数

参数被放置在请求体中进行传输

获取POST参数需要使用data事件end事件

使用querystring系统模块将参数转换为对象格式

表单:

  &lt;form action=&quot;http://localhost:4000&quot; method=&quot;POST&quot;&gt;
    用户名:&lt;input type=&quot;text&quot; name=&quot;username&quot;&gt;
    密码:&lt;input type=&quot;password&quot; name=&quot;pwd&quot;&gt;
    &lt;input type=&quot;submit&quot;&gt;

  &lt;/form&gt;

node服务

const http = require(&quot;http&quot;);
// 导入querystring模块
const querystring = require(&quot;querystring&quot;);
const app = http.createServer();
app.on(&quot;request&quot;, (req, res) =&gt; {
  // 定义变量,接收客户端post请求参数
  let paramsStr = &quot;&quot;;
  // 注册data事件接收参数数据
  req.on(&quot;data&quot;, (chunk) =&gt; { paramsStr += chunk });
  // 注册end事件,接收完毕后的处理
  req.on(&quot;end&quot;, () =&gt; {
    let paramsObj = querystring.parse(paramsStr);
    console.log(paramsObj);
  });

});
app.listen(4000);

4.4-路由

<http://localhost:3000/index&gt;

<http://localhost:3000/login&gt;

路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。

![]()

const http = require(&quot;http&quot;);
const url = require(&quot;url&quot;);
const app = http.createServer();
app.on(&quot;request&quot;, (req, res) =&gt; { 
  // 获取请求的路径
  let { pathname } = url.parse(req.url, true);
  // 设置响应头
  res.writeHead(200, { &quot;content-Type&quot;: &quot;text/html;charset=utf-8&quot; });
  // 处理路由
  if (pathname == &quot;/index.html&quot; || pathname == &quot;/&quot;) {
    res.end(&quot;&lt;h1&gt;首页&lt;/h1&gt;&quot;);
  } else if (pathname == &quot;/list.html&quot;) {
    res.end(&quot;&lt;h1&gt;列表页面&lt;/h1&gt;&quot;);
  } else {
    res.writeHead(404, { &quot;content-Type&quot;: &quot;text/html;charset=utf-8&quot; });
    res.end(&quot;&lt;h1&gt;页面不存在&lt;/h1&gt;&quot;);
  }

});
app.listen(4000);

4.5-静态资源

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

<https://m.360buyimg.com/babel/jfs/t1/36002/35/9106/3311/5cd4f1bdE06ff07ed/9570fdd46ecd3a76.png&gt;

![]()

const http = require(&quot;http&quot;);
const url = require(&quot;url&quot;);
const path = require(&quot;path&quot;);
const fs = require(&quot;fs&quot;);
// 导入第三方模块mime
const mime = require(&quot;mime&quot;);

const app = http.createServer();
app.on(&quot;request&quot;, (req, res) =&gt; { 
  // 获取请求的路径
  let { pathname } = url.parse(req.url, true);
  // 拼接服务器上文件的物理路径
  let realPath = path.join(__dirname, &quot;public&quot;, pathname);
  // 获取请求的资源类型
  let type = mime.getType(realPath);
  // 读取服务器本地文件
  fs.readFile(realPath, (err, data) =&gt; { 
    if (err) {
      res.writeHead(404,{&quot;Content-type&quot;:type+&quot;;charset=utf-8&quot;});
      res.end(&quot;访问资源不存在&quot;);
      return;
    }
    res.writeHead(200);
    res.end(data);
  });

});
app.listen(4000);

4.6-动态资源

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

https://www.baidu.com/s?wd=美女

![]()

https://www.baidu.com/s?wd=帅哥

![]()

4.7-客户端请求途径

  • GET方式
    • 浏览器地址栏
    • link标签的href属性
    • script标签的old-src属性
    • img标签的old-src属性
    • Form表单提交
  • POST方式
    • Form表单提交

第五章:Node异步编程

5.1-同步API, 异步API

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

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

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

console.log('before');
setTimeout(
   () =&gt; { console.log('last');
}, 2000);
console.log('after');

区别1:同步API, 异步API的区别(返回值):

// 【同步】
function sum (n1, n2) { 
   return n1 + n2;
} 
const result = sum (10, 20);
// 结果:30

// 【异步】
function getMsg () { 
  setTimeout(function () { 
     return { msg: 'Hello Node.js' }
  }, 2000);
}
const msg = getMsg ();
// 结果:undefind

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

  • 同步API从上到下依次执行,前面代码会阻塞后面代码的执行
  • 异步API不会等待API执行完成后再向下执行代码

5.2-回调函数

回调函数的基本定义和使用

// getData函数定义
 function getData (callback) {}
  // getData函数调用
 getData (() =&gt; {});

使用回调函数获取异步API执行结果

function getMsg (callback) {
    setTimeout(function () {
        callback ({ msg: 'Hello Node.js' })
    }, 2000);
}
getMsg (function (msg) { 
    console.log(msg);  // 结果:Hello Node.js
});

5.3-异步代码执行顺序分析

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

![]()

5.4-Node.js中的异步API

需求:依次读取A文件、B文件、C文件

const fs = require(&quot;fs&quot;);
// 读取文件1
fs.readFile(&quot;./public/a.txt&quot;, &quot;utf-8&quot;, function (err, data) {
  console.log(data);
  fs.readFile(&quot;./public/b.txt&quot;, &quot;utf-8&quot;, function (err, data) {
    console.log(data);
    fs.readFile(&quot;./public/c.txt&quot;, &quot;utf-8&quot;, function (err, data) {
      console.log(data);
    })
  })
});

问题:回调嵌套太多,代码不易于维护

解决方案:Promise对象

5.5-Promise对象

> 基本使用

let p1 = new Promise((resolve, reject) =&gt; { 
    // resolve表示执行成功后的函数
    // reject表示异常时的函数
  fs.readFile(&quot;./public/1.txt&quot;, &quot;utf-8&quot;, function (err, data) {
    if (err) {
      reject(err);
    } else {
      resolve(data);
    }
  });
});

p1
  // 执行成功后的操作
  .then((data) =&gt; { console.log(data) })
  // 发生异常时的操作
  .catch((err) =&gt; { console.log(err) });

> 完成需求

const fs = require(&quot;fs&quot;);
function p1() {
  return new Promise((resolve, reject) =&gt; {
    fs.readFile(&quot;./public/a.txt&quot;, &quot;utf-8&quot;, (err, data) =&gt; { 
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}
function p2() {
  return new Promise((resolve, reject) =&gt; {
    fs.readFile(&quot;./public/b.txt&quot;, &quot;utf-8&quot;, (err, data) =&gt; { 
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}
function p3() {
  return new Promise((resolve, reject) =&gt; {
    fs.readFile(&quot;./public/c.txt&quot;, &quot;utf-8&quot;, (err, data) =&gt; { 
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

p1()
  .then((data) =&gt; {
    console.log(data);
    return p2();
  })
  .then((data) =&gt; {
    console.log(data);
    return p3();
  })
  .then((data) =&gt; {
    console.log(data);
  })

5.6-异步函数

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

> 关键字:async

语法:async function fn () {}const fn = async () =&gt; {};

  1. 普通函数定义前加async关键字 普通函数变成异步函数
  2. 异步函数默认返回promise对象
  3. 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
  4. 在异步函数内部使用throw关键字抛出程序异常
  5. 调用异步函数再链式调用then方法获取异步函数执行结果
  6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
let fn1 = async () =&gt; { 
  // throw &quot;异常&quot;;
  return &quot;success&quot;;
};

fn1()
  .then((data) =&gt; { console.log(data) })
  .catch((err)=&gt;{console.log(err)})

> 关键字:await

  1. await关键字只能出现在异步函数中
  2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
  3. await关键字可是暂停异步函数向下执行 直到promise返回结果

> 完成需求

const fs = require(&quot;fs&quot;);
// 用于改造现有的异步函数api,让其返回promise对象,从而支持异步函数的语法
const promisify = require(&quot;util&quot;).promisify;
let readFile = promisify(fs.readFile);
async function run() {
  let f1 = await readFile(&quot;./public/1.txt&quot;, &quot;utf-8&quot;);
  let f2 = await readFile(&quot;./public/2.txt&quot;, &quot;utf-8&quot;);
  let f3 = await readFile(&quot;./public/3.txt&quot;, &quot;utf-8&quot;);
  console.log(f1);
  console.log(f2);
  console.log(f3);
}
run();

第六章:扩展【可选-了解】

6.1-CommonJS

6.1.1-什么是CommonJs

JavaScript 是一个强大面向对象语言,它有很多快速高效的解释器。然而, JavaScript

标准定义的 API 是为了构建基于浏览器的应用程序。并没有制定一个用于更广泛的应用程序

的标准库。CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准库的缺陷。它的

终极目标就是:提供一个类似 Python,Ruby 和 Java 语言的标准库,而不只是让 JavaScript 停

留在小脚本程序的阶段。用 CommonJS API 编写出的应用,不仅可以利用 JavaScript 开发客

户端应用,而且还可以编写以下应用。

  • 服务器端 JavaScript 应用程序。(nodejs)
  • 命令行工具。
  • 桌面图形界面应用程序。

CommonJS 就是模块化的标准,nodejs 就是 CommonJS(模块化)的实现

6.1.2-CommonJS规范

  • require('模块名称') 导入模块
  • module.exports 或 exports 导出模块内容

6.2-npm相关

6.2.1-npm介绍

npm 是世界上最大的开放源代码的生态系统。我们可以通过 npm 下载各种各样的包,

这些源代码(包)我们可以在 <https://www.npmjs.com&gt; 找到。

npm 是随同 NodeJS 一起安装的包管理工具,能解决 NodeJS 代码部署上的很多问题,

常见的使用场景有以下几种:

  • 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用。(silly-datetime)
  • 允许用户从 NPM 服务器下载并安装别人编写的命令行程序(工具)到本地使用。 (supervisor)
  • 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用

6.2.2-npm命令

> npm -v 查看 npm 版本

npm -v

> 使用 npm 命令安装模块

npm install Module Name

如安装 jq 模块:npm install jquery

指定版本安装 npm install jquery@1.8.0

> npm uninstall moudleName 卸载模块

如卸载jq模块:npm uninstall jquery

> npm list 查看当前目录下已安装的 node 包

npm list

> npm info moduleName 查看模块系信息

如:npm info jquery 查看 jquery 的版本

6.3-fs模块常用API

6.3.1-fs.stat 获取文件信息

const fs = require('fs')
// 检测一个本地文件
fs.stat('./data/hello.js', (err, stats) =&gt; {
  if (err) {
    // 若读取本地文件有异常,则打印异常信息
    console.log(err.message)
  } else {
    console.log(stats)
    console.log('是否是文件:' + stats.isFile())
    console.log('是否是文件夹:' + stats.isDirectory())
    // 单位是b-位
    console.log('大小:' + Math.ceil(stats.size/1024) + 'kb')
  }
})

输出结果:

是否是文件:true
是否是文件夹:false
大小:85kb

6.3.2-fs.mkdir 创建目录

// 在data目录下创建一个logs目录
fs.mkdir('./data/logs', (error) =&gt; {
  if (error) {
    console.log(error)
  } else {
    console.log('成功创建目录:logs')
  }
})

6.3.3-fs.writeFile 写入文件

fs.writeFile('./data/logs/hello.log', '您好 ~ \n', (error) =&gt; {
  if (error) {
    console.log(error)
  } else {
    console.log('成功写入文件')
  }
})

6.3.4-fs.appendFile 追加文件

fs.appendFile('./data/logs/hello.log', '您好 ~ \n', (error) =&gt; {
  if (error) {
    console.log(error)
  } else {
    console.log('追加写入文件')
  }
})

6.3.5-fs.readFile读取文件

fs.readFile('./data/logs/hello.log', 'utf8', (error, data) =&gt; {
  if (error) {
    console.log(error)
  } else {
    console.log(data)
  }
})

6.3.6-读取目录

fs.readdir('./data', (error, files) =&gt; {
  if (error) {
    console.log(error)
  } else {
    console.log(files)
    // [ 'hello.js', 'logs', 'test01.js' ]
  }
})

6.3.7-重命名

const fs = require('fs')
// fs.rename(oldName,newName,callback)
fs.rename('./data/test01.js', './data/test02.js', (err)=&gt;{
  if (err) {
    return err.message
  } else {
    console.log('重命名成功!')
  }
})

6.3.8-删除文件

const fs = require('fs')
fs.unlink('./data/logs/log.js', (err) =&gt; {
  if (err) return err
  console.log('删除成功!')
})

6.3.9-从文件流中读取数据

const fs = require('fs')
const fileStream = fs.createReadStream('./data/hello.js')
var count = 0
var str = ''
fileStream.on('data', (chunk) =&gt; {
  console.log(chunk.toString())
  str += chunk
  count++
})
fileStream.on('end', (chunk) =&gt; {
  cosnole.log('读取结束')
  console.log(str)
})
fileStream.on('error', (err) =&gt; {
  console.log(err)
})

6.3.10-把数据写入流中

var fs = require(&quot;fs&quot;);
var data = '我是从数据库获取的数据,我要保存起来';
//创建一个可以写入的流,写入到文件output.txt中
var writerStream = fs.createWriteStream('output.txt');
//使用utf8编码写入数据
writerStream.write(data, 'UTF8');
//标记文件末尾
writerStream.end();
// 处理流事件-- &gt; finish事件 finish-所有数据已被写入到底层系统时触发。
writerStream.on('finish', function () { 
  console.log('写入完成')
})
writerStream.on('error', function (err) { 
  console.log(err)
})

6.3.11-管道流

管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

![]()

如上面的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。

以下实例我们通过读取一个文件内容并将内容写入到另外一个文件中。

var fs = require(&quot;fs&quot;);
// 创建一个可读流
var readerStream = fs.createReadStream('./data/hello.js');
// 创建一个可写流
var writerStream = fs.createWriteStream('output.txt');
// 管道读写操作 
// 读取input.txt文件内容,并将内容写入到output.txt文件中
readerStream.pipe(writerStream);
console.log(&quot;程序执行完毕&quot;);

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。