Skip to content

Commit

Permalink
refactor(runtime): refactor storage proxy (labring#1714)
Browse files Browse the repository at this point in the history
* refactor(runtime): refact storage proxy
  • Loading branch information
HUAHUAI23 authored Dec 25, 2023
1 parent 74518b9 commit 8315fda
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 69 deletions.
6 changes: 3 additions & 3 deletions cli/src/command/environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ export function command(): Command {
.command('environment')
.alias('env')
.hook('preAction', () => {
checkApplication()
})
checkApplication()
})

cmd
.command('pull')
.description('pull environment variables')
.action(() => {
pull()
})

cmd
.command('push')
.description('push environment variables')
Expand Down
12 changes: 11 additions & 1 deletion runtimes/nodejs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion runtimes/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"nodemailer": "^6.6.3",
"pako": "^2.1.0",
"validator": "^13.7.0",
"ws": "^8.11.0"
"ws": "^8.11.0",
"zlib": "^1.0.5"
},
"devDependencies": {
"@types/cors": "^2.8.13",
Expand Down
177 changes: 113 additions & 64 deletions runtimes/nodejs/src/storage-server.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,133 @@
import express from 'express'
import * as http from 'http'
import Config from './config'
import { logger } from './support/logger'
import './support/cloud-sdk'
import { WebsiteHostingChangeStream } from './support/database-change-stream/website-hosting-change-stream'
import proxy from 'express-http-proxy'
import axios from 'axios'
import cors from 'cors'

const app = express()

app.use(
cors({
origin: true,
methods: '*',
exposedHeaders: '*',
credentials: true,
}),
)
import axios, { AxiosError } from 'axios'

const tryPath = (bucket: string, path: string) => {
const testPaths = path.endsWith('/')
? [path + 'index.html', '/index.html']
: [path, path + '/index.html', '/index.html']
return testPaths.map((v) => `/${bucket}${v}`)
const tryPath = (bucket: string, path: string): string[] => {
return path.endsWith('/')
? [`/${bucket}${path}index.html`, `/${bucket}/index.html`]
: [
`/${bucket}${path}`,
`/${bucket}${path}/index.html`,
`/${bucket}/index.html`,
]
}

app.use(
proxy(Config.OSS_INTERNAL_ENDPOINT, {
preserveHostHdr: true,
parseReqBody: false,
proxyReqOptDecorator: function (proxyReqOpts, srcReq) {
// patch for
if ('content-length' in srcReq.headers) {
proxyReqOpts.headers['content-length'] =
srcReq.headers['content-length']
}
if ('connection' in srcReq.headers) {
proxyReqOpts.headers['connection'] = srcReq.headers['connection']
}
return proxyReqOpts
},
proxyReqPathResolver: async function (req) {
// check if is website hosting
const websiteHosting = WebsiteHostingChangeStream.websiteHosting.find(
(item) => req.hostname === item.domain,
)
if (!websiteHosting) {
return req.url
const websiteHostingPathHandler = async (
host: string,
url: string,
): Promise<string> => {
const websiteHosting = WebsiteHostingChangeStream.websiteHosting.find(
(item) => host === item.domain,
)
if (!websiteHosting) {
return url
}

const minioUrl = new URL(url, Config.OSS_INTERNAL_ENDPOINT)
const paths = tryPath(websiteHosting.bucketName, url)

for (const path of paths) {
minioUrl.pathname = path
try {
await axios.head(minioUrl.toString())
return minioUrl.pathname + minioUrl.search
} catch (err) {
if ((err as AxiosError).response?.status !== 404) {
break
}
}
}
return url // If all paths are unavailable, the original URL is returned.
}

const storageServer = http.createServer(
async (req: http.IncomingMessage, res: http.ServerResponse) => {
const headers = {
'Access-Control-Allow-Origin': req.headers.origin || '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Credentials': 'true',
}

// req.url doesn't have hostname
const minioUrl = new URL(req.url, Config.OSS_INTERNAL_ENDPOINT)
const paths = tryPath(websiteHosting.bucketName, req.path)
const getUrl = () => minioUrl.pathname + minioUrl.search
if (req.method === 'OPTIONS') {
res.writeHead(204, headers)
res.end()
return
}
try {
const proxyReqUrl = new URL(Config.OSS_INTERNAL_ENDPOINT)
const path = await websiteHostingPathHandler(req.headers.host, req.url)

for (const [idx, path] of paths.entries()) {
minioUrl.pathname = path
const proxyReq = http.request({
host: proxyReqUrl.hostname,
port: proxyReqUrl.port,
headers: req.headers,
method: req.method,
path: path,
})

if (idx === paths.length - 1) {
return getUrl()
proxyReq.on('response', (proxyRes: http.IncomingMessage) => {
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers)
proxyRes.pipe(res)
})

proxyReq.on('error', (err) => {
req.emit('close')
proxyReq.emit('close')
logger.error('Proxy request error:', err)
if (!res.headersSent) {
res.writeHead(500)
res.end('Internal Server Error')
}
})

proxyReq.on('close', () => {
proxyReq.removeAllListeners()
proxyReq.destroy()
})

try {
await axios.head(minioUrl.toString())
return getUrl()
} catch (err) {
if (err.response.status === 404) {
continue
}
return getUrl()
req.on('aborted', () => {
proxyReq.emit('close')
req.emit('close')
if (!res.headersSent) {
res.writeHead(504)
}
res.end()
})

req.on('close', () => {
req.removeAllListeners()
req.destroy()
})

req.on('error', (err) => {
req.emit('close')
proxyReq.emit('close')
logger.error('Source request error:', err)
if (!res.headersSent) {
res.writeHead(500)
res.end('Internal Server Error')
}
})

req.pipe(proxyReq)
} catch (err) {
logger.error('Error handling request:', err)
if (!res.headersSent) {
res.writeHead(500)
res.end('Internal Server Error')
}
},
}),
}
},
)

const storageServer = app.listen(Config.STORAGE_PORT, () =>
storageServer.listen(Config.STORAGE_PORT, () => {
logger.info(
`storage server ${process.pid} listened on ${Config.STORAGE_PORT}`,
),
)
`Storage server ${process.pid} listened on ${Config.STORAGE_PORT}`,
)
})

export default storageServer

0 comments on commit 8315fda

Please sign in to comment.