【http协议】超详细介绍

文章目录

  • Method
    • HEAD
  • Content-Type
    • miultipart/form-data
    • x-www-form-urlencoded
    • application/json

http 协议是 第七层协议,其在前、后端、移动端都很常用,通常都用 json 传递,但其实也有很多传递方式,本文将对 Content-Type 一探究竟。

Method

常有的有GET、POST、PUT(全量编辑)、PATCH(增量编辑)、DELETE、HEAD(仅头部)、OPTIONS等。

HEAD

HEAD 类似 GET,但因为只返回 header 所以速度更快。 浏览器常用此方式检查资源是否变化,若未变化则复用缓存,否则用 GET 重新请求资源。

参考:HTTP HEAD Request Method

Content-Type

Content-Type 用于指明 http 协议的媒体类型(传文本,文件,图片,还是视频等),在 chrome 的 F12 开发者工具可以看到 Content-Type,如下图所示:

【http协议】超详细介绍_第1张图片

其由三部分组成:

  • media type:媒体类型(如 application/json),接收方根据 media type 来处理不同的数据内容(如文件、图片或视频等)
  • charset:字符类型(如 utf-8)
  • boundary:分隔符(是唯一的一个字符串,用来将较长的内容分隔开,如-----------------------------340073633417401055292887335273)

其中 media type 有如下值域:

# Application
application/EDI-X12
application/EDIFACT
application/javascript
application/octet-stream
application/ogg
application/pdf
application/xhtml+xml
application/x-shockwave-flash
application/json
application/ld+json
application/xml
application/zip
application/x-www-form-urlencoded

# Audio	
audio/mpeg
audio/x-ms-wma
audio/vnd.rn-realaudio
audio/x-wav

# Image	
image/gif
image/jpeg
image/png
image/tiff
image/vnd.microsoft.icon
image/x-icon
image/vnd.djvu
image/svg+xml

# Multipart
multipart/mixed
multipart/alternative
multipart/related (using by MHTML (HTML mail).)
multipart/form-data

# Text
text/css
text/csv
text/html
text/javascript (obsolete)
text/plain
text/xml

# Video
video/mpeg
video/mp4
video/quicktime
video/x-ms-wmv
video/x-msvideo
video/x-flv
video/webm

miultipart/form-data

在 postman 中即可选 multipart/form-data 类型,其中可同时传图片、文件和文本,示例如下:

【http协议】超详细介绍_第2张图片

我们来看一个简单的 form 表单:

<form action="/submit" method="POST" enctype="multipart/form-data">
    <input type="text" name="username"><br>
    <input type="text" name="password"><br>
    <button>提交button> 
form>

当提交的时候,查看浏览器的网络请求:

请求头:

POST /submit HTTP/1.1
Host: localhost:3000
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------340073633417401055292887335273
Content-Length: 303

请求体:

-----------------------------340073633417401055292887335273
Content-Disposition: form-data; name="username"

张三
-----------------------------340073633417401055292887335273
Content-Disposition: form-data; name="password"

123456
-----------------------------340073633417401055292887335273--

具体格式是这样的:

...
Content-Type: multipart/form-data; boundary=${boundary} 

--${boundary}
...
...

--${boundary}--

这就是 multipart/form-data 的传输过程了,但是这里面有三个大坑:

请求头 Content-Type 里面的 boundary 分隔符比请求体用的分隔符少了两个杠(-)

从请求头中取分隔符之后,一定要在之前加两个 - 再对请求体进行分割

请求头 Content-Length 的换行用的是 \r\n 而不是 \n

请求体的真实面目是下面的字符串:

“-----------------------------340073633417401055292887335273\r\nContent-Disposition: form-data; name=“username”\r\n\r\n张三\r\n-----------------------------340073633417401055292887335273\r\nContent-Disposition: form-data; name=“password”\r\n\r\n123456\r\n-----------------------------340073633417401055292887335273–\r\n”

请求头 Content-Length 的值表示字节的长度,而不是字符串的长度

因为字节的长度跟编码无关,而字符串的长度往往跟编码有关,举个例子,在 utf8 编码下:

console.log('a1'.length) // 2
console.log(Buffer.from('a1').length) // 2
console.log('张三'.length) // 2
console.log(Buffer.from('张三').length) // 6

如果仅仅是基本的字符串类型,完全可以用 www-form-urlencoded 来进行传输,multipart/form-data 强大的地方是其能够传输二进制文件的能力,我们看一下如果包含二进制文件的话应该如何处理。我们增加一个 file 类型的 input,上传一张图片作为头像,发现请求体多出了一部分:

-----------------------------114007818631328932362459060915
Content-Disposition: form-data; name="avatar"; filename="1.jpg"
Content-Type: image/jpeg

xxxxxx文件的二进制数据xxxxx

可以发现,文件类型的 part 跟之前字符串的格式有所不同了,head 部分有两个头字段,多出一个 Content-Type 头,而且 Content-Disposition 头多出来 filename 字段,body 部分是文件的二进制数据。
了解这这些规律之后,接下来就可以在服务端对 multipart/form-data 进行解码了:

const http = require('http')
const fs = require('fs')
http
  .createServer(function (req, res) {
    // 获取 content-type 头,格式为: multipart/form-data; boundary=--------------------------754404743474233185974315
    const contentType = req.headers['content-type']
    const headBoundary = contentType.slice(contentType.lastIndexOf('=') + 1) // 截取 header 里面的 boundary 部分
    const bodyBoundary = '--' + headBoundary // 前面加两个 - 才是 body 里面真实的分隔符
    const arr = [], obj = {}
    req.on('data', (chunk) => arr.push(chunk))
    req.on('end', function () {
      const parts = Buffer.concat(arr).split(bodyBoundary).slice(1, -1) // 根据分隔符进行分割
      for (let i = 0; i < parts.length; i++) {
        const { key, value } = handlePart(parts[i])
        obj[key] = value
      }
      res.end(JSON.stringify(obj))
    })
  })
  .listen(3000)

// 对分隔出来的每一部分单独处理,如果是二进制的就保存到文件,是字符串就返回键值对:
function handlePart(part) {
  const [head, body] = part.split('\r\n\r\n') // buffer 分割
  const headStr = head.toString()
  const key = headStr.match(/name="(.+?)"/)[1]
  const match = headStr.match(/filename="(.+?)"/)
  if (!match) {
    const value = body.toString().slice(0, -2) // 把末尾的 \r\n 去掉
    return { key, value }
  }
  const filename = match[1]
  const content = part.slice(head.length + 4, -2) // 文件二进制部分是 head + \r\n\r\n 再去掉最后的 \r\n
  fs.writeFileSync(filename, content)
  return { key, value: filename }
}

// 这里面涉及到 buffer 的分割,nodejs 中并没有提供 split 方法,可根据 slice 方法自己实现
Buffer.prototype.split = function (sep) {
  let sepLength = sep.length, arr = [], offset = 0, currentIndex = 0
  while ((currentIndex = this.indexOf(sep, offset)) !== -1) {
    arr.push(this.slice(offset, currentIndex))
    offset = currentIndex + sepLength
  }
  arr.push(this.slice(offset))
  return arr
}

参考

x-www-form-urlencoded

Get 方法通常用 Content-Type = application/x-www-form-urlencoded 方式,而在 url 中有两种方式:

  • query:即在 url 尾部用 www.ppp.com/qqq/?a=x&b=y&c=z 使用,其语义是参数,且可传递多个参数
  • path:即在 url 中间用 www.ppp.com/qq q 使用,其语义是 RESTful 来表示资源,通常放置 id、name 这些信息等

application/json

在 postman 软件,即可设置为 application/json 方式,这是 web 端文本常用的消息格式,示例如下:

【http协议】超详细介绍_第3张图片

你可能感兴趣的:(http,javascript,网络协议)