pixai / server.mjs
ghuser1's picture
Upload 2 files
34e385a verified
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import { createServer } from 'node:http'
import { extname, join, normalize, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const distDir = resolve(__dirname, 'dist')
const port = Number(process.env.PORT || 7860)
const host = process.env.HOST || '0.0.0.0'
const mimeTypes = new Map([
['.html', 'text/html; charset=utf-8'],
['.js', 'text/javascript; charset=utf-8'],
['.css', 'text/css; charset=utf-8'],
['.json', 'application/json; charset=utf-8'],
['.png', 'image/png'],
['.jpg', 'image/jpeg'],
['.jpeg', 'image/jpeg'],
['.ico', 'image/x-icon'],
['.svg', 'image/svg+xml'],
['.webp', 'image/webp'],
['.woff', 'font/woff'],
['.woff2', 'font/woff2']
])
const hopByHopHeaders = new Set([
'accept-encoding',
'connection',
'content-length',
'content-encoding',
'content-md5',
'host',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'te',
'trailer',
'transfer-encoding',
'upgrade'
])
const server = createServer(async (request, response) => {
try {
const requestUrl = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`)
if (requestUrl.pathname === '/api/proxy') {
await proxyApiRequest(request, response, requestUrl)
return
}
await serveStatic(requestUrl.pathname, response)
} catch (error) {
console.error(error)
sendJson(response, 500, { error: 'Internal server error.' })
}
})
server.listen(port, host, () => {
console.log(`PixAI web server listening on http://${host}:${port}`)
})
async function proxyApiRequest(request, response, requestUrl) {
const targetValue = requestUrl.searchParams.get('target')
if (!targetValue) {
sendJson(response, 400, { error: 'Missing target URL.' })
return
}
let targetUrl
try {
targetUrl = new URL(targetValue)
} catch {
sendJson(response, 400, { error: 'Invalid target URL.' })
return
}
if (!['https:', 'http:'].includes(targetUrl.protocol)) {
sendJson(response, 400, { error: 'Unsupported target protocol.' })
return
}
const headers = new Headers()
for (const [key, value] of Object.entries(request.headers)) {
if (hopByHopHeaders.has(key.toLowerCase())) continue
if (Array.isArray(value)) headers.set(key, value.join(', '))
else if (value != null) headers.set(key, value)
}
headers.set('accept-encoding', 'identity')
const upstream = await fetch(targetUrl, {
method: request.method,
headers,
body: request.method === 'GET' || request.method === 'HEAD' ? undefined : request,
duplex: 'half'
})
response.statusCode = upstream.status
for (const [key, value] of upstream.headers) {
if (hopByHopHeaders.has(key.toLowerCase())) continue
response.setHeader(key, value)
}
if (!upstream.body) {
response.end()
return
}
await upstream.body.pipeTo(new WritableStream({
write(chunk) {
response.write(Buffer.from(chunk))
},
close() {
response.end()
},
abort(reason) {
response.destroy(reason)
}
}))
}
async function serveStatic(pathname, response) {
const decodedPath = decodeURIComponent(pathname)
const safePath = normalize(decodedPath).replace(/^(\.\.[/\\])+/, '')
const requestedPath = resolve(join(distDir, safePath))
const requestedStat = requestedPath.startsWith(distDir) ? await stat(requestedPath).catch(() => null) : null
const filePath = requestedStat?.isFile()
? requestedPath
: join(distDir, 'index.html')
const fileStat = await stat(filePath).catch(() => null)
if (!fileStat?.isFile()) {
sendJson(response, 404, { error: 'Build output not found. Run pnpm build first.' })
return
}
response.statusCode = 200
response.setHeader('Content-Type', mimeTypes.get(extname(filePath)) || 'application/octet-stream')
response.setHeader('Cache-Control', filePath.endsWith('index.html') ? 'no-cache' : 'public, max-age=31536000, immutable')
createReadStream(filePath).pipe(response)
}
function sendJson(response, status, payload) {
response.statusCode = status
response.setHeader('Content-Type', 'application/json; charset=utf-8')
response.end(JSON.stringify(payload))
}