feature: 新增缓存拦截器。

pull/278/head
王良 2024-03-26 09:31:24 +08:00
parent 3594c68f7b
commit 3221b75a1b
12 changed files with 259 additions and 22 deletions

View File

@ -17,12 +17,12 @@ function get () {
const getDefaultConfigBasePath = function () {
return get().server.setting.userBasePath
}
function _getRemoteSavePath (prefix = '', version = '') {
function _getRemoteSavePath (prefix = '') {
const dir = getDefaultConfigBasePath()
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
return path.join(dir, prefix + 'remote_config.json' + version)
return path.join(dir, prefix + 'remote_config.json5')
}
function _getConfigPath () {
const dir = getDefaultConfigBasePath()
@ -64,7 +64,7 @@ const configApi = {
return
}
if (response && response.statusCode === 200) {
const originalRemoteSavePath = _getRemoteSavePath('original_', '5')
const originalRemoteSavePath = _getRemoteSavePath('original_')
fs.writeFileSync(originalRemoteSavePath, body)
log.info('保存原来的远程配置文件成功:', originalRemoteSavePath)

View File

@ -52,14 +52,14 @@ module.exports = {
intercepts: {
'github.com': {
'/.*/.*/releases/download/': {
redirect: 'download.fastgit.org',
redirect: 'gh.api.99988866.xyz/https://github.com',
desc: 'release文件加速下载跳转地址'
},
'/.*/.*/archive/': {
redirect: 'download.fastgit.org'
redirect: 'gh.api.99988866.xyz/https://github.com'
},
'/.*/.*/blame/': {
redirect: 'hub.fastgit.org'
redirect: 'gh.api.99988866.xyz/https://github.com'
},
'^/[^/]+/[^/]+(/releases(/.*)?)?$': {
script: [
@ -69,15 +69,17 @@ module.exports = {
},
'/.*': {
proxy: 'github.com',
// proxy: 'gh.docmirror.top/_proxy',
desc: '目前禁掉sni就可以直接访问如果后续github.com的ip被封锁只能再走proxy模式',
sni: 'baidu.com'
},
'/fluidicon.png': {
cacheDays: 365,
desc: 'Github那只猫的图片缓存1年'
},
'^/[^/]+/[^/]+/pull/\\d+/open_with_menu.*$': {
cacheDays: 1,
desc: 'PR详情页标题右边那个Code按钮的HTML代理请求地址感觉上应该可以缓存。暂时先设置为缓存1天'
}
// '/.*/.*/raw11/': {
// replace: '(.+)\\/raw\\/(.+)',
// proxy: 'raw.fastgit.org$1/$2',
// sni: 'baidu.com'
// }
},
'github-releases.githubusercontent.com': {
'.*': {
@ -95,6 +97,10 @@ module.exports = {
'.*': {
proxy: 'camo.githubusercontent.com',
sni: 'baidu.com'
},
'^[a-zA-Z0-9/]+(\\?.*)?$': {
cacheDays: 365,
desc: '图片缓存1年'
}
},
'collector.github.com': {
@ -116,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': {

View File

@ -67,6 +67,11 @@ const serverApi = {
}
}
serverConfig.plugin = allConfig.plugin
if (allConfig.proxy && allConfig.proxy.enabled) {
serverConfig.proxy = allConfig.proxy
}
// fireStatus('ing') // 启动中
const basePath = serverConfig.setting.userBasePath
const runningConfigPath = path.join(basePath, '/running.json')
@ -125,13 +130,13 @@ const serverApi = {
// fireStatus('ing')// 关闭中
server.close((err) => {
if (err) {
log.info('close error', err, ',', err.code, ',', err.message, ',', err.errno)
log.warn('close error', err, ',', err.code, ',', err.message, ',', err.errno)
if (err.code === 'ERR_SERVER_NOT_RUNNING') {
log.info('代理服务关闭成功')
resolve()
return
}
log.info('代理服务关闭失败', err)
log.warn('代理服务关闭失败', err)
reject(err)
} else {
log.info('代理服务关闭成功')

View File

@ -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 // 601分钟
}
// 小时
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
}

View File

@ -0,0 +1,89 @@
const cacheReq = require('../req/cacheReq')
module.exports = {
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions } = 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)
},
is (interceptOpt) {
const maxAge = cacheReq.getMaxAge(interceptOpt)
return maxAge != null && maxAge > 0
}
}

View File

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

View File

@ -1,9 +1,25 @@
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')
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')
const modules = [
// request interceptor impls
success, redirect, abort,
cacheReq,
proxy, sni,
// response interceptor impls
cacheRes, script
]
module.exports = modules