server-test.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. const express = require('express');
  2. const https = require('https');
  3. const http = require('http');
  4. const tls = require('tls');
  5. const fs = require('fs');
  6. const path = require('path');
  7. const url = require('url');
  8. const { execFile } = require('child_process');
  9. require('dotenv').config({ path: path.join(__dirname, '.env') });
  10. const app = express();
  11. // Parse ALLOWED_DOMAINS environment variable as an array
  12. const allowedDomains = process.env.ALLOWED_DOMAINS ? process.env.ALLOWED_DOMAINS.split(',') : [];
  13. // Middleware to parse JSON request bodies
  14. app.use(express.json());
  15. // Helper function to get SSL options based on hostname
  16. const getSSLOptions = (hostname) => {
  17. const sslPrefix = process.env.SSL_PREFIX || '/etc/letsencrypt/live/';
  18. try {
  19. return {
  20. key: fs.readFileSync(path.join(sslPrefix, hostname, 'privkey.pem')),
  21. cert: fs.readFileSync(path.join(sslPrefix, hostname, 'fullchain.pem')),
  22. };
  23. } catch (error) {
  24. // Fallback to default certificates if domain-specific ones aren't found
  25. console.warn(`Could not load SSL certificates for ${hostname}, using defaults`);
  26. return {
  27. key: fs.readFileSync(process.env.SSL_KEY_FILE),
  28. cert: fs.readFileSync(process.env.SSL_CRT_FILE),
  29. };
  30. }
  31. };
  32. // Redirect HTTP to HTTPS
  33. app.use((req, res, next) => {
  34. if (!req.secure && req.headers['x-forwarded-proto'] !== 'https') {
  35. return res.redirect(`https://${req.headers.host}${req.url}`);
  36. }
  37. next();
  38. });
  39. const forwardRequest = (req, res) => {
  40. // Using named wildcard parameter
  41. const npxRoute = req.params.route;
  42. if (!npxRoute) {
  43. return res.status(400).json({ error: 'Bad Request: No dynamic route found' });
  44. }
  45. const parsedUrl = url.parse(req.url);
  46. const requestOptions = {
  47. hostname: 'localhost',
  48. port: 3000,
  49. path: `/api/v1/${npxRoute}${parsedUrl.search || ''}`, // Include query string
  50. method: req.method,
  51. headers: { ...req.headers },
  52. };
  53. let jsonPayload = null;
  54. if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
  55. jsonPayload = JSON.stringify(req.body);
  56. requestOptions.headers['Content-Type'] = 'application/json';
  57. requestOptions.headers['Content-Length'] = Buffer.byteLength(jsonPayload);
  58. }
  59. const proxyReq = http.request(requestOptions, (proxyRes) => {
  60. res.writeHead(proxyRes.statusCode, proxyRes.headers);
  61. proxyRes.pipe(res);
  62. });
  63. proxyReq.on('error', (error) => {
  64. console.error(`Error forwarding request: ${error.message}`);
  65. res.status(500).json({ error: 'Internal Server Error' });
  66. });
  67. if (jsonPayload) {
  68. proxyReq.write(jsonPayload);
  69. }
  70. proxyReq.end();
  71. };
  72. // Handle /api/* route forwarding with correct wildcard syntax
  73. app.use('/api/v1/*route', (req, res) => {
  74. forwardRequest(req, res);
  75. });
  76. // Function for direct PHP execution
  77. const executePhpDirectly = (phpFilePath, domainPath, req, res) => {
  78. // Create environment variables that PHP might expect
  79. const env = {
  80. ...process.env,
  81. DOCUMENT_ROOT: domainPath,
  82. SCRIPT_FILENAME: phpFilePath,
  83. REQUEST_URI: req.url,
  84. QUERY_STRING: url.parse(req.url).query || '',
  85. REQUEST_METHOD: req.method,
  86. HTTP_HOST: req.headers.host,
  87. REMOTE_ADDR: req.ip,
  88. SERVER_NAME: req.headers.host.split(':')[0],
  89. // Additional variables that PHP might expect
  90. GATEWAY_INTERFACE: 'CGI/1.1',
  91. SERVER_PROTOCOL: 'HTTP/1.1',
  92. // Pass HTTP headers as environment variables
  93. ...Object.entries(req.headers).reduce((env, [key, value]) => {
  94. env[`HTTP_${key.toUpperCase().replace(/-/g, '_')}`] = value;
  95. return env;
  96. }, {})
  97. };
  98. execFile('php', [phpFilePath], { env }, (error, stdout, stderr) => {
  99. if (error) {
  100. console.error(`PHP execution error: ${error.message}`);
  101. return res.status(500).send(`PHP Execution Error: ${error.message}`);
  102. }
  103. if (stderr) {
  104. console.error(`PHP stderr output: ${stderr}`);
  105. }
  106. // Set Content-Type to HTML for proper rendering
  107. res.setHeader('Content-Type', 'text/html; charset=utf-8');
  108. res.send(stdout);
  109. });
  110. };
  111. // Helper function to check for index files
  112. const findIndexFile = (directoryPath) => {
  113. // Common index file types to check for
  114. const indexFiles = ['index.html', 'index.htm', 'index.php'];
  115. for (const indexFile of indexFiles) {
  116. const filePath = path.join(directoryPath, indexFile);
  117. if (fs.existsSync(filePath)) {
  118. return { path: filePath, type: indexFile.endsWith('.php') ? 'php' : 'html' };
  119. }
  120. }
  121. return null;
  122. };
  123. // Handle PHP for all domains (both xyz and non-xyz)
  124. app.use((req, res, next) => {
  125. const host = req.headers.host.split(':')[0]; // Remove port if present
  126. const domainPath = path.join('/var/www', host);
  127. // Make sure the domain directory exists
  128. if (!fs.existsSync(domainPath)) {
  129. return next(); // Skip to next middleware if domain directory doesn't exist
  130. }
  131. // Handle direct PHP file requests
  132. if (req.path.endsWith('.php')) {
  133. const phpFilePath = path.join(domainPath, req.path);
  134. if (fs.existsSync(phpFilePath)) {
  135. return executePhpDirectly(phpFilePath, domainPath, req, res);
  136. }
  137. }
  138. // Handle directory requests - check for index files
  139. if (req.path === '/' || req.path.endsWith('/')) {
  140. // First, check for index files in domain root
  141. const indexResult = findIndexFile(path.join(domainPath, req.path));
  142. if (indexResult) {
  143. if (indexResult.type === 'php') {
  144. return executePhpDirectly(indexResult.path, domainPath, req, res);
  145. } else {
  146. return res.sendFile(indexResult.path);
  147. }
  148. }
  149. }
  150. next();
  151. });
  152. // Serve static files from domain root first for all domains
  153. app.use((req, res, next) => {
  154. const host = req.headers.host.split(':')[0]; // Remove port if present
  155. const domainPath = path.join('/var/www', host);
  156. if (!fs.existsSync(domainPath)) {
  157. return next();
  158. }
  159. // Try to serve files from the domain's root directory
  160. express.static(domainPath)(req, res, (err) => {
  161. // Continue to next middleware if file not found
  162. next();
  163. });
  164. });
  165. // Serve static files from build directory as fallback for non-xyz domains
  166. app.use((req, res, next) => {
  167. const host = req.headers.host.split(':')[0]; // Remove port if present
  168. // Handle all domains (both xyz and non-xyz)
  169. const domainPath = path.join('/var/www', host);
  170. const buildPath = path.join(domainPath, 'build');
  171. if (fs.existsSync(buildPath)) {
  172. // Serve static files from build directory
  173. express.static(buildPath)(req, res, next);
  174. } else {
  175. next();
  176. }
  177. });
  178. // Catch-all route handler for all domains with correct wildcard syntax
  179. app.get('*wildcard', (req, res) => {
  180. const host = req.headers.host.split(':')[0]; // Remove port if present
  181. const domainPath = path.join('/var/www', host);
  182. // First, check for index.html in domain root
  183. const rootIndexPath = path.join(domainPath, 'index.html');
  184. if (fs.existsSync(rootIndexPath)) {
  185. return res.sendFile(rootIndexPath);
  186. }
  187. // Then, check build folder
  188. const buildPath = path.join(domainPath, 'build');
  189. if (fs.existsSync(buildPath)) {
  190. const buildIndexPath = path.join(buildPath, 'index.html');
  191. if (fs.existsSync(buildIndexPath)) {
  192. return res.sendFile(buildIndexPath);
  193. }
  194. }
  195. // No matching file found
  196. res.status(404).send('Not Found');
  197. });
  198. // Create HTTPS server with SNI support for multiple domains
  199. const server = https.createServer({
  200. SNICallback: (hostname, cb) => {
  201. const secureContext = tls.createSecureContext(getSSLOptions(hostname));
  202. if (cb) {
  203. cb(null, secureContext);
  204. } else {
  205. return secureContext;
  206. }
  207. }
  208. }, app);
  209. server.listen(443, '0.0.0.0', () => {
  210. console.log(`Server running on https://0.0.0.0:443`);
  211. });
  212. // Also create a HTTP server that redirects to HTTPS
  213. http.createServer((req, res) => {
  214. res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
  215. res.end();
  216. }).listen(80, '0.0.0.0', () => {
  217. console.log('HTTP to HTTPS redirect server running on port 80');
  218. });