feature: 新增缓存拦截器。
parent
3594c68f7b
commit
3221b75a1b
|
@ -17,12 +17,12 @@ function get () {
|
||||||
const getDefaultConfigBasePath = function () {
|
const getDefaultConfigBasePath = function () {
|
||||||
return get().server.setting.userBasePath
|
return get().server.setting.userBasePath
|
||||||
}
|
}
|
||||||
function _getRemoteSavePath (prefix = '', version = '') {
|
function _getRemoteSavePath (prefix = '') {
|
||||||
const dir = getDefaultConfigBasePath()
|
const dir = getDefaultConfigBasePath()
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir)
|
fs.mkdirSync(dir)
|
||||||
}
|
}
|
||||||
return path.join(dir, prefix + 'remote_config.json' + version)
|
return path.join(dir, prefix + 'remote_config.json5')
|
||||||
}
|
}
|
||||||
function _getConfigPath () {
|
function _getConfigPath () {
|
||||||
const dir = getDefaultConfigBasePath()
|
const dir = getDefaultConfigBasePath()
|
||||||
|
@ -64,7 +64,7 @@ const configApi = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (response && response.statusCode === 200) {
|
if (response && response.statusCode === 200) {
|
||||||
const originalRemoteSavePath = _getRemoteSavePath('original_', '5')
|
const originalRemoteSavePath = _getRemoteSavePath('original_')
|
||||||
fs.writeFileSync(originalRemoteSavePath, body)
|
fs.writeFileSync(originalRemoteSavePath, body)
|
||||||
log.info('保存原来的远程配置文件成功:', originalRemoteSavePath)
|
log.info('保存原来的远程配置文件成功:', originalRemoteSavePath)
|
||||||
|
|
||||||
|
|
|
@ -52,14 +52,14 @@ module.exports = {
|
||||||
intercepts: {
|
intercepts: {
|
||||||
'github.com': {
|
'github.com': {
|
||||||
'/.*/.*/releases/download/': {
|
'/.*/.*/releases/download/': {
|
||||||
redirect: 'download.fastgit.org',
|
redirect: 'gh.api.99988866.xyz/https://github.com',
|
||||||
desc: 'release文件加速下载跳转地址'
|
desc: 'release文件加速下载跳转地址'
|
||||||
},
|
},
|
||||||
'/.*/.*/archive/': {
|
'/.*/.*/archive/': {
|
||||||
redirect: 'download.fastgit.org'
|
redirect: 'gh.api.99988866.xyz/https://github.com'
|
||||||
},
|
},
|
||||||
'/.*/.*/blame/': {
|
'/.*/.*/blame/': {
|
||||||
redirect: 'hub.fastgit.org'
|
redirect: 'gh.api.99988866.xyz/https://github.com'
|
||||||
},
|
},
|
||||||
'^/[^/]+/[^/]+(/releases(/.*)?)?$': {
|
'^/[^/]+/[^/]+(/releases(/.*)?)?$': {
|
||||||
script: [
|
script: [
|
||||||
|
@ -69,15 +69,17 @@ module.exports = {
|
||||||
},
|
},
|
||||||
'/.*': {
|
'/.*': {
|
||||||
proxy: 'github.com',
|
proxy: 'github.com',
|
||||||
// proxy: 'gh.docmirror.top/_proxy',
|
|
||||||
desc: '目前禁掉sni就可以直接访问,如果后续github.com的ip被封锁,只能再走proxy模式',
|
desc: '目前禁掉sni就可以直接访问,如果后续github.com的ip被封锁,只能再走proxy模式',
|
||||||
sni: 'baidu.com'
|
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': {
|
'github-releases.githubusercontent.com': {
|
||||||
'.*': {
|
'.*': {
|
||||||
|
@ -95,6 +97,10 @@ module.exports = {
|
||||||
'.*': {
|
'.*': {
|
||||||
proxy: 'camo.githubusercontent.com',
|
proxy: 'camo.githubusercontent.com',
|
||||||
sni: 'baidu.com'
|
sni: 'baidu.com'
|
||||||
|
},
|
||||||
|
'^[a-zA-Z0-9/]+(\\?.*)?$': {
|
||||||
|
cacheDays: 365,
|
||||||
|
desc: '图片,缓存1年'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'collector.github.com': {
|
'collector.github.com': {
|
||||||
|
@ -116,12 +122,30 @@ module.exports = {
|
||||||
'.*': {
|
'.*': {
|
||||||
proxy: 'user-images.githubusercontent.com',
|
proxy: 'user-images.githubusercontent.com',
|
||||||
sni: 'baidu.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': {
|
'avatars.githubusercontent.com': {
|
||||||
'.*': {
|
'.*': {
|
||||||
proxy: 'avatars.githubusercontent.com',
|
proxy: 'avatars.githubusercontent.com',
|
||||||
sni: 'baidu.com'
|
sni: 'baidu.com'
|
||||||
|
},
|
||||||
|
'^/u/\\d+.*$': {
|
||||||
|
cacheDays: 365,
|
||||||
|
desc: '用户头像,缓存1年'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'api.github.com': {
|
'api.github.com': {
|
||||||
|
|
|
@ -67,6 +67,11 @@ const serverApi = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverConfig.plugin = allConfig.plugin
|
serverConfig.plugin = allConfig.plugin
|
||||||
|
|
||||||
|
if (allConfig.proxy && allConfig.proxy.enabled) {
|
||||||
|
serverConfig.proxy = allConfig.proxy
|
||||||
|
}
|
||||||
|
|
||||||
// fireStatus('ing') // 启动中
|
// fireStatus('ing') // 启动中
|
||||||
const basePath = serverConfig.setting.userBasePath
|
const basePath = serverConfig.setting.userBasePath
|
||||||
const runningConfigPath = path.join(basePath, '/running.json')
|
const runningConfigPath = path.join(basePath, '/running.json')
|
||||||
|
@ -125,13 +130,13 @@ const serverApi = {
|
||||||
// fireStatus('ing')// 关闭中
|
// fireStatus('ing')// 关闭中
|
||||||
server.close((err) => {
|
server.close((err) => {
|
||||||
if (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') {
|
if (err.code === 'ERR_SERVER_NOT_RUNNING') {
|
||||||
log.info('代理服务关闭成功')
|
log.info('代理服务关闭成功')
|
||||||
resolve()
|
resolve()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.info('代理服务关闭失败', err)
|
log.warn('代理服务关闭失败', err)
|
||||||
reject(err)
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
log.info('代理服务关闭成功')
|
log.info('代理服务关闭成功')
|
||||||
|
|
|
@ -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,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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
const contextPath = '/____ds_script____/'
|
const contextPath = '/____ds_script____/'
|
||||||
const monkey = require('../../monkey')
|
const monkey = require('../../../monkey')
|
||||||
const CryptoJs = require('crypto-js')
|
const CryptoJs = require('crypto-js')
|
||||||
function getScript (key, script) {
|
function getScript (key, script) {
|
||||||
const scriptUrl = contextPath + key
|
const scriptUrl = contextPath + key
|
|
@ -1,9 +1,25 @@
|
||||||
const proxy = require('./impl/proxy')
|
// request interceptor impls
|
||||||
const redirect = require('./impl/redirect')
|
const success = require('./impl/req/success')
|
||||||
const abort = require('./impl/abort')
|
const redirect = require('./impl/req/redirect')
|
||||||
const success = require('./impl/success')
|
const abort = require('./impl/req/abort')
|
||||||
const script = require('./impl/script')
|
|
||||||
const sni = require('./impl/sni')
|
const cacheReq = require('./impl/req/cacheReq')
|
||||||
const modules = [proxy, redirect, abort, script, success, sni]
|
|
||||||
|
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
|
module.exports = modules
|
||||||
|
|
Loading…
Reference in New Issue