HTTP事务
- 一个HTTP事务由一条请求报文(从客户端发往服务器的)和一条响应报文(从服务器发回客户端的)组成
- 无连接:每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接。HTTP1.1支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。
- 无状态:客户端发送请求后,服务器不存储关于该客户端的任何状态信息
HTTP方法
- 每个HTTP请求报文都包含一个HTTP方法,告诉服务器要执行什么动作
- 常见的HTTP方法
- GET(SELECT):从服务器取出资源(一项或多项)
- POST(CREATE):在服务器新建一个资源
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性 补丁)
- DELETE(DELETE):从服务器删除资源
- HEAD:获取资源的元数据
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的
HTTP状态码
- 每条HTTP响应报文返回时都会携带一个状态码。状态码是一个三位数字的代码,告知客户端请求是否成功,或者是否需要采取其他动作
- 200 OK - [GET]:服务器成功返回用户请求的数据
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)
- 403 Forbidden - [*]:表示拥护得到授权(与401错误相对),但是访问是被禁止的
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功
HTTP报文
- 类型
- 请求报文
- 响应报文
- 包括三个部分
- 起始行,报文第一行,纯文本
- 首部,包括多个key-value形式字段,纯文本,首部以空行结束
- 主体,可选,可以包括任意二进制数据
- HTTP请求报文
- ⚠️一个使用GET方式的请求报文中不能包含实体内容,只有使用POST、PUT和DELETE等方式的请求消息中才可以包含实体内容
- HTTP响应报文
- 响应报文的实体内容就是网页文件的内容,也就是在浏览器中使用查看源文件的方式所看到的内容
- 例子hwServer.js
REST ( Representional State Transfer具象狀態傳輸)
- REST这个词,是Roy Thomas Fileding博士在他2000年的博士论文中提出来的,此外他是HTTP协议(1.0版和1.1版)的主要设计者
- REST从资源的角度来提供网络服务,资源由URL确定,而客户端的应用通过URL来获取资源的表征。获得这些表征致使这些应用程序转变了其状态。随着不断获取资源的表征,客户端应用不断地在转变着其状态
- REST式的Web Service
- 使用URL代表一种资源
- 用HTTP方法GET、POST、DELETE、PUT、PATCH等来传递对资源的具体操作
- 使用HTTP状态码作为操作结果返回值
Node http
- Node http模块包含对HTTP处理的封装,用于创建支持HTTP服务器,HTTP客户端,是Node最核心的功能模块(还有https模块)
- 为了支持各种各种可能的HTTP应用,Node.js的HTTP API是非常底层的
- Node http模块不仅能用于开发web应用,甚至提供一个web应用服务器,不再需要Tomcat、Apache、IIS等应用服务器,有利于节省资源、提高效率
- HTTP模块主要包括
- 类
- Server,Node作为http服务器
- IncomingMessage,收到的报文(作为http服务器端时收到的客户端请求,作为http客户端对收到的服务器响应)
- ServerResponse,作为http服务器端时发送的响应
- ClientRequest,Node作为http客户端时发送的请求
- 方法
- createServer(),创建http服务器的快捷函数
- request(),创建http客户端的快捷函数
- get(),创建http客户端get请求的快捷函数
- 常量集合
- METHODS,HTTP方法名集合
- STATUS_CODES,HTTP状态码集合
- 类
Node http Server
- TCP Server,通过监听端口,根据客户端请求产生服务器端socket对象,是以connection为单位的服务封装
- http Server继承自TCP Server,但不是直接使用底层的Socket,而是用ServerResponse。
- IncomingMessage来抽象响应和请求的读写(底层还是同一个socket对象),抽象成以request为单位的服务封装
Server事件
- 继承自net.Server的事件
- ‘close’ 当server关闭时触发
- ‘connection’ 当一个新的connection建立的时候触发,事件回调函数参数为server为该连接创建的socket
- 新增的事件
- ‘request’ 在接收到一个请求时触发
- ‘clientError’ 客户端触发的’error’事件会被传递过来,该事件的监听器负责关闭或销毁底层的socket
- ‘checkContinue’ 客户端要发送较大数据时,不会直接发送而是先发一个Expect请求,此时触发该事件。服务器响应100状态码后,客户端重新发起数据发送请求,会触发’request’
- …
Server属性和方法
- 继承自net.Server的属性方法
- listening 表示服务器是否正在监听连接
- close() 关闭服务器,停止接收新的客户端请求
- listen() 开始监听,可以传入绑定的IP地址,端口和’listening’事件的回调函数
- 新属性方法
- timeout socket被认定为超时的空闲毫秒数,默认2分钟
- maxHeaderCount 请求的HTTP首部的最大长度
- KeepAliveTimeout 连接复用的超时空闲毫秒数,默认5秒
- setTimeOut() 设置超时的空闲毫秒数
IncomingMessage
- IncomingMessage是Node收到的http报文,是底层TCP socket可读流的抽象
- 作为http服务器端时,表示收到的客户端请求
- 作为http客户端时,表示收到的服务器响应
- IncomingMessage在Node作为服务器端或客户端时的属性有所差异:
- 作为http服务端时,statusCode,statusMessage属性无意义
- 作为http客户端时,method,url属性无意义
- IncomingMessage属性
- headers http报文头的信息
- httpVersion 对方的HTTP版本,可能是’1.1’或’1.0’
- method 客户端请求的HTTP方法,仅在Node作为服务器端时有效
- rawHeaders http报文头的原始信息
- socket 底层的TCP socket
- statusCode 服务器返回的HTTP响应状态码,仅在Node作为客户端时有效
- statusMessage 服务器返回的HTTP响应状态消息,如OK或Internal Server Error,该属性仅在Node作为客户端时有效
- url 客户端请求的URL字符串,该属性仅在Node作为服务器端有效
- 例子:IMServer IMClient
OutgoingMessage
- OutgoingMessage跟IncomingMessage对应,表示Node发送的http报文,是底层TCP socket可写流的抽象
- OutgoingMessage通常不直接使用,属于隐藏的内部类,当Node作为服务器端时使用ServerResponse,当Node作为客户端时使用ClientRequest
ServerResponse的属性和方法
- 属性
- finished 表示响应是否已完成。执行end()之后会变为true
- headersSend 表示响应头是否已发送
- statusCode 设置返回给客户端的HTTP响应状态码
- statusMessage 设置返回给客户端的HTTP响应状态消息
- socket 底层的TCP socket
- 方法
- end() 关闭可写流,可以通过参数写入最后的数据,每次响应都必须调用一次
- write() 发送一块响应主体,可被多次调用
- setHeader() 设置响应头某项的值
- getHeader() 查询响应头某项的值
- removeHeader() 移除响应头某项
ClientRequest的事件、属性和方法
- 事件
- ‘continue’ 当服务器发送了一个100 Continue和HTTP响应时触发
- ‘response’ 当请求的响应被接收到时触发
- 方法
- abort() 标记请求为终止,使响应中剩余的数据被丢弃且socket被销毁
- end() 关闭可写流,可以通过参数写入最后的数据
- write() 发送请求主体的一个数据块,可被多次调用
- 属性
- aborted 请求被终止的时间
- socket 底层的TCP socket
- 例子 SRTest CRTest
http模块的快捷函数
- http.createServer([requestListener]),返回http服务器
- requestListener函数会在’request’事件触发时被调用,包括两个参数:
- request ,IncomingMessage
- response,ServerResponse
- requestListener函数会在’request’事件触发时被调用,包括两个参数:
- 例子:RTest GTest
实现有状态的会话
- 为了效率,HTTP协议具备无连接、无状态特性
- 为了业务,WEB服务器端缺需要进行用户识别,因此需要客户端对请求消息进行标识,常用以下两种机制完成会话跟踪:
- Cookie
- Session
cookie机制
- cookie机制采用的是在客户端保持HTTP状态信息的方案
- Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头重附带传送给浏览器的一个小文本文件
- 一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问这个WEB服务器时,都会在HTTP请求头重将这个Cookie回传给WEB服务器
- 底层的原理实现:Web服务器通过在HTTP响应消息中增加Set-Cookie响应消息头字段将Cookie信息发送给浏览器。浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给Web服务器
- 一个Cookie只能标识一种信息,即标识信息的名称(NAME)和设置值(VALUE)
- 一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie
- 浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB
Node Cookie处理
- 浏览器等客户端发送的Cookie在请求报文的cookie字段里
- Node HTTP服务器可以直接读取请求报文的cookie字段,形式为”key1=value1; key2=value2……”
- Node HTTP服务器可以设置ServerResponse报文的头部Set-Cookie字段,把cookie的key value发给客户端,客户端再次访问会携带该cookie
- 例子 cookie
会话cookie和持久cookie的区别
- 会话cookie
- 不设置过期时间,只要关闭浏览器窗口,cookie就消失了。会话cookie一般不保存在硬盘上而是保存在内存里
- 持久cookie
- 设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效,直到超过设定的过期时间
- 存储在硬盘上的cookie可以在不同的浏览器进程中共享,比如两个IE窗口。而对于保存在内存中的cookie,不同的浏览器有不同的处理方式
Session机制
- session机制采用的是在服务器端保持HTTP状态信息的方案
- 服务器使用一种类似于散列表的结构来保存信息
- session通过SessionID来区分不同的客户,session默认使用cookie来实现,在cookie被禁用时采用URL重写,就是把session id附加在URL路径的后面
HTTP认证
- 客户端向服务器发出对受保护的资源的请求时,服务器会和客户端进行交互(用含有特别的首部和状态码的应答来响应)以确认客户端可以访问保护资源
- HTTP认证就是这个交互过程,HTTP/1.1的认证方式:
- 基本认证(BASIC认证)
- 摘要认证(DIGEST认证)
- SSL客户端认证
- 基于表单认证(FromBase认证)
- BASIC认证
- 认证步骤
- 客户端请求认证资源,服务器返回状态码401 Unauthorized和带WWW-Authenticate字段(Basic开头)首部的响应
- 客户端把“用户名:密码”形式的文本用Base64编码后写入首部字段Authorization,重新发起请求
- 服务器验证成功,响应状态码200
- 缺点
- 密码无加密,易破解
- 注销麻烦
- 例子:Basic
- 认证步骤
- DIGEST认证
- 认证步骤
- 客户端请求认证资源,服务器返回状态码401 Unauthorized和带WWW-Authenticate字段(Digest开头并且有nonce随机字符串)首部的响应
- 客户端把响应摘要 =MD5(HA1:nonce:HA2),其中HA1=MD5(username:realm:password),HA2=MD5(method:digestURI)写入首部字段response,重新发起请求
- 服务器验证成功,响应状态码200
- 认证步骤
Express Web开发
- Why Express
- Node HTTP API比较底层,Node自身提供的web应用服务器一穷二白,应付日常的Web开发需要做一些常规的重复工作,比如静态文件的发送
- Express是一个基于Node.js平台的极简、灵活的web应用开发框架
- 极简,“少即是多”,比如4.x版本把Connect(除static之外的所有中间件)去掉,以便这些中间件可以各自独立升级
- 灵活,可扩展性好,通过中间件模式让很多模块可以按需添加,并且提供多种选择
Express基础安装配置
Express基础项目初始化
- 新建目录
- npm init
- npm install express —save //或从全局区把express模块拷贝过来
var express = require("express"); var app = express(); app.get('/', function(req, res){ // 响应对/的请求 res.send("hello"); // 发送“hello”作为响应文本 }); app.listen(3000); // 监听3000端口
Express模块
- 方法
- static() Express内置的唯一一个中间件,负责托管Express应用内的静态资源
- 类
- Application Express应用实例,类似于http.Server
- Request 代表客户端请求,在http.IncomingMessage上提供一些快捷属性和方法(要结合中间件使用)
- Response 代表服务器响应,在http.ServerResponse上提供一些快捷属性和方法(要结合中间件使用)
- Router 代表路由处理和中间件,每个Express应用都有一个内置的Router对象
- 方法
- Express Application
- Application对象由express()构造,可以独立作为http的Server来使用,另外该对象也是一个函数,所以也可以把该对象传递给http.createServer函数中作为回调函数来使用。
- Application对象还可以包括多个子Application对象用于不同的业务区域(一个Application类似于一个模块,比如前端 后端模块)
- 事件
- ‘mount’,该事件在子Application对象被加载时触发
- 属性
- locals 用来存储应用的本地变量,比如标题、邮箱
- mountpath 子Application对象被加载的web路径,如/admin
- Express Application的主要方法
- http方法相关
- get() 提供HTTP GET请求的处理
- post()
- put()
- delete()
- all() 提供对HTTP各种方法请求的处理
- 其他
- listen() 启动监听,跟http.Server.listen()一样
- set() 配置express的参数,比如页面模版等
- use() 加载路由,中间件函数
- http方法相关
- Express的主要特性
- 中间件,类似于管道,可以对请求做一些列的处理(请求和响应是一个双向流,在这个过程中间是一个管道 中间件就是这个管道上 可以在请求或响应上面加点东西 是一个垂直的概念)
- 路由,将请求(由URL和HTTP方法指定)路由到处理它们的代码去的一种机制(水平的概念)
- 模版,支持各种产生html的模版引擎(比如jsp php都会用到模板 一个基本的html页面)
- 中间件
- http.Server响应客户端请求是通过’request’事件的回调函数来处理(该函数通常作为createServer的参数传入),把所有应用逻辑都打包在这个回调函数中会使该函数臃肿不堪且可复用性不高
- express对象(connect)把这个回调函数转换为一个处理函数function(request,response,next)的数组,各个处理函数会依次运行且只处理一项事务,这些处理函数可在大多数应用中复用,所以命名为中间件(Middleware)
- 中间件函数默认会依次执行,而有些中间件运行结果会影响后续执行(比如用户认证),因此要考虑中间件的运行顺序(比如把日志记录放前面)
- 自定义中间件
- 中间件通常为function(request, response, next)
- request, Express.Request对象,表示请求
- response, Express.response对象,表示响应
- next, 表示处理函数数组的下一个函数
- 没有后续处理函数的中间件可以运行为function(request, response),与http模块的’request’事件的回调函数一致
- 例子:mw1 mw2
- 中间件通常为function(request, response, next)
- 第三方中间件
- 常用第三方中间件
- static 提供静态文件支持,express内置,唯一 不用安装
- morgan 提供自动日志记录支持
- cookie-parser 提供对cookie的支持
- express-session 提供对session的支持
- body-parser 提供对form提交的内容解析的支持
- 还有很多第三方中间件,在npm网站上搜索express middleware有三千多个
- 第三方中间件通常在express项目的目录上执行npm install —save MIDDLEWARE,以安装中间件和在package.json上配置中间件的当前版本信息
- 例子:mw3 mw4
- 常用第三方中间件
- 路由
- 传统应用服务器提供的路由是基于静态文件路径的,所以网页url最后通常是”.jsp .asp .php .html”,应用服务器加载请求的静态文件生成动态html再返回给客户端。这种url本身的语义性较差
- REST强调URL来获取资源的表征,通过HTTP方法来传递对资源的具体操作,使用HTTP状态码作为操作结果返回
- Express路由提供将请求(由URL和HTTP方法指定,URL与静态文件无关)路由到处理它们的代码去的一种机制
- Express路由
- Express中间件是纵向的,对每个请求依次调用每个中间件处理函数
- Express路由是横向的,对URL和HTTP方法不同的请求调用不同的处理函数
- 例子:route1
- Express路由URL的通配性
- URL语法支持传参,要求express路由对URL可以通配处理
- url语法匹配,用url查询字符串来获取参数值
- 简单匹配,在url路径用:para获取参数值
- 正则匹配,用正则表达式匹配url以进行路由
- 例子:route2
- URL语法支持传参,要求express路由对URL可以通配处理
- 路由的组织
- 每个Express Application对象都内置一个Router对象,该对象负责加载中间件,识别不同的请求和路由到不同的处理函数
- 一个网站可能包括多个子域,比如论坛、后台、API等,在一个文件定义所有路由太笨重,也不方便修改。可以采用一个主express带多个子express的方式,也可以在一个express中创建多个Router对象的方式
- 例子:route3
RESTful Api
- RESTful API提供统一的机制,方便不同的前端设备与后端进行通信
- RESTful API规范
- GET /collection 返回资源对象的列表(数组)
- GET /collection/resource 返回单个资源对象
- POST /collection 新建资源对象,返回新生成的资源对象
- PUT /collection/resource 修改资源对象,返回完整的资源对象
- DELETE /collection/resource 删除资源对象,返回一个空文档
- Express RESTful API
- express要处理POST、PUT等请求时,需要解析请求的body部分,需要额外中间件
- body-parser 用来支持json、form格式等封装的数据
- multer 用来支持上传文件
- Express RESTful API的简单测试可以用curl
- curl http://localhost:1337/staffs/
- curl http://localhost:1337/staffs/1
- curl -X POST —data “id=3&name=king” http://localhost:1337/staffs/
- curl -X PUT —data “name=John” http://localhost:1337/staffs/3
- curl -X DELETE http://localhost:1337/staffs/2
- 例子:rest
- express要处理POST、PUT等请求时,需要解析请求的body部分,需要额外中间件
- RESTful API的安全验证
- RESTful API涉及内部数据需要安全验证
- 传统网站用户验证流程基于浏览器
- 用户通过form提交用户名、密码
- 服务器验证通过后通过cookie、session来保持用户验证状态
- 而RESTful API属于通用网络API,除了支持浏览器,还要支持移动客户端,桌面应用等其他,不适用基于浏览器的验证流程
- 通用类网络API通常时无状态的,所以对用户的验证主要通过有时效性的token(令牌),比如微信公众号的API
JSON Web Token
- JSON Web Token(JWT)是一个轻量级的认证规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
- 规范文档
- JWT的组成
- 头部(Header),描述最基本的信息,类型以及签名所用的算法,用Base64编码,相当于不加密
- 载荷(Payload),包含要传递内容和辅助信息的JSON对象,用Base64编码,相当于不加密
- 签名(Signature),把头部和载荷用Base64编码后再进行加密,保证前两部分内容没有被篡改(加密秘钥只有服务器端有)
- JSON Web Token测试
- npm提供jsonwebtoken包用于处理JSON Web Token
- jsonwebtoken可以把json数据作为Payload部分进行签名(秘钥和过期时间自行设置)生成Token
- Payload的标准字段:
- iss: Issue 发行者
- sub: Subject 主体
- aud: Audience 观众
- exp: Expiration time 过期时间
- nbf: Not before
- iat: Issue at 发行时间
- jti: JWT ID
- Payload的标准字段:
- jsonwebtoken也可以验证某token是否被篡改和时间已失效,并提取payload内容
- 例子:jwt
- 基于jwt的RESTful API的安全验证
- 步骤:
- 客户端post用户和密码到服务器
- 服务器验证后返回包含用户id和有效时间的token
- 客户端请求RESTful API,在请求的url的查询字符串里附上token值(也可以放在请求头部,但查询字符串对移动客户端更方便)
- 服务器验证token有效后,执行RESTful API命令
- curl测试 例子:secRest
- 登陆 curl -X POST —data “name=sys&pw=abc” http://localhost:1337/login/
- 全部 curl http://localhost:1337/staffs/?access_token=
- 增加 curl -X POST —data “id=3&name=king” http://localhost:1337/staffs/?access_token=
- 步骤:
模版
- web服务器的核心任务是向客户端提供html
- web应用核心任务动态生成html
- 模版文件用来定义html页面的静态部分和动态生成部分
- 模版引擎负责解析模版文件,渲染成html页面
- 很多模版引擎与开发语言已经解耦
- Express模版
- Express支持多个模版引擎
- Pug(Jade),express作者设计,官方引擎,最简洁
- EJS,最像原生html,最简单
- Handlebars,著名模版引擎Mustache的扩展
- 使用模版步骤
- 在项目目录npm install模版引擎 —save
- 配置express模版文件目录和使用的模版引擎
- 在response.render方法中传递使用的模版文件
- 例子:ejs, pug
- Express支持多个模版引擎
- Express常规项目配置
- 为了减少不必要的重复和直接使用最新的模块版本,Express提供一个脚手架工具express-generator
- 脚手架工具安装
- npm install express-generator -g 会在系统安装一个express-generator的命令行,用express执行
- 脚手架工具的用法 express -h
- 脚手架工具使用流程
- express —view=pug myapp
- cd myapp
- npm install