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 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
@@ -0,0 +1,9 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>ssi_server demo</title>
5
+ </head>
6
+ <body>
7
+ <!--#include virtual="about.html"-->
8
+ </body>
9
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spa-ssi",
3
- "version": "0.0.0",
3
+ "version": "0.0.1",
4
4
  "description": "Single Page App / Server Side Include Simple File Web Server",
5
5
  "keywords": [
6
6
  "single-page-app",
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
@@ -0,0 +1,4 @@
1
+ @charset "UTF-8";
2
+ p {
3
+ color: green;
4
+ }