spa-ssi 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/about.html +13 -0
- package/index.html +9 -0
- package/package.json +1 -1
- package/serve.js +160 -0
- package/test.css +4 -0
package/about.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-16">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Document</title>
|
|
7
|
+
<link rel="stylesheet" href="test.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<p>ssi_server is like Python's SimpleHTTPServer, but with minimal support for <a href="http://en.wikipedia.org/wiki/Server_Side_Includes">Server Side Includes</a> (SSI).</p>
|
|
11
|
+
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/index.html
ADDED
package/package.json
CHANGED
package/serve.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
// simple_http_server.js
|
|
4
|
+
import http from "http";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import url from "url";
|
|
8
|
+
import net from "net";
|
|
9
|
+
|
|
10
|
+
class SimpleHTTPRequestHandler {
|
|
11
|
+
constructor(rootDir = process.cwd(), port = 8000) {
|
|
12
|
+
this.rootDir = rootDir;
|
|
13
|
+
this.port = port;
|
|
14
|
+
this.server = http.createServer(this.handleRequest.bind(this));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Start the server
|
|
18
|
+
serve() {
|
|
19
|
+
this.server.listen(this.port, () => {
|
|
20
|
+
console.log(`Serving HTTP on 0.0.0.0 port ${this.port} (http://localhost:${this.port}/)`);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Main request handler
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param {Request} req
|
|
28
|
+
* @param {http.ServerResponse} res
|
|
29
|
+
*/
|
|
30
|
+
async handleRequest(req, res) {
|
|
31
|
+
try {
|
|
32
|
+
const parsedUrl = url.parse(req.url);
|
|
33
|
+
let pathname = decodeURIComponent(parsedUrl.pathname);
|
|
34
|
+
|
|
35
|
+
// Normalize path to prevent directory traversal
|
|
36
|
+
pathname = path.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
|
|
37
|
+
let filepath = path.join(this.rootDir, pathname);
|
|
38
|
+
|
|
39
|
+
// If directory, append index.html
|
|
40
|
+
let stat;
|
|
41
|
+
try {
|
|
42
|
+
stat = await fs.stat(filepath);
|
|
43
|
+
if (stat.isDirectory()) {
|
|
44
|
+
filepath = path.join(this.rootDir, '/index.html'); //path.join(filepath, "index.html");
|
|
45
|
+
stat = await fs.stat(filepath);
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
stat = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// If requested .html doesn't exist → fallback to root index.html (SPA mode)
|
|
52
|
+
if ((!stat || !stat.isFile()) && pathname.endsWith(".html")) {
|
|
53
|
+
filepath = path.join(this.rootDir, "index.html");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Read file
|
|
57
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
58
|
+
let content = await fs.readFile(filepath);
|
|
59
|
+
|
|
60
|
+
// SSI processing for HTML
|
|
61
|
+
if (ext === ".html") {
|
|
62
|
+
content = await this.processIncludes(content.toString(), path.dirname(filepath));
|
|
63
|
+
content = Buffer.from(content);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Send response
|
|
67
|
+
res.writeHead(200, { "Content-Type": this.getMimeType(ext) });
|
|
68
|
+
res.end(content);
|
|
69
|
+
|
|
70
|
+
} catch (err) {
|
|
71
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
72
|
+
res.end("404 Not Found\n");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Process SSI includes
|
|
78
|
+
* @param {string} html
|
|
79
|
+
* @param {*} currentDir
|
|
80
|
+
* @returns
|
|
81
|
+
*/
|
|
82
|
+
async processIncludes(html, currentDir) {
|
|
83
|
+
const includeRegex = /<!--\s*#include\s+virtual="([^"]+)"\s*-->/g;
|
|
84
|
+
|
|
85
|
+
const tasks = [];
|
|
86
|
+
let match;
|
|
87
|
+
while ((match = includeRegex.exec(html)) !== null) {
|
|
88
|
+
const includePath = path.join(currentDir, match[1]);
|
|
89
|
+
const content = await fs.readFile(includePath, 'utf8');
|
|
90
|
+
html = html.replace(match[0], content);
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
return html;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Basic MIME type mapping
|
|
101
|
+
* @param {string} ext
|
|
102
|
+
* @returns
|
|
103
|
+
*/
|
|
104
|
+
getMimeType(ext) {
|
|
105
|
+
|
|
106
|
+
return types[ext] || "application/octet-stream";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
*
|
|
112
|
+
* @param {number} startingAt
|
|
113
|
+
* @returns
|
|
114
|
+
*/
|
|
115
|
+
function getAvailablePort(startingAt) {
|
|
116
|
+
/**
|
|
117
|
+
*
|
|
118
|
+
* @param {number} currentPort
|
|
119
|
+
* @param {(value: any) => void} cb
|
|
120
|
+
*/
|
|
121
|
+
function getNextAvailablePort(currentPort, cb) {
|
|
122
|
+
const server = net.createServer();
|
|
123
|
+
server.listen(currentPort, () => {
|
|
124
|
+
server.once('close', () => {
|
|
125
|
+
cb(currentPort);
|
|
126
|
+
});
|
|
127
|
+
server.close();
|
|
128
|
+
});
|
|
129
|
+
server.on('error', _ => {
|
|
130
|
+
getNextAvailablePort(++currentPort, cb);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return new Promise(resolve => {
|
|
134
|
+
getNextAvailablePort(startingAt, resolve);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @type {{[key: string]: string}}
|
|
140
|
+
*/
|
|
141
|
+
const types = {
|
|
142
|
+
".html": "text/html",
|
|
143
|
+
".css": "text/css",
|
|
144
|
+
".js": "application/javascript",
|
|
145
|
+
".json": "application/json",
|
|
146
|
+
".png": "image/png",
|
|
147
|
+
".jpg": "image/jpeg",
|
|
148
|
+
".jpeg": "image/jpeg",
|
|
149
|
+
".gif": "image/gif",
|
|
150
|
+
".svg": "image/svg+xml",
|
|
151
|
+
".ico": "image/x-icon",
|
|
152
|
+
".txt": "text/plain",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Run if executed directly
|
|
156
|
+
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
157
|
+
const port = await getAvailablePort(Number(process.env.PORT) || 8000);
|
|
158
|
+
const server = new SimpleHTTPRequestHandler(process.cwd(), port);
|
|
159
|
+
server.serve();
|
|
160
|
+
}
|
package/test.css
ADDED