共计 5206 个字符,预计需要花费 14 分钟才能阅读完成。
Accept-Language
Accept-Language 通常用来实现多语言:
Accept-Language: <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 协定范畴申请容许服务器只发送 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.