feature: 新增缓存拦截器,并为github站点添加5类静态资源的缓存拦截配置。
parent
5a96635844
commit
cf4aa83bd1
|
@ -181,10 +181,11 @@ const intercepts = {
|
|||
//需要拦截url的正则表达式
|
||||
'/.*/.*/releases/download/': {
|
||||
//拦截类型
|
||||
// redirect:url, 临时重定向(url会变,一些下载资源可以通过此方式配置)
|
||||
// proxy:url, 代理(url不会变,没有跨域问题)
|
||||
// abort:true, 取消请求(适用于被***封锁的资源,找不到替代,直接取消请求,快速失败,节省时间)
|
||||
// success:true, 直接返回成功请求(某些请求不想发出去,可以伪装成功返回)
|
||||
// redirect: url, 临时重定向(url会变,一些下载资源可以通过此方式配置)
|
||||
// proxy: url, 代理(url不会变,没有跨域问题)
|
||||
// abort: true, 取消请求(适用于被***封锁的资源,找不到替代,直接取消请求,快速失败,节省时间)
|
||||
// success: true, 直接返回成功请求(某些请求不想发出去,可以伪装成功返回)
|
||||
// cacheDays: 1, GET请求的缓存时间,单位天(常用于一些静态资源)
|
||||
redirect: 'download.fastgit.org'
|
||||
},
|
||||
'.*':{
|
||||
|
|
|
@ -71,6 +71,14 @@ module.exports = {
|
|||
proxy: 'github.com',
|
||||
desc: '目前禁掉sni就可以直接访问,如果后续github.com的ip被封锁,只能再走proxy模式',
|
||||
sni: 'baidu.com'
|
||||
},
|
||||
'/fluidicon.png': {
|
||||
cacheDays: 365,
|
||||
desc: 'Github那只猫的图片,缓存1年'
|
||||
},
|
||||
'^(/[^/]+){2}/pull/\\d+/open_with_menu.*$': {
|
||||
cacheDays: 7,
|
||||
desc: 'PR详情页:标题右边那个Code按钮的HTML代理请求地址,感觉上应该可以缓存。暂时先设置为缓存7天'
|
||||
}
|
||||
},
|
||||
'github-releases.githubusercontent.com': {
|
||||
|
@ -89,6 +97,10 @@ module.exports = {
|
|||
'.*': {
|
||||
proxy: 'camo.githubusercontent.com',
|
||||
sni: 'baidu.com'
|
||||
},
|
||||
'^[a-zA-Z0-9/]+(\\?.*)?$': {
|
||||
cacheDays: 365,
|
||||
desc: '图片,缓存1年'
|
||||
}
|
||||
},
|
||||
'collector.github.com': {
|
||||
|
@ -110,12 +122,30 @@ module.exports = {
|
|||
'.*': {
|
||||
proxy: 'user-images.githubusercontent.com',
|
||||
sni: 'baidu.com'
|
||||
},
|
||||
'^/.*\\.png(\\?.*)?$': {
|
||||
cacheDays: 365,
|
||||
desc: '用户在PR或issue等内容中上传的图片,缓存1年。注:每张图片都有唯一的ID,不会重复,可以安心缓存'
|
||||
}
|
||||
},
|
||||
'private-user-images.githubusercontent.com': {
|
||||
'.*': {
|
||||
proxy: 'private-user-images.githubusercontent.com',
|
||||
sni: 'baidu.com'
|
||||
},
|
||||
'^/.*\\.png(\\?.*)?$': {
|
||||
cacheDays: 365,
|
||||
desc: '用户在PR或issue等内容中上传的图片,缓存1年。注:每张图片都有唯一的ID,不会重复,可以安心缓存'
|
||||
}
|
||||
},
|
||||
'avatars.githubusercontent.com': {
|
||||
'.*': {
|
||||
proxy: 'avatars.githubusercontent.com',
|
||||
sni: 'baidu.com'
|
||||
},
|
||||
'^/u/\\d+(\\?.*)?$': {
|
||||
cacheDays: 365,
|
||||
desc: '用户头像,缓存1年'
|
||||
}
|
||||
},
|
||||
'api.github.com': {
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
function getMaxAge (interceptOpt) {
|
||||
// 秒
|
||||
if (interceptOpt.cacheSeconds > 0 || interceptOpt.cacheMaxAge > 0 || interceptOpt.cache > 0) {
|
||||
return interceptOpt.cacheSeconds || interceptOpt.cacheMaxAge || interceptOpt.cache
|
||||
}
|
||||
// 分钟
|
||||
if (interceptOpt.cacheMinutes > 0) {
|
||||
return interceptOpt.cacheMinutes * 60 // 60:1分钟
|
||||
}
|
||||
// 小时
|
||||
if (interceptOpt.cacheHours > 0) {
|
||||
return interceptOpt.cacheHours * 3600 // 60 * 60 一小时
|
||||
}
|
||||
// 天
|
||||
if (interceptOpt.cacheDays > 0) {
|
||||
return interceptOpt.cacheDays * 86400 // 60 * 60 * 24 一天
|
||||
}
|
||||
// 星期
|
||||
if (interceptOpt.cacheWeeks > 0) {
|
||||
return interceptOpt.cacheWeeks * 604800 // 60 * 60 * 24 * 7 一周
|
||||
}
|
||||
// 月
|
||||
if (interceptOpt.cacheMonths > 0) {
|
||||
return interceptOpt.cacheMonths * 2592000 // 60 * 60 * 24 * 30 一个月
|
||||
}
|
||||
// 年
|
||||
if (interceptOpt.cacheYears > 0) {
|
||||
return interceptOpt.cacheYears * 31536000 // 60 * 60 * 24 * 365 一年
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 获取 lastModifiedTime 的方法
|
||||
function getLastModifiedTimeFromIfModifiedSince (rOptions, log) {
|
||||
// 获取 If-Modified-Since 和 If-None-Match 用于判断是否命中缓存
|
||||
const lastModified = rOptions.headers['if-modified-since']
|
||||
if (lastModified == null || lastModified.length === 0) {
|
||||
return null // 没有lastModified,返回null
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试解析 lastModified,并获取time
|
||||
return new Date(lastModified).getTime()
|
||||
} catch (e) {
|
||||
// 为数字时,直接返回
|
||||
if (/\\d+/g.test(lastModified)) {
|
||||
return lastModified - 0
|
||||
}
|
||||
|
||||
log.warn(`cache intercept: 解析 if-modified-since 失败: '${lastModified}', error:`, e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requestIntercept (context, interceptOpt, req, res, ssl, next) {
|
||||
const { rOptions, log } = context
|
||||
|
||||
if (rOptions.method !== 'GET') {
|
||||
return // 非GET请求,不拦截
|
||||
}
|
||||
|
||||
// 获取 Cache-Control 用于判断是否禁用缓存
|
||||
const cacheControl = rOptions.headers['cache-control']
|
||||
if (cacheControl && (cacheControl.indexOf('no-cache') >= 0 || cacheControl.indexOf('no-store') >= 0)) {
|
||||
return // 禁用缓存,跳过当前拦截器
|
||||
}
|
||||
// 获取 Pragma 用于判断是否禁用缓存
|
||||
const pragma = rOptions.headers.pragma
|
||||
if (pragma && (pragma.indexOf('no-cache') >= 0 || pragma.indexOf('no-store') >= 0)) {
|
||||
return // 禁用缓存,跳过当前拦截
|
||||
}
|
||||
|
||||
// 最近编辑时间
|
||||
const lastModifiedTime = getLastModifiedTimeFromIfModifiedSince(rOptions, log)
|
||||
if (lastModifiedTime == null) {
|
||||
return // 没有 lastModified,不拦截
|
||||
}
|
||||
|
||||
// 获取maxAge配置
|
||||
const maxAge = getMaxAge(interceptOpt)
|
||||
// 判断缓存是否已过期
|
||||
const passTime = Date.now() - lastModifiedTime
|
||||
if (passTime > maxAge * 1000) {
|
||||
return // 缓存已过期,不拦截
|
||||
}
|
||||
|
||||
// 缓存未过期,直接拦截请求并响应304
|
||||
res.writeHead(304, {
|
||||
'Dev-Sidecar-Interceptor': 'cacheReq'
|
||||
})
|
||||
res.end()
|
||||
|
||||
return true
|
||||
},
|
||||
is (interceptOpt) {
|
||||
const maxAge = getMaxAge(interceptOpt)
|
||||
return maxAge != null && maxAge > 0
|
||||
},
|
||||
getMaxAge
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
requestIntercept (context, interceptOpt) {
|
||||
const { rOptions, log } = context
|
||||
if (interceptOpt.sni != null) {
|
||||
rOptions.servername = interceptOpt.sni
|
||||
log.info('sni intercept: sni replace servername:', rOptions.hostname, '➜', rOptions.servername)
|
||||
}
|
||||
return true
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return !!interceptOpt.sni && !interceptOpt.proxy // proxy生效时,sni不需要生效,因为proxy中也会使用sni覆盖 rOptions.servername
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
const cacheReq = require('../req/cacheReq')
|
||||
|
||||
module.exports = {
|
||||
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
|
||||
const { rOptions, log } = context
|
||||
|
||||
// 只有GET请求,且响应码为2xx时才进行缓存
|
||||
if (rOptions.method !== 'GET' || proxyRes.statusCode < 200 || proxyRes.statusCode >= 300) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取maxAge配置
|
||||
let maxAge = cacheReq.getMaxAge(interceptOpt)
|
||||
// public 或 private
|
||||
const cacheControlType = (interceptOpt.cacheControlType || 'public') + ', '
|
||||
// immutable属性
|
||||
const cacheImmutable = interceptOpt.cacheImmutable !== false ? ', immutable' : ''
|
||||
|
||||
// 获取原响应头中的cache-control、last-modified、expires
|
||||
const originalHeaders = {
|
||||
cacheControl: null,
|
||||
lastModified: null,
|
||||
expires: null,
|
||||
etag: null
|
||||
}
|
||||
for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
|
||||
// 尝试修改rawHeaders中的cache-control、last-modified、expires
|
||||
if (proxyRes.rawHeaders[i].toLowerCase() === 'cache-control') {
|
||||
originalHeaders.cacheControl = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
|
||||
} else if (proxyRes.rawHeaders[i].toLowerCase() === 'last-modified') {
|
||||
originalHeaders.lastModified = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
|
||||
} else if (proxyRes.rawHeaders[i].toLowerCase() === 'expires') {
|
||||
originalHeaders.expires = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
|
||||
} else if (proxyRes.rawHeaders[i].toLowerCase() === 'etag') {
|
||||
originalHeaders.etag = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
|
||||
}
|
||||
|
||||
// 如果已经设置了cache-control、last-modified、expires,则直接break
|
||||
if (originalHeaders.cacheControl && originalHeaders.lastModified && originalHeaders.expires && originalHeaders.etag) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 判断原max-age是否大于新max-age
|
||||
if (originalHeaders.cacheControl) {
|
||||
const maxAgeMatch = originalHeaders.cacheControl.value.match(/max-age=(\d+)/)
|
||||
if (maxAgeMatch && maxAgeMatch[1] > maxAge) {
|
||||
if (interceptOpt.cacheImmutable !== false && originalHeaders.cacheControl.value.indexOf('immutable') < 0) {
|
||||
maxAge = maxAgeMatch[1]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 替换用的头信息
|
||||
const now = new Date()
|
||||
const replaceHeaders = {
|
||||
cacheControl: `${cacheControlType}max-age=${maxAge + 1}${cacheImmutable}`,
|
||||
lastModified: now.toUTCString(),
|
||||
expires: new Date(now.getTime() + maxAge * 1000).toUTCString()
|
||||
}
|
||||
// 开始替换
|
||||
// 替换cache-control
|
||||
if (originalHeaders.cacheControl) {
|
||||
proxyRes.rawHeaders[originalHeaders.cacheControl.valueIndex] = replaceHeaders.cacheControl
|
||||
} else {
|
||||
res.setHeader('Cache-Control', replaceHeaders.cacheControl)
|
||||
}
|
||||
// 替换last-modified
|
||||
if (originalHeaders.lastModified) {
|
||||
proxyRes.rawHeaders[originalHeaders.lastModified.valueIndex] = replaceHeaders.lastModified
|
||||
} else {
|
||||
res.setHeader('Last-Modified', replaceHeaders.lastModified)
|
||||
}
|
||||
// 替换expires
|
||||
if (originalHeaders.expires) {
|
||||
proxyRes.rawHeaders[originalHeaders.expires.valueIndex] = replaceHeaders.expires
|
||||
} else {
|
||||
res.setHeader('Expires', replaceHeaders.expires)
|
||||
}
|
||||
|
||||
res.setHeader('Dev-Sidecar-Cache-Response-Interceptor', 'cacheRes:maxAge=' + maxAge)
|
||||
log.info('[cacheRes]', 'maxAge=' + maxAge)
|
||||
},
|
||||
is (interceptOpt) {
|
||||
const maxAge = cacheReq.getMaxAge(interceptOpt)
|
||||
return maxAge != null && maxAge > 0
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
const contextPath = '/____ds_script____/'
|
||||
const monkey = require('../../monkey')
|
||||
const monkey = require('../../../monkey')
|
||||
const CryptoJs = require('crypto-js')
|
||||
function getScript (key, script) {
|
||||
const scriptUrl = contextPath + key
|
|
@ -1,13 +0,0 @@
|
|||
module.exports = {
|
||||
requestIntercept (context, interceptOpt) {
|
||||
const { rOptions } = context
|
||||
if (interceptOpt.sni != null) {
|
||||
rOptions.servername = interceptOpt.sni
|
||||
console.log('sni replace', rOptions.hostname, rOptions.servername)
|
||||
}
|
||||
return true
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return !!interceptOpt.sni
|
||||
}
|
||||
}
|
|
@ -1,9 +1,23 @@
|
|||
const proxy = require('./impl/proxy')
|
||||
const redirect = require('./impl/redirect')
|
||||
const abort = require('./impl/abort')
|
||||
const success = require('./impl/success')
|
||||
const script = require('./impl/script')
|
||||
const sni = require('./impl/sni')
|
||||
const modules = [proxy, redirect, abort, script, success, sni]
|
||||
// request interceptor impls
|
||||
const success = require('./impl/req/success')
|
||||
const redirect = require('./impl/req/redirect')
|
||||
const abort = require('./impl/req/abort')
|
||||
|
||||
module.exports = modules
|
||||
const cacheReq = require('./impl/req/cacheReq')
|
||||
|
||||
const proxy = require('./impl/req/proxy')
|
||||
const sni = require('./impl/req/sni')
|
||||
|
||||
// response interceptor impls
|
||||
const cacheRes = require('./impl/res/cacheRes')
|
||||
const script = require('./impl/res/script')
|
||||
|
||||
module.exports = [
|
||||
// request interceptor impls
|
||||
success, redirect, abort,
|
||||
cacheReq,
|
||||
proxy, sni,
|
||||
|
||||
// response interceptor impls
|
||||
cacheRes, script
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue