Browse Source

feature: 新增缓存拦截器,并为github站点添加5类静态资源的缓存拦截配置。

pull/279/head
WangLiang/王良 8 months ago committed by GitHub
parent
commit
cf4aa83bd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      README.md
  2. 30
      packages/core/src/config/index.js
  3. 0
      packages/mitmproxy/src/lib/interceptor/impl/req/abort.js
  4. 103
      packages/mitmproxy/src/lib/interceptor/impl/req/cacheReq.js
  5. 0
      packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js
  6. 0
      packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js
  7. 13
      packages/mitmproxy/src/lib/interceptor/impl/req/sni.js
  8. 0
      packages/mitmproxy/src/lib/interceptor/impl/req/success.js
  9. 90
      packages/mitmproxy/src/lib/interceptor/impl/res/cacheRes.js
  10. 2
      packages/mitmproxy/src/lib/interceptor/impl/res/script.js
  11. 13
      packages/mitmproxy/src/lib/interceptor/impl/sni.js
  12. 32
      packages/mitmproxy/src/lib/interceptor/index.js

9
README.md

@ -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'
},
'.*':{

30
packages/core/src/config/index.js

@ -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
packages/mitmproxy/src/lib/interceptor/impl/abort.js → packages/mitmproxy/src/lib/interceptor/impl/req/abort.js

103
packages/mitmproxy/src/lib/interceptor/impl/req/cacheReq.js

@ -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
packages/mitmproxy/src/lib/interceptor/impl/proxy.js → packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js

0
packages/mitmproxy/src/lib/interceptor/impl/redirect.js → packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js

13
packages/mitmproxy/src/lib/interceptor/impl/req/sni.js

@ -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
packages/mitmproxy/src/lib/interceptor/impl/success.js → packages/mitmproxy/src/lib/interceptor/impl/req/success.js

90
packages/mitmproxy/src/lib/interceptor/impl/res/cacheRes.js

@ -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
}
}

2
packages/mitmproxy/src/lib/interceptor/impl/script.js → packages/mitmproxy/src/lib/interceptor/impl/res/script.js

@ -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

13
packages/mitmproxy/src/lib/interceptor/impl/sni.js

@ -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
}
}

32
packages/mitmproxy/src/lib/interceptor/index.js

@ -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]
module.exports = modules
// request interceptor impls
const success = require('./impl/req/success')
const redirect = require('./impl/req/redirect')
const abort = require('./impl/req/abort')
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…
Cancel
Save