Jonatan Nilsson
840f23908e
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
176 lines
4.1 KiB
JavaScript
176 lines
4.1 KiB
JavaScript
import https from 'https'
|
|
import pipe from 'multipipe'
|
|
import { Transform, compose } from 'stream'
|
|
import config from './config.mjs'
|
|
|
|
export default class Proxy {
|
|
constructor(opts = {}) {
|
|
Object.assign(this, {
|
|
agent: opts.agent || new https.Agent({
|
|
keepAlive: true,
|
|
maxSockets: 2,
|
|
keepAliveMsecs: 1000 * 60 * 60,
|
|
secureProtocol: 'TLSv1_2_method',
|
|
rejectUnauthorized: false,
|
|
}),
|
|
cache: new Map(),
|
|
defaultRemap: 'default',
|
|
remap: new Map([
|
|
['default', '/frettatengt/serstok-malefni/taktu-tvaer/']
|
|
])
|
|
})
|
|
}
|
|
|
|
register(server) {
|
|
server.flaska.on404(this.proxy.bind(this))
|
|
}
|
|
|
|
getTime(time) {
|
|
if (!time) return 60
|
|
let m = time.match(/max-age=(\d+)/)
|
|
if (!m) return 60
|
|
return Number(m[1]) || 60
|
|
}
|
|
|
|
getVaryKey(req, vary) {
|
|
if (!vary) {
|
|
return 'null'
|
|
}
|
|
let out = []
|
|
for (let v of vary.split(',')) {
|
|
out.push(req.headers[v.trim().toLowerCase()] || 'null')
|
|
}
|
|
return out.join(', ')
|
|
}
|
|
|
|
cleanHeaders(headers) {
|
|
var set = new Set()
|
|
for (let key of Object.keys(headers)) {
|
|
if (set.has(key.toLowerCase())) {
|
|
delete headers[key]
|
|
} else {
|
|
set.add(key)
|
|
}
|
|
}
|
|
return headers
|
|
}
|
|
|
|
cloneHeaders(headers) {
|
|
var out = {}
|
|
for (let key of Object.keys(headers)) {
|
|
if (key !== 'content-length' && key !== 'content-type') {
|
|
out[key] = headers[key]
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
proxyCache(req, path, time, res) {
|
|
let cache = this.cache.get(path)
|
|
if (!cache) {
|
|
this.cache.set(path, cache = {
|
|
path: path,
|
|
time: this.getTime(time),
|
|
created: new Date(),
|
|
status: res.statusCode,
|
|
type: res.headers['content-type'],
|
|
vary: (res.headers['vary'] || '').toLowerCase(),
|
|
data: new Map()
|
|
})
|
|
}
|
|
|
|
let varyKey = this.getVaryKey(req, cache.vary)
|
|
|
|
if (cache.data.has(varyKey)) return res
|
|
|
|
let item = {
|
|
headers: this.cleanHeaders(res.headers),
|
|
data: [],
|
|
done: false,
|
|
}
|
|
cache.data.set(varyKey, item)
|
|
|
|
const capture = new Transform({
|
|
decodeStrings: false,
|
|
destroy(err, cb) {
|
|
if (!err) {
|
|
if (item.data.length) {
|
|
item.data = Buffer.concat(item.data)
|
|
item.done = true
|
|
} else {
|
|
cache.data.delete(varyKey, item)
|
|
}
|
|
}
|
|
cb(err)
|
|
},
|
|
|
|
transform(chunk, encoding, callback) {
|
|
item.data.push(chunk)
|
|
callback(null, chunk);
|
|
},
|
|
});
|
|
|
|
return pipe(res, capture)
|
|
}
|
|
|
|
getCache(req, path) {
|
|
let cache = this.cache.get(path)
|
|
if (!cache) return null
|
|
if ((new Date() - cache.created) / 1000 > cache.time) {
|
|
this.cache.delete(path)
|
|
return null
|
|
}
|
|
let varyKey = this.getVaryKey(req, cache.vary)
|
|
let data = cache.data.get(varyKey)
|
|
if (!data) return null
|
|
if (!data.done) return null
|
|
|
|
return {
|
|
varyKey: varyKey,
|
|
res: cache,
|
|
data: cache.data.get(varyKey)
|
|
}
|
|
}
|
|
|
|
async proxy(ctx) {
|
|
let url = ctx.req.url
|
|
if (!url.startsWith('/_')) {
|
|
url = this.remap.get(ctx.req.headers['host'])
|
|
if (!url) {
|
|
url = this.remap.get(this.defaultRemap)
|
|
}
|
|
url += ctx.req.url.slice(1)
|
|
}
|
|
let cache = this.getCache(ctx.req, url)
|
|
|
|
if (cache) {
|
|
ctx.status = cache.res.status
|
|
ctx.type = cache.res.type
|
|
ctx.body = cache.data.data
|
|
ctx.headers = this.cloneHeaders(cache.data.headers)
|
|
console.log('found cache', cache)
|
|
return
|
|
}
|
|
|
|
ctx.req.headers.host = 'www.sa.is'
|
|
|
|
var res = await new Promise((res, rej) => {
|
|
var req = https.request({
|
|
hostname: 'www.sa.is',
|
|
path: url,
|
|
method: ctx.req.method,
|
|
headers: ctx.req.headers,
|
|
agent: this.agent,
|
|
}, results => {
|
|
res(results)
|
|
})
|
|
req.on('error', rej)
|
|
req.end()
|
|
})
|
|
|
|
ctx.status = res.statusCode
|
|
ctx.headers = this.cloneHeaders(res.headers)
|
|
ctx.type = res.headers['content-type']
|
|
ctx.body = this.proxyCache(ctx.req, url, ctx.headers['cache-control'], res)
|
|
}
|
|
}
|