【BUAA软工】技术博客——HTTP协议

浅谈HTTP协议与前后端实现

HTTP协议概要

HTTP协议是计算机网络里的应用层协议,在网络层使用的是具有稳定传输的TCP协议,建立连接时依照TCP协议的三次握手,四次挥手进行连接和断开。本博客主要讲解顶层相关的,程序员比较关心的顶层编程实现,因此不细讲HTTP协议的底层实现。

HTTP协议的方法有很多种,例如POST,GET,PUT,DELETE等。这里本博客仅对两种常用的方法POST和GET进行分享。HTTP报文的结构主要有两个部分,一个是报文头,一个是报文体。

HTTP报文头

HTTP报文的报文头一般负责和通信对方协商一些信息,包括编码格式,连接方式等。一般情况下,请求报文头和回应报文头的字段略有不同。下面列举一些常见的报文头字段:

请求报文头

以访问百度发出的GET请求为例,浏览器发出的请求报文头的字段值如上图所示,其中常用字段的意义分别表示为:

  • Accept:表示请求方可以接收的报文格式
  • Accept_Encoding:表示请求方的编码格式
  • Accept_Language:表示请求方使用的语言
  • Connection:表示连接方式,一般情况下默认开启Keep-Alive,表示客户端和服务端保持长连接,即使本次HTTP请求结束后仍保持TCP连接,下一次请求时不必再进行TCP连接建立,直至客户端或者服务端程序关闭或者主动断开连接时,才断开TCP连接。如果该字段值为Closed,则表示在本次请求结束后,服务端和客户端的连接将断开
  • Cookie:Cookie字段,保存一些服务端返回给客户端的浏览凭证,一般用于服务端验证客户端身份
  • Host:请求访问的主机域名或IP地址
  • Sec-Fetch-xxx:这些字段表示跨站请求模式以及一些相关参数
  • User-Agent:表示客户端使用的设备或浏览器,服务端通过过滤一些User-Agent非法的请求来防爬虫脚本(虽然User-Agent基本可以伪造,该防御手段已经过时)

以上展示的是GET请求的报文头,对于POST请求,一般还多出以下字段:

  • Content-Type:报文体的内容格式,POST请求报文体一般有JSON,urlencoded,form-data格式等,其中JSON即字典格式,urlencoded即和GET请求的url格式一样传递参数,form-data是一种特殊的格式,参数之间以特定的字符串进行分隔,此时该字段会多一个子字段boundary以表示分隔的字符串
  • Content-Length:报文体的长度,供接收端检验报文以及读取报文。

这里已经可以体会到,GET和POST方法传递参数的模式有一定区别,GET请求通过在url中传递请求的参数,POST请求通过报文体传输请求的参数。一般情况下,url的长度不宜过长,因此GET请求的参数长度有一定限制。而POST请求在理论上参数可以无限大,但一般由于服务端设置不同,因此有不同的报文大小限制,但相较于GET请求POST请求可发送的参数大小还是大得多的。

应答报文头

应答报文头的字段一般和请求报文头很不相同,因为应答是服务端对客户端的应答,一般不再是传递一些沟通参数,而是传递一些结果参数,即对请求处理后返回的结果的一些参数。一些常用字段有:

  • Content-Length:返回报文体长度
  • Location:重定向地址,一般要求客户端去向Location地址发送请求
  • Server:服务端使用的服务器框架
  • Set-Cookie:服务端向客户端分发的Cookie,一般有Cookie值,使用域,使用路径等
  • Access-Control-Allow-Credentials:要求客户端是否携带证书来访问
  • Access-Control-Allow-Origin:是否允许跨域,如果不允许,则在该字段值填写指定的前端网站的域名地址,若允许则可使用‘*’

使用HTTP协议通信

前端实现HTTP请求

这里以javascript为例,一般前端浏览器都有内置类XMLHttpRequest用来实现HTTP请求的发送和处理,通过实例化该类的对象,就能够发送HTTP请求并对response进行处理。

发送请求需要实例化XMLHttpRequest对象

扫描二维码关注公众号,回复: 11278975 查看本文章
var http = new XMLHttpRequest()
http.withCredentials = true 

其中对象里有一个属性为withCredentials表示是否携带Cookie,为true时则后续请求会自动携带该站点的相关Cookie,不需要程序员手动管控Cookie

发送请求:

// GET
http.open("GET", url, true)
http.send()

// POST
http.open("POST", url, true)
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
http.send(data)

这里的POST请求需要注意,Content-type字段值根据data格式进行相应的设置。

open函数有三个参数,第一个参数代表请求方法,第二个参数代表请求的url,第三个参数为是否异步。若为true则为异步,即不阻塞后续程序;若为false则为同步,即请求过程阻塞整个程序,知道response到来才继续执行。一般情况下异步较为多,同步会导致页面阻塞,用户体验极差。

设置处理回调函数

http.onreadystatechange = function(data) {
    if (http.readyStatus == 4 && http.status == 200) {
        // OK
    } else if (http.readyState == 4) {
        // Fail
    }
}

这里readyStatus有0-4五种状态,分别为:

  • 0:初始化,XMLHttpRequest对象还没有完成初始化
  • 1:载入,XMLHttpRequest对象开始发送请求
  • 2:载入完成,XMLHttpRequest对象的请求发送完成
  • 3:解析,XMLHttpRequest对象开始读取服务器的响应
  • 4:完成,XMLHttpRequest对象读取服务器响应结束

status为请求返回的服务器回应码,200为OK,500为Server Error,404为NOT Found等

一些坑:

  • 一般来讲,一个XMLHttpRequest对象可以发送若干个请求,也可以同时使用多个XMLHttpRequest发送多个请求,但是一个XMLHttpRequest在同一时刻(较短的时间内)只能发送一个请求,因此如果要连续同时发送多个请求,建议使用多个XMLHttpRequest对象进行发送。注意进行对象的销毁避免缓存占用过大
  • 对于一些浏览器的跨域要求非常严格,对于Chrome浏览器要求报文返回头必须要有Access-Control-Allow-Credentials和Access-Control-Allow-Origin字段,否则报文会被浏览器拦截。对于Safari浏览器Access-Control-Allow-Origin字段必须限制跨域,否则可能会被Safari跨域设置所拦截
  • 关于Cookie值的操作,前端脚本最好不要进行Cookie值操作,否则会有一系列不安全的隐患,Chrome浏览器也禁止了这一点。

后端构建HTTP服务器

HTTP服务器即处理HTTP请求的服务程序。这里的后端以nodejs为例,使用express框架实现http服务器搭建。

express框架+bodyParser插件搭建http服务器非常简洁,express框架为程序员准备好了一套http处理流程,程序员只需要设定相关回调函数即可,而bodyParser帮助程序员做好了http报文的解析,程序员可以直接拿到请求参数。构建http服务器:

const app = express()
app.use(bodyParser.json({limit:'100mb'}));
app.use(bodyParser.urlencoded({ limit:'100mb', extended: true }));
app.use(function (req, res, next) {
    res.setTimeout(60*1000, function () {
        console.log("Request has timed out.");
        return res.status(408).send("请求超时")
    });
    next();
});

app是express实例,也是服务器实例,前两行使用bodyParser设定了请求参数的解析格式,这里设定了JSON和urlencoded参数的解析,当发送的请求为JSON格式或者urlencoded格式时,程序员不再需要进行参数解析,就可以直接获取各个参数。

第三行设定了服务器超时相应,这里设置的超时时间为60秒。

服务器监听端口:

app.listen(port, function() {
    console.log('Listen at %d', port)
})

处理get请求:

app.get('/', function(req, res) {
    let param = req.query
    let path = req.path
    res.statusCode = 200
    res.end(message)
}

由于设置了bodyParser,因此这里可以直接调用req.query就可以获取请求参数,即将参数转化为JSON格式对象。

处理post请求:

app.post('/', function(req, res) {
    let param = req.body
    let path = req.path
    res.statusCode = 200
    res.end(message)
})

同理,调用req.body即可获取请求参数。

另外,可以使用app.all对所有请求进行处理:

app.all('*', function (req, res) {
    switch (req.method) {
        case 'POST':
            switch (req.path) {
                case '/':
                    break;
                default:
                    break;
            }
            break;
        case 'GET':
            switch (req.path) {
                case '/':
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
})

这样的写法在扩展时可以对一些代码进行复用,比如res的处理,拓展比较方便,但是在可读性上会稍差一些。

一些坑:

  • 对于POST请求,最好设置最大接收报文体大小,否则一些较大的报文可能会被服务器拦截导致接收数据错误。
  • 对于文件的传输,最好使用文件流的方式进行传输,可以结合fs模块的writeStream进行实现。

实战例子:实现北航云盘转储服务

  • 需求:实时向北航云盘保存数据以及下载数据
  • 功能:
    • 向固定账户的北航云盘上传文件
    • 下载固定账户的某个文件
    • 实时保存登录状态
  • 使用框架:nodejs+express+https模块

北航云盘行为调研

实现

猜你喜欢

转载自www.cnblogs.com/lpxofbuaa/p/12971986.html