一. 包 (文件模块)
文件模块是由程序员基于Node环境编写并发布的代码, 通过由多个文件组成放在一个文件夹中, 所以也叫做包
它不同于Nodejs中的内置模块和自定义模块,包是由第三方个人或者团队开发出来的,免费给其他人使用
好处
- 包是基于内置模块封装出来的,提供了更高级,更方便的API,极大的提高了开发效率
- 包和内置模块的关系,类似jquery和浏览器内置的DOM之间的关系
1 npm包管理工具
npm(node package manager)是由美国的一家公司提供的包管理工具,用来管理包1) npm基本使用
npm在安装node时, 会被自动安装, 首先通过命令查看npm是否已经安装
在cmd命令行执行npm -v, 查看npm的版本信息安装包
npm install 包名称
示例
如果我们想安装jquery, 执行npm install jquery即可
删除包
npm uninstall 包名称
示例
2) 镜像与nrm
由于npm是国外的网站, 包的下载速度比较慢, 有时不稳定会导致安装出错. 为了解决这个问题, 我们可以使用国内镜像, 镜像源就是一个和npm官网一样的网站(像镜子一样), 只不过在国内, 这样更加稳定, 并且速度也很快.
nrm是一个镜像管理工具
执行命令
npm install nrm -g
- -g: 表示全局安装
在命令行执行
nrm ls
显示可用镜像
使用nrm use切换镜像 (如使用taobao镜像)
nrm use taobao
2 package规范
每个发布在npm上的包都遵循统一的规范, 这个规范也就被称为’package规范’. 以jquery为例
├─dist // 项目发布目录├─external│ └─sizzle│ └─dist└─src // 源代码└─package.json // 包配置文件└─README.md // 说明文件
其中, 最重要的是package.json文件
文件组成
node_modules用来存放所有已经安装到项目中的包,require导入第三方包的时候,就是从这个目录中查找并加载
package.json配置文件 用来记录node_modules目录下的每一个包的下载信息,例如包名,版本号,下载地址
src 就是源码存放的位置
dist 程序的入口文件夹
规范
1,包必须以单独的目录存在
2,包的顶级目录下必须包含 package.json这个包管理的配置文件
3,package.json配置文件必须包含name(名字) version(版本号) main(包的入口) 这三个属性
3 手写一个package
1) 创建一个文件夹
在node_modules下创建一个文件夹calc, 这个文件夹也就是一个package
2) 初始化
打开calc, 执行命令 npm init -y, 会在calc里生成package.json配置文件
其中, 最重要的是main, 用来指定: 其他人加载这个package时, 实际引入的文件是哪一个
npm init -y
- -y: 表示使用默认值初始化
3) 创建包的目录结构
1,创建src文件夹,存放源码<br /> 2,创建dist文件夹,用来发布目录<br /> 3,在dist文件夹下创建index.js文件,这个程序的入口文件<br /> 4,修改package.json配置文件中的main属性值,为'dist/index.js',这个才是包的入门文件
4) 编写代码
1,在src下创建一个add.js和sub.js,编写加减法的代码并用module.exports导出<br /> 2,在dist下的index.js中,接收引入的方法,并导出
src/add.js
function add (x, y) {return x + y}module.exports = add
src/sub.js
module.exports = function (x, y) {return x - y}
dist/index.js
const add = require('../src/add.js')const sub = require('../src/sub.js')module.exports = {add,sub}
5) 使用自定义的package
1,导入自定义的包 const calc = require('calc');<br /> 2,使用 console.log(calc.add(1,2));
const calc = require('calc')console.log(calc.add(1, 2))
4 导入规则(模块加载机制)
1) 以名称开头
示例
require('fs')require('calc')
- Node.js会假设它是核心模块
- 如果不是核心模块Node.js会去当前目录下的node_modules中查找
- 首先看是否有该名字的JS文件
- 再看是否有该名字的文件夹
- 查看该文件夹中的package.json中的main选项确定模块入口文件
- 如果没有package.json文件, 加载index.js
- 否则找不到报错
一般规则: 先内置模块—node-module找—该名字的js文件—该名字的文件夹main—该文件加下的index.js
2) 以路径开头
示例
require('./find')require('../add.js')
- 如果模块后缀省略,先找同名JS文件再找同名JS文件夹
- 如果找到了同名文件夹,找文件夹中的index.js
- 如果文件夹中没有index.js就会去当前文件夹中的package.json文件中查找main选项中的入口文件
- 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到
二. 服务器编程
Node最显著的特点就是将js扩展到了服务端, 使得可以使用js编写服务端程序.
0 概念
1) 服务器
提供服务的机器, 本质是一台电脑电脑, 跟普通的电脑相比, 服务器一般性能更好
负责存放和对外提供资源的电脑,叫做服务器
负责获取和消费资源的电脑,叫做客户端
- http服务器: 提供http服务的电脑
- 数据库服务器: 提供数据存储服务的电脑
网络通讯的三要素
ip: 设备在网络中的唯一标识
端口: 程序在设备中的唯一标识
协议: 规定了数据传输的格式
2) 服务
所谓服务, 就是运行在电脑上的一个应用程序.
在计算机网络中. 通过IP地址可以唯一的确定某一台电脑, 通过端口就可以唯一的确定这个电脑提供的服务
结论
1 URL地址
URL(Uniform Resource Locator), 统一资源定位符
在计算机网络中, 可以通过统一资源定位符(URL)请求对应的服务器资源(Resource)
组成:
Schema://host[:port]/path[?query-string]
- Schema: 使用的协议类型, 如http/https/ftp等
- host: 主机域名或IP
- port: 端口号(可选)
- path: 路径
-
2 Http协议
Http是一种网络协议(超文本传输协议), 规定了web服务器与浏览器之前的交互语言, 是一种一问一答协议 (请求—响应机制)
由浏览器发起请求(request)
- 由web服务器针对请求生成响应(response)
浏览器传给服务器什么样格式的数据,服务器才能解析
服务器传给浏览器什么样格式的数据,浏览器才能解析
1) Http请求消息(req对象操作)
概念
客户端发给服务器端的消息,告诉服务器,我当前浏览器的一些信息
HTTP请求由三部分组成, 分别是:
- 请求行
- 请求头
- 请求体
请求行
格式: 请求方式 请求的url 协议版本
Method Request-URL HTTP-Version CRLF
- Method: HTTP请求的类型, 如:GET/POST/PUT/DELETE
- Request-URL: HTTP请求的唯一标识符, 如: /test.hmtl
- HTTP-Version: HTTP协议版本, 如HTTP/1.1
- CRLF: 回车换行 CR(回车\n) LF(换行\r)
例子: GET /test.html HTTP/1.1 (CRLF)
请求行以”空格”分割, 除结尾的外CR和LF外, 不允许出现单独的CR或LF字符
请求头
请求头包含许多有关的前端环境和请求正文的有用信息.<br /> ** 格式: 一组一组的键值对**<br /> user-agent:浏览器的版本型号<br /> content-type:发给服务器的数据格式<br /> accept:浏览器能够接收什么类型的返回内容<br /> Accept-Language:用户期望获得的自然语言的优先顺序
请求体
请求体主要包含前端发送给后端的数据
对于GET请求, 一般不需要请求体, 因为GET参数直接体现在URL上
只有post的请求方式中才有请求体
2) Http响应消息(res对象操作)
概念
服务器端发给浏览器端的信息,告诉浏览器我发给你的消息的数据及特点
同样, HTTP响应也是由三部分组成, 分别是:
- 响应行
- 响应头
- 响应体
响应行
**格式: 协议版本 状态码 状态码描述**
HTTP-Version Status-Code Reason-Phrase CRLF
- HTTP-Version: HTTP协议版本, 如HTTP/1.1
- Status-Code: 响应状态码, 如200/401/500
- Reason-Phrase: 描述信息
- CRLF: 回车换行 CR(回车\n) LF(换行\r)
示例
HTTP/1.1 200 OK
状态码
- 1xx:指示信息—表示请求已接收,继续处理。
- 2xx:成功—表示请求已被成功接收、理解、接受。
- 3xx:重定向—要完成请求必须进行更进一步的操作。
- 4xx:客户端错误—请求有语法错误或请求无法实现。
- 5xx:服务器端错误—服务器未能实现合法的请求。
常见的状态码
- 200 OK:客户端请求成功。
- 400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
- 401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate一起使用。
- 403 Forbidden:服务器收到请求,但是拒绝提供服务。
- 404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
- 500 Internal Server Error:服务器发生不可预期的错误。
响应头
响应头是后端(服务器)返回给前端(客户端)的信息.<br /> content-type:告诉浏览器我发送数据的类型和编码<br /> content-length:发给浏览器内容的字节长度
响应体
响应体是后端(服务器)返回给前端(客户端)的数据.3) 常见请求方式
get方式
数据拼接在地址栏后面.相对的不安全,只能向客户端传递少量数据.经常用于查询某个商品等
post方法
数据保存在请求体当中,相对的安全.可以传递大量数据.比如文件的上传下载,表单数据的提交
restful风格中,不同请求方式间的作用
get:查询
post:新增
put:修改
delete:删除3 手写http服务程序
Node提供了Http核心模块, 方便我们快速构建一个http服务程序1) 最简单的http服务程序
示例
// 引入http核心模块const http = require('http')// 创建web服务器const server = http.createServer()// 处理请求server.on('request', (req, res) => {res.end('<h1>hello</h1>')})// 监听3000端口server.listen(3000)
演示
2) 处理中文字符
示例
如果响应里包含中文会怎样?
注意, 修改代码后要重启服务
res.end('<h1>这是一个web服务器</h1>')
演示

我们发现会出现乱码. 我们需要在响应头里添加编码格式
server.on('request', (req, res) => {// 设置响应头res.writeHead(200, {'content-type': 'text/html;charset=utf-8',})res.end('<h1>这是一个web服务器</h1>')})
演示

nodemon
每次修改代码都需要手动重启服务. 这并不友好, 而且容易忘记. 这里, 大家可以安装nodemon工具
npm install nodemon -g
然后, 使用nodemon来执行js文件, nodemon会监听文件的变化, 并且重新执行
3) 处理路由
分析URL
不论是get请求还是post请求, 作为服务端而言, 首先要知道请求的URL
在Node中, 可以通过url核心模块进行分析, 参考 官方文档
示例
// 导入url模块const url = require('url')// 通过url.parse方法, 返回url对象const str = 'http://localhost:3000/index'const obj = url.parse(str, true)console.dir(obj)
输出
Url {protocol: 'http:',slashes: true,auth: null,host: 'localhost:3000',port: '3000',hostname: 'localhost',hash: null,search: null,query: [Object: null prototype] {},pathname: '/index',path: '/index',href: 'http://localhost:3000/index'}
这里我们最关心的是
- pathname: 请求的路由
通过路由, 服务端可以区分具体的资源, 比如
- /和index: 首页
- list: 列表页
- detail: 详情页
示例
// 引入http核心模块const http = require('http')// 引入url核心模块const url = require('url')// 创建web服务器const server = http.createServer()// 处理请求server.on('request', (req, res) => {// 设置响应头res.writeHead('200', {'content-type': 'text/html;charset=utf-8',})// 分析路由const { pathname } = url.parse(req.url, true)if (pathname == '/' || pathname == '/index') {res.end('首页')} else if (pathname == '/list') {res.end('列表页')} else {res.writeHead('404')res.end('Not Found')}})// 监听3000端口server.listen(3000)console.log('server is running on localhost:3000')
4) 处理GET请求
对于同一个URL, 可以发起不同类型的请求, 处理请求, 主要是分析请求的参数
由于GET参数直接在URL中, 在处理URL时, 通过query就可以得到
示例
// 导入url模块const url = require('url')// 通过url.parse方法, 返回url对象const str = 'http://localhost:3000/index?username=xiaopang'const obj = url.parse(str, true)console.dir(obj)
输出
Url {protocol: 'http:',slashes: true,auth: null,host: 'localhost:3000',port: '3000',hostname: 'localhost',hash: null,search: '?username=xiaopang',query: [Object: null prototype] { username: 'xiaopang' },pathname: '/users/index',path: '/users/index?username=xiaopang',href: 'http://localhost:3000/users/index?username=xiaopang'}
处理get请求
// 引入http核心模块const http = require('http')// 引入url核心模块const url = require('url')// 创建web服务器const server = http.createServer()// 处理请求server.on('request', (req, res) => {// 设置响应头res.writeHead('200', {'content-type': 'text/html;charset=utf-8',})// 分析路由const { query, pathname } = url.parse(req.url, true)if (pathname == '/' || pathname == '/index') {// 处理get请求if (req.method == 'GET') {// 打印在后端控制台console.log(query)// 返回给浏览器res.end(query.username)}} else if (pathname == '/list') {res.end('列表页')} else {res.writeHead('404')res.end('Not Found')}})// 监听3000端口server.listen(3000)console.log('server is running on localhost:3000')
5) 处理POST请求
对于POST请求, 由于参数在数据报文中, 只有等数据传输完成才可以进行处理.
主要使用request提供的data和end事件来处理
示例
// 引入http核心模块const http = require('http')// 引入url核心模块const url = require('url')// 创建web服务器const server = http.createServer()// 处理请求server.on('request', (req, res) => {// 设置响应头res.writeHead('200', {'content-type': 'text/html;charset=utf-8',})// 分析路由const { query, pathname } = url.parse(req.url, true)if (pathname == '/' || pathname == '/index') {// 处理get请求if (req.method == 'GET') {// 显示页面console.log(query)} else if (req.method == 'POST') {let post_data = ''// post数据传递req.on('data', (data) => (post_data += data))// post数据传输完成req.on('end', () => {console.log(post_data)res.end(post_data)})}} else if (pathname == '/list') {res.end('列表页')} else {res.writeHead('404')res.end('Not Found')}})// 监听3000端口server.listen(3000)console.log('server is running on localhost:3000')
监听request的两个事件
像html, js, css, 图片这些数据都属于静态资源
如果我们直接在end方法里通过字符串返回html, 显然不够友好.
最好是能以文件的形式保存, 通过读取文件的方式返回
示例
// 引入http核心模块const http = require('http')// 引入url核心模块const url = require('url')// 引入path核心模块const path = require('path')// 引入fs核心模块const fs = require('fs')// 创建web服务器const server = http.createServer()// 读取静态资源function resolveStatic(file) {// 将网络路由转换成服务器端真实路径const realPath = path.join(__dirname, 'public/' + file)// 同步读取文件return fs.readFileSync(realPath)}// 处理请求server.on('request', (req, res) => {// 设置响应头res.writeHead('200', {'content-type': 'text/html;charset=utf-8',})// 分析路由const { pathname } = url.parse(req.url, true)if (pathname == '/' || pathname == '/index') {const html = resolveStatic('index.html')res.end(html)} else if (pathname == '/list') {res.end('列表页')} else {res.writeHead('404')res.end('Not Found')}})// 监听3000端口server.listen(3000)console.log('server is running on localhost:3000')
