Skip to content

Commit

Permalink
feat: setup clustering (#530)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aviortheking authored Aug 30, 2024
1 parent 5928a1d commit ae6ed3c
Showing 1 changed file with 97 additions and 76 deletions.
173 changes: 97 additions & 76 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,106 @@
import express, { type Response } from 'express'
import jsonEndpoints from './V2/endpoints/jsonEndpoints'
import graphql from './V2/graphql'
import cluster from 'node:cluster'
import { availableParallelism } from "node:os"
import { Errors, sendError } from './libs/Errors'
import status from './status'
import jsonEndpoints from './V2/endpoints/jsonEndpoints'
import graphql from './V2/graphql'

// Current API version
const VERSION = 2
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} is running`);

const parallelism = availableParallelism()
console.log(`creating ${parallelism} workers...`)
for (let i = 0; i < parallelism; i++) {
cluster.fork();
}

cluster.on('online', (worker) => {
console.log('Worker', worker.id, 'online')
})

cluster.on("exit", (worker, code, _signal) => {
console.log(`Worker ${worker.id} exited with code ${code}`);
})
console.log('🚀 Server ready at localhost:3000');
} else {

// Current API version
const VERSION = 2

// Init Express server
const server = express()

// Route logging / Error logging for debugging
server.use((req, res, next) => {
const now = new Date()
// Date of request User-Agent 32 first chars request Method
const prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${req.headers['user-agent']?.slice(0, 32).padEnd(32)} ${req.method.toUpperCase().padEnd(7)}`

const url = new URL(req.url, `http://${req.headers.host}`)
const fullURL = url.toString()
const path = fullURL.slice(fullURL.indexOf(url.pathname, fullURL.indexOf(url.host)))

// HTTP Status Code Time to run request path of request
console.log(`${prefix} ${''.padStart(5, ' ')} ${''.padStart(7, ' ')} ${path}`)

res.on('close', () => {
console.log(`${prefix} \x1b[34m[${'statusCode' in res ? res.statusCode : '???'}]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`)
})
res.on('error', (err) => {
// log the request
console.log(`${prefix} \x1b[34m[500]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`)

// add a full line dash to not miss it
const columns = (process?.stdout?.columns ?? 32) - 7
const dashes = ''.padEnd(columns / 2, '-')

// colorize the lines to make sur to not miss it
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)
console.error(err)
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)
})

next()
})

// Set CORS global headers
server.use((req, res, next) => {
res
.setHeader('Access-Control-Allow-Origin', '*')
.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
.setHeader('Access-Control-Allow-Headers', 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range')
.setHeader('Access-Control-Expose-Headers', 'Content-Length,Content-Range')

if (req.method.toUpperCase() === 'OPTIONS') {
res.status(200).send('ok')
return
}
next()
})

server.get('/', (_, res) => {
res.redirect('https://www.tcgdex.dev/?ref=api.tcgdex.net')
})

// Init Express server
const server = express()
server.use(express.static('./public'))

// Route logging / Error logging for debugging
server.use((req, res, next) => {
const now = new Date()
// Date of request User-Agent 32 first chars request Method
const prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${req.headers['user-agent']?.slice(0, 32).padEnd(32)} ${req.method.toUpperCase().padEnd(7)}`
// Setup GraphQL
server.use(`/v${VERSION}/graphql`, graphql)

const url = new URL(req.url, `http://${req.headers.host}`)
const fullURL = url.toString()
const path = fullURL.slice(fullURL.indexOf(url.pathname, fullURL.indexOf(url.host)))
// Setup JSON endpoints
server.use(`/v${VERSION}`, jsonEndpoints)

// HTTP Status Code Time to run request path of request
console.log(`${prefix} ${''.padStart(5, ' ')} ${''.padStart(7, ' ')} ${path}`)
// Status page
server.use('/status', status)

res.on('close', () => {
console.log(`${prefix} \x1b[34m[${'statusCode' in res ? res.statusCode : '???'}]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`)
// handle 404 errors
server.use((_, res) => {
sendError(Errors.NOT_FOUND, res)
})
res.on('error', (err) => {
// log the request
console.log(`${prefix} \x1b[34m[500]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`)

// General error handler
server.use((err: Error, _1: unknown, res: Response, _2: unknown) => {
// add a full line dash to not miss it
const columns = (process?.stdout?.columns ?? 32) - 7
const dashes = ''.padEnd(columns / 2, '-')
Expand All @@ -38,60 +109,10 @@ server.use((req, res, next) => {
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)
console.error(err)
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)
})

next()
})

// Set CORS global headers
server.use((req, res, next) => {
res
.setHeader('Access-Control-Allow-Origin', '*')
.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
.setHeader('Access-Control-Allow-Headers', 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range')
.setHeader('Access-Control-Expose-Headers', 'Content-Length,Content-Range')

if (req.method.toUpperCase() === 'OPTIONS') {
res.status(200).send('ok')
return
}
next()
})

server.get('/', (_, res) => {
res.redirect('https://www.tcgdex.dev/?ref=api.tcgdex.net')
})

server.use(express.static('./public'))

// Setup GraphQL
server.use(`/v${VERSION}/graphql`, graphql)

// Setup JSON endpoints
server.use(`/v${VERSION}`, jsonEndpoints)

// Status page
server.use('/status', status)

// handle 404 errors
server.use((_, res) => {
sendError(Errors.NOT_FOUND, res)
})

// General error handler
server.use((err: Error, _1: unknown, res: Response, _2: unknown) => {
// add a full line dash to not miss it
const columns = (process?.stdout?.columns ?? 32) - 7
const dashes = ''.padEnd(columns / 2, '-')

// colorize the lines to make sur to not miss it
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)
console.error(err)
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)

sendError(Errors.GENERAL, res, { err })
})
sendError(Errors.GENERAL, res, { err })
})

// Start server
server.listen(3000)
console.log('🚀 Server ready at localhost:3000');
// Start server
server.listen(3000)
}

0 comments on commit ae6ed3c

Please sign in to comment.