Skip to content

Commit

Permalink
refactor stream-metadata server startup to improve startup orchestrat…
Browse files Browse the repository at this point in the history
…ion, and fail fast if misconfigured (#771)

Co-authored-by: Kerem Kazan <[email protected]>
  • Loading branch information
tak-hntlabs and mechanical-turk authored Aug 15, 2024
1 parent c46e439 commit 516fa1b
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 79 deletions.
6 changes: 3 additions & 3 deletions packages/stream-metadata/.env.local.sample
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Required.
RIVER_ENV=local_multi
RIVER_CHAIN_RPC_URL=http://localhost:8546
# port that the server listens at. e.g. http://localhost:3002
PORT=3002

# IMPORTANT: disable tls cert validation in localhost env ONLY
# in all other environments, comment out this line.
NODE_TLS_REJECT_UNAUTHORIZED=0
# Optional: port that the server listens at. e.g. http://localhost:3002
## default: 443
PORT=3002
121 changes: 45 additions & 76 deletions packages/stream-metadata/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import { handleHealthCheckRequest } from './handleHealthCheckRequest'
import { handleImageRequest } from './handleImageRequest'
import { handleMetadataRequest } from './handleMetadataRequest'

// Set the process title to 'fetch-image' so it can be easily identified
// or killed with `pkill fetch-image`
// Set the process title to 'stream-metadata' so it can be easily identified
// or killed with `pkill stream-metadata`
process.title = 'stream-metadata'

const logger = getLogger('server')

logger.info('config', {
logger.info({
riverEnv: config.riverEnv,
chainId: config.web3Config.river.chainId,
port: config.port,
Expand All @@ -31,52 +31,43 @@ const server = Fastify({
})

async function registerPlugins() {
try {
await server.register(cors, {
origin: '*', // Allow any origin
methods: ['GET'], // Allowed HTTP methods
})
logger.info('CORS registered successfully')
} catch (err) {
logger.error('Error registering CORS', err)
process.exit(1) // Exit the process if registration fails
}
await server.register(cors, {
origin: '*', // Allow any origin
methods: ['GET'], // Allowed HTTP methods
})
logger.info('CORS registered successfully')
}

void registerPlugins()

/*
* Routes
*/
server.get('/health', async (request, reply) => {
logger.info(`GET /health`)
return handleHealthCheckRequest(config, request, reply)
})
function setupRoutes() {
/*
* Routes
*/
server.get('/health', async (request, reply) => {
logger.info(`GET /health`)
return handleHealthCheckRequest(config, request, reply)
})

/*
* Routes
*/
server.get('/space/:spaceAddress', async (request, reply) => {
const { spaceAddress } = request.params as { spaceAddress?: string }
logger.info(`GET /space`, { spaceAddress })
server.get('/space/:spaceAddress', async (request, reply) => {
const { spaceAddress } = request.params as { spaceAddress?: string }
logger.info(`GET /space`, { spaceAddress })
const { protocol, serverAddress } = getServerInfo()
return handleMetadataRequest(request, reply, `${protocol}://${serverAddress}`)
})

const { protocol, serverAddress } = getServerInfo()
return handleMetadataRequest(request, reply, `${protocol}://${serverAddress}`)
})
server.get('/space/:spaceAddress/image', async (request, reply) => {
const { spaceAddress } = request.params as { spaceAddress?: string }
logger.info(`GET /space/../image`, {
spaceAddress,
})

server.get('/space/:spaceAddress/image', async (request, reply) => {
const { spaceAddress } = request.params as { spaceAddress?: string }
logger.info(`GET /space/../image`, {
spaceAddress,
return handleImageRequest(config, request, reply)
})

return handleImageRequest(config, request, reply)
})

// Generic / route to return 404
server.get('/', async (request, reply) => {
return reply.code(404).send('Not found')
})
// Generic / route to return 404
server.get('/', async (request, reply) => {
return reply.code(404).send('Not found')
})
}

/*
* Start the server
Expand All @@ -91,49 +82,27 @@ function getServerInfo() {
return { protocol, serverAddress }
}

// Type guard to check if error has code property
function isAddressInUseError(err: unknown): err is NodeJS.ErrnoException {
return err instanceof Error && 'code' in err && err.code === 'EADDRINUSE'
}

// Function to start the server on the first available port
async function startServer(port: number) {
try {
await server.listen({ port, host: 'localhost' })
const addressInfo = server.server.address()
if (addressInfo && typeof addressInfo === 'object') {
server.log.info(`Server listening on ${addressInfo.address}:${addressInfo.port}`)
}
} catch (err) {
if (isAddressInUseError(err)) {
server.log.warn(`Port ${port} is in use, trying port ${port + 1}`)
await startServer(port + 1) // Try the next port
} else {
server.log.error(err)
process.exit(1)
}
}
}

process.on('SIGTERM', async () => {
try {
await server.close()
logger.info('Server closed gracefully')
process.exit(0)
} catch (err) {
logger.info('Error during server shutdown', err)
logger.error('Error during server shutdown', err)
process.exit(1)
}
})

// Start the server on the port set in the .env, or the next available port
startServer(config.port)
.then(() => {
async function main() {
try {
await registerPlugins()
setupRoutes()
await server.listen({ port: config.port })
logger.info('Server started')
})
.catch((err: unknown) => {
logger.error('Error starting server', {
err,
})
} catch (err) {
logger.error('Error starting server', err)
process.exit(1)
})
}
}

void main()

0 comments on commit 516fa1b

Please sign in to comment.