Accept-Language
Accept-Language通常用来实现多语言:
Accept-Language:
Accept-Language: *
// Multiple types, weighted with the quality value syntax:
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5
;q= (q-factor weighting)
表示优先顺序,用相对质量价值表示,又称为权重。
简单实现:
const http = require("http")
const languages = {zh: "你好",en: "hello",jp: "こんにちは",}
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "text/plain;charset=utf-8")
let lang = req.headers["accept-language"] // zh-CN,zh;q=0.9,en;q=0.8
if (!lang) return res.end("")
lang = lang
.split(",")
.filter((t) => t.includes(";"))
.map((t) => {
let [name, q] = t.split(";")
return {
name,
q: q.slice(2) * 1,
}
})
.sort((a, b) => a.q > b.q)
for (let i = 0; i < lang.length; i++) {
let work = languages[lang[i].name]
if (work) {
res.end(work)
break
}
}
res.end(JSON.stringify(lang))
})
server.listen(3000, () =>console.log(`Serving on: \r\n http://localhost:3000`))
Accept-Ranges
HTTP 协议范围请求允许服务器只发送 HTTP 消息的一部分到客户端。范围请求在传送大的媒体文件,或者与文件下载的断点续传功能搭配使用时非常有用。
范围请求的响应状态:
206 Partial Content
服务请求成功416 Requested Range Not Satisfiable
请求范围越界(范围值超过了资源的大小)200 OK
不支持范围请求返回整个资源,分段下载时,要先判断
Range
请求头:
Range: bytes=start-end
Range: bytes=10-
:第10个字节及最后个字节的数据 Range: bytes=40-100
:第40个字节到第100个字节之间的数据.
注意,这个表示[start,end],即是包含请求头的start及end字节的,所以,下一个请求,应该是上一个请求的[end+1, nextEnd]
Content-Range
响应头
// 服务器响应了前(0-10)个字节的数据,该资源一共有(3103)个字节大小。
Content-Range: bytes 0-10/3103;
// 服务器响应了11个字节的数据(0-10)
Content-Length: 11;
代码实现
服务端按range
范围下载
const path = require("path")
const http = require("http")
const fs = require("fs")
const DOWNLOAD_FILE = path.resolve(__dirname, "./server_download.txt")
const TOTAL = fs.statSync(DOWNLOAD_FILE).size;
http.createServer((req, res) => {
res.setHeader("Content-Type", "text/plain;charset=utf-8")
// curl http://www.example.com -i -H "Range: bytes=0-50"
const range = req.headers["range"]
// 没有range直接返回文件
if (!range) return fs.createReadStream(DOWNLOAD_FILE).pipe(res)
// 截取范围值,还有种用,隔开的情况这里暂不考虑
let [, start, end] = range.match(/(\d*)-(\d*)/)
start = start ? start * 1 : 0
end = end ? end * 1 : TOTAL
// 范围请求成功状态码 206 Partial Content
res.statusCode = 206
// 设置响应头
res.setHeader("Content-Range", `bytes ${start}-${end}/${TOTAL}`)
// 返回范围数据
fs.createReadStream(DOWNLOAD_FILE, { start, end }).pipe(res)
}).listen(3000, () => console.log(`Serving on 3000`))
客户端下载
const path = require("path")
const http = require("http")
const fs = require("fs")
const DOWNLOAD_FILE = path.resolve(__dirname, "./client_download.txt")
const ws = fs.createWriteStream(DOWNLOAD_FILE)
let start = 0
let mode = "start" //下载模式 "start" | "pause"
download()
function download() {
const downloadConfig = {
hostname: "localhost",
port: 3000,
encoding: "utf-8",
headers: {
Range: `bytes=${start}-${start + 100}`,
},
}
const request = (res) => {
let total = res.headers["content-range"].split("/")[1] * 1
res.on("data", (chunk) => {
ws.write(chunk)
if (start <= total) {
start += 101
// 打印下载进度
console.clear();
console.log(`下载进度:${Math.min(parseInt((start / total) * 100), 100)}%\r\n按p键后回车可暂停`)
setTimeout(()=>{
// mode 是start模式 则继续下载
mode === "start" ? download() : console.log("暂停下载,按任意键回车后下载")
},1000)
} else {ws.end()}
})
res.on("end", () => {
if (total > start) return;
console.log("下载完成")
process.exit(1)
})
}
http.get(downloadConfig, request)
}
process.stdin.on("data", (chunk) => {
if (chunk.toString().includes("p")) {
//键盘p暂停下载
mode = "pause"
} else {
mode = "start"
download()
}
})
User-Agent
User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。
User-Agent
判断是否移动端,重定向到新地址:
require("http")
.createServer((req, res) => {
const ua = req.headers["user-agent"];
const isMobile = /(iPhone|iPad|iPod|iOS|Android)/i.test(ua);
const redirectUrl = isMobile ? "https://m.58.com/gz" : "https://gz.58.com";
res.statusCode = 302;
res.setHeader("Location", redirectUrl)
res.end()
})
.listen(3000, () => console.log(`Serving on: \r\n http://localhost:3000`))
Referer
Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。
示例
Referer: https://developer.mozilla.org/en-US/docs/Web/JavaScript
以下两种情况下,Referer 不会被发送:
- 来源页面采用的协议为表示本地文件的 "file" 或者 "data" URI
- 当前请求页面采用的是非安全协议,而来源页面采用的是安全协议(HTTPS)
判断盗链示例:
const url = require("url");
const http = require("http");
http.createServer((req, res) => {
let referer = req.headers["referer"];
if (referer) {
let refererHost = url.parse(referer).host
let host = req.headers["host"];
if(refererHost!==host){
// 被盗链了
}
}
}).listen(3000, () => console.log(`Serving on: \r\n http://localhost:3000`))
Content-Encoding
Content-Encoding 是一个实体消息首部,用于对特定媒体类型的数据进行压缩。当这个首部出现的时候,它的值表示消息主体进行了何种方式的内容编码转换。这个消息首部用来告知客户端应该怎样解码才能获取在 Content-Type 中标示的媒体类型内容。
一般建议对数据尽可能地进行压缩,因此才有了这个消息首部的出现。不过对于特定类型的文件来说,比如jpeg图片文件,已经是进行过压缩的了。有时候再次进行额外的压缩无助于负载体积的减小,反而有可能会使其增大。
使用 gzip
方式进行压缩
服务端配置Content-Encoding
字段:
Content-Encoding:gzip
客户端使用 Accept-Encoding
字段说明接收方法:
Accept-Encoding: gzip, deflate
代码实现
const fs = require("fs");
const zlib = require("zlib")
const http = require("http");
http
.createServer((req, res) => {
let encoding = req.headers["accept-encoding"]
if(!encoding) return fs.createWriteStream("./test.html").pipe(res);
if(/\bgzip\b/.test(encoding)){
res.setHeader("Content-Encoding","gzip");
return fs.createWriteStream("./test.html").pipe(zlib.createGzip()).pipe(res)
}
if(/\bdeflate\b/.test(encoding)){
res.setHeader("Content-Encoding", "bdeflate")
return fs.createWriteStream("./test.html").pipe(zlib.createDeflate()).pipe(res)
}
})
.listen(3000, () => console.log(`Serving on: \r\n http://localhost:3000`))
注意事项
根据HTTP规范,HTTP的消息头部的字段名,是不区分大小写的.
3.2. Header Fields
Each header field consists of a case-insensitive field name followed by a colon (”:“), optional leading whitespace, the field value, and optional trailing whitespace.