instaserve 1.0.21 → 1.1.2
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/README.md +176 -0
- package/generate-certs.sh +59 -0
- package/instaserve +118 -0
- package/module.mjs +136 -0
- package/package.json +14 -5
- package/routes_example.md +54 -0
- package/index.mjs +0 -2
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1 style="color: #3b82f6; margin: 10px 0 5px;">Instaserve</h1>
|
|
3
|
+
<p style="color: #6b7280; margin: 0;">Instant web stack for Node.js</p>
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<img src="https://img.shields.io/npm/v/instaserve" alt="npm version" />
|
|
7
|
+
<img src="https://img.shields.io/bundlephobia/minzip/instaserve" alt="bundle size" />
|
|
8
|
+
</p>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
<div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); margin: 15px 0;">
|
|
14
|
+
<pre style="margin: 0;"><code>npx instaserve [options]
|
|
15
|
+
npx instaserve generate-routes</code></pre>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
21
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">generate-routes</code>
|
|
22
|
+
<span>Create a sample routes.js file in the current directory</span>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
## Options
|
|
26
|
+
|
|
27
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
28
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-port <number></code>
|
|
29
|
+
<span>Port to listen on (default: 3000)</span>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
33
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-ip <address></code>
|
|
34
|
+
<span>IP address to bind to (default: 127.0.0.1)</span>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
38
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-public <path></code>
|
|
39
|
+
<span>Public directory path (default: ./public)</span>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
43
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-api <file></code>
|
|
44
|
+
<span>Path to routes file (default: ./routes.js)</span>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
48
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-secure</code>
|
|
49
|
+
<span>Enable HTTPS (requires cert.pem and key.pem - run ./generate-certs.sh)</span>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
53
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-help</code>
|
|
54
|
+
<span>Show help message</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
## HTTPS Support
|
|
58
|
+
|
|
59
|
+
Instaserve supports HTTPS with self-signed certificates. To enable HTTPS:
|
|
60
|
+
|
|
61
|
+
1. **Generate certificates:**
|
|
62
|
+
```bash
|
|
63
|
+
./generate-certs.sh
|
|
64
|
+
```
|
|
65
|
+
This creates `cert.pem` and `key.pem` files and adds them to your system's trust store.
|
|
66
|
+
|
|
67
|
+
2. **Run with HTTPS:**
|
|
68
|
+
```bash
|
|
69
|
+
npx instaserve -secure
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The certificate generation script:
|
|
73
|
+
- Creates a self-signed certificate valid for 365 days
|
|
74
|
+
- Automatically adds the certificate to your system trust store (macOS/Linux)
|
|
75
|
+
- Prevents browser security warnings
|
|
76
|
+
|
|
77
|
+
## Routes
|
|
78
|
+
|
|
79
|
+
The routes file (`routes.js` by default) defines your API endpoints. Each route is a function that handles requests to a specific URL path.
|
|
80
|
+
|
|
81
|
+
### Generating a Routes File
|
|
82
|
+
|
|
83
|
+
To create a sample `routes.js` file with example routes and middleware:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx instaserve generate-routes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This creates a `routes.js` file in the current directory with example code. If the file already exists, the command will fail to prevent overwriting.
|
|
90
|
+
|
|
91
|
+
### Routes File Validation
|
|
92
|
+
|
|
93
|
+
Instaserve validates routes files on startup:
|
|
94
|
+
- If `-api` is specified and the file doesn't exist, the server will fail to start
|
|
95
|
+
- The routes file must export a default object
|
|
96
|
+
- All route handlers must be functions
|
|
97
|
+
- Invalid routes files will cause the server to exit with an error message
|
|
98
|
+
|
|
99
|
+
### Basic Route Example
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
export default {
|
|
103
|
+
// Handle GET /hello
|
|
104
|
+
hello: (req, res, data) => {
|
|
105
|
+
return { message: 'Hello World' }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Special Routes (Middleware)
|
|
111
|
+
|
|
112
|
+
Routes starting with `_` are middleware functions that run on **every request** before the main route handler. They are useful for:
|
|
113
|
+
|
|
114
|
+
- Logging requests
|
|
115
|
+
- Authentication
|
|
116
|
+
- Request modification
|
|
117
|
+
- Response headers
|
|
118
|
+
|
|
119
|
+
Middleware functions can:
|
|
120
|
+
- Return `false` to continue to the next middleware or main route
|
|
121
|
+
- Return a truthy value to stop processing and use that as the response
|
|
122
|
+
- Modify the request or response objects
|
|
123
|
+
|
|
124
|
+
#### Middleware Example
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
export default {
|
|
128
|
+
// Log every request
|
|
129
|
+
_log: (req, res, data) => {
|
|
130
|
+
console.log(`${req.method} ${req.url}`)
|
|
131
|
+
return false // Continue processing
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// Block unauthorized requests
|
|
135
|
+
_auth: (req, res, data) => {
|
|
136
|
+
if (!data.token) {
|
|
137
|
+
res.writeHead(401)
|
|
138
|
+
return 'Unauthorized'
|
|
139
|
+
}
|
|
140
|
+
return false // Continue if authorized
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Route Parameters
|
|
146
|
+
|
|
147
|
+
Each route function receives:
|
|
148
|
+
- `req` - The HTTP request object
|
|
149
|
+
- `res` - The HTTP response object
|
|
150
|
+
- `data` - Combined data from:
|
|
151
|
+
- POST body (if JSON)
|
|
152
|
+
- URL query parameters
|
|
153
|
+
- Form data
|
|
154
|
+
|
|
155
|
+
### Example Routes File
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// routes.js
|
|
159
|
+
export default {
|
|
160
|
+
// Middleware example
|
|
161
|
+
_debug: (req, res, data) => {
|
|
162
|
+
console.log('Request:', req.url)
|
|
163
|
+
return false // Continue to next route
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// API endpoint
|
|
167
|
+
api: (req, res, data) => {
|
|
168
|
+
return { status: 'ok', data }
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// Error handling
|
|
172
|
+
testerror: () => {
|
|
173
|
+
throw new Error('Test error')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "Generating self-signed certificates for Instaserve..."
|
|
4
|
+
|
|
5
|
+
# Create openssl config file
|
|
6
|
+
cat > openssl.conf << EOF
|
|
7
|
+
[req]
|
|
8
|
+
distinguished_name = req_distinguished_name
|
|
9
|
+
req_extensions = v3_req
|
|
10
|
+
prompt = no
|
|
11
|
+
|
|
12
|
+
[req_distinguished_name]
|
|
13
|
+
C = US
|
|
14
|
+
ST = State
|
|
15
|
+
L = City
|
|
16
|
+
O = Instaserve
|
|
17
|
+
OU = Development
|
|
18
|
+
CN = localhost
|
|
19
|
+
|
|
20
|
+
[v3_req]
|
|
21
|
+
basicConstraints = CA:FALSE
|
|
22
|
+
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
|
23
|
+
extendedKeyUsage = serverAuth, clientAuth
|
|
24
|
+
subjectAltName = @alt_names
|
|
25
|
+
|
|
26
|
+
[alt_names]
|
|
27
|
+
DNS.1 = localhost
|
|
28
|
+
DNS.2 = *.localhost
|
|
29
|
+
IP.1 = 127.0.0.1
|
|
30
|
+
IP.2 = ::1
|
|
31
|
+
EOF
|
|
32
|
+
|
|
33
|
+
# Generate private key
|
|
34
|
+
openssl genrsa -out key.pem 2048
|
|
35
|
+
|
|
36
|
+
# Generate certificate with config
|
|
37
|
+
openssl req -new -x509 -key key.pem -out cert.pem -days 365 -config openssl.conf -extensions v3_req
|
|
38
|
+
|
|
39
|
+
# Clean up config
|
|
40
|
+
rm openssl.conf
|
|
41
|
+
|
|
42
|
+
echo "Certificates generated: cert.pem and key.pem"
|
|
43
|
+
|
|
44
|
+
# Add to system trust store (macOS)
|
|
45
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
46
|
+
echo "Adding certificate to macOS trust store..."
|
|
47
|
+
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem
|
|
48
|
+
echo "Certificate added to macOS trust store"
|
|
49
|
+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
50
|
+
echo "Adding certificate to Linux trust store..."
|
|
51
|
+
sudo cp cert.pem /usr/local/share/ca-certificates/instaserve.crt
|
|
52
|
+
sudo update-ca-certificates
|
|
53
|
+
echo "Certificate added to Linux trust store"
|
|
54
|
+
else
|
|
55
|
+
echo "Please manually add cert.pem to your system's trust store"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "HTTPS certificates ready! Use 'npx instaserve -secure' to enable HTTPS"
|
|
59
|
+
echo "Note: You may still see browser warnings for self-signed certificates"
|
package/instaserve
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
|
|
6
|
+
console.log(chalk.cyan('\nInstaserve - Instant Web Stack\n'))
|
|
7
|
+
console.log(chalk.yellow('Usage:'))
|
|
8
|
+
console.log(chalk.green(' npx instaserve [options]'))
|
|
9
|
+
console.log(chalk.green(' npx instaserve generate-routes\n'))
|
|
10
|
+
console.log(chalk.yellow('Commands:'))
|
|
11
|
+
console.log(chalk.green(' generate-routes') + ' Create a sample routes.js file in the current directory\n')
|
|
12
|
+
console.log(chalk.yellow('Options:'))
|
|
13
|
+
console.log(chalk.green(' -port <number>') + ' Port to listen on (default: 3000)')
|
|
14
|
+
console.log(chalk.green(' -ip <address>') + ' IP address to bind to (default: 127.0.0.1)')
|
|
15
|
+
console.log(chalk.green(' -public <path>') + ' Public directory path (default: ./public)')
|
|
16
|
+
console.log(chalk.green(' -api <file>') + ' Path to routes file (default: ./routes.js)')
|
|
17
|
+
console.log(chalk.green(' -secure') + ' Enable HTTPS (requires cert.pem and key.pem: run generate-certs.sh)')
|
|
18
|
+
console.log(chalk.green(' -help') + ' Show this help message\n')
|
|
19
|
+
|
|
20
|
+
if (process.argv.includes('-help')) {
|
|
21
|
+
process.exit(0)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2)
|
|
25
|
+
|
|
26
|
+
// Handle generate-routes command
|
|
27
|
+
if (args[0] === 'generate-routes') {
|
|
28
|
+
const routesFile = './routes.js'
|
|
29
|
+
if (fs.existsSync(routesFile)) {
|
|
30
|
+
console.error(chalk.red(`Error: ${routesFile} already exists`))
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const sampleRoutes = `export default {
|
|
35
|
+
// Middleware functions (prefixed with _) run on every request
|
|
36
|
+
// Return false to continue processing, or a value to use as response
|
|
37
|
+
|
|
38
|
+
// Example: Log all requests
|
|
39
|
+
_log: (req, res, data) => {
|
|
40
|
+
console.log(\`\${req.method} \${req.url}\`)
|
|
41
|
+
return false // Continue to next middleware or route
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Example: Basic authentication (commented out)
|
|
45
|
+
// _auth: (req, res, data) => {
|
|
46
|
+
// if (!data.token) {
|
|
47
|
+
// res.writeHead(401)
|
|
48
|
+
// return 'Unauthorized'
|
|
49
|
+
// }
|
|
50
|
+
// return false // Continue if authorized
|
|
51
|
+
// },
|
|
52
|
+
|
|
53
|
+
// Regular route handlers
|
|
54
|
+
hello: (req, res, data) => {
|
|
55
|
+
return { message: 'Hello World' }
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
api: (req, res, data) => {
|
|
59
|
+
return { message: 'API endpoint', data }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
`
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(routesFile, sampleRoutes)
|
|
65
|
+
console.log(chalk.green(`✓ Created ${routesFile}`))
|
|
66
|
+
process.exit(0)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
import server from './module.mjs'
|
|
70
|
+
|
|
71
|
+
const params = {}
|
|
72
|
+
for (let i = 0; i < args.length; i++) {
|
|
73
|
+
const arg = args[i]
|
|
74
|
+
if (arg.startsWith('-')) {
|
|
75
|
+
const key = arg.slice(1)
|
|
76
|
+
const nextArg = args[i + 1]
|
|
77
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
78
|
+
params[key] = nextArg
|
|
79
|
+
i++ // Skip the next argument since we used it
|
|
80
|
+
} else {
|
|
81
|
+
params[key] = true // Boolean flag
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Load routes file
|
|
87
|
+
let routes = {}
|
|
88
|
+
const routesFile = params.api || './routes.js'
|
|
89
|
+
const routesFileSpecified = !!params.api
|
|
90
|
+
|
|
91
|
+
if (routesFileSpecified && !fs.existsSync(routesFile)) {
|
|
92
|
+
console.error(chalk.red(`Error: Routes file "${routesFile}" does not exist`))
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (fs.existsSync(routesFile)) {
|
|
97
|
+
try {
|
|
98
|
+
const imported = await import(routesFile)
|
|
99
|
+
routes = imported.default || imported
|
|
100
|
+
|
|
101
|
+
if (!routes || typeof routes !== 'object' || Array.isArray(routes)) {
|
|
102
|
+
console.error(chalk.red(`Error: Routes file "${routesFile}" must export a default object`))
|
|
103
|
+
process.exit(1)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [key, handler] of Object.entries(routes)) {
|
|
107
|
+
if (typeof handler !== 'function') {
|
|
108
|
+
console.error(chalk.red(`Error: Route "${key}" in "${routesFile}" must be a function`))
|
|
109
|
+
process.exit(1)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error(chalk.red(`Error: Could not load routes file "${routesFile}": ${e.message}`))
|
|
114
|
+
process.exit(1)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
server(routes)
|
package/module.mjs
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
import https from 'node:https'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2)
|
|
6
|
+
const params = {}
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const arg = args[i]
|
|
9
|
+
if (arg.startsWith('-')) {
|
|
10
|
+
const key = arg.slice(1)
|
|
11
|
+
const nextArg = args[i + 1]
|
|
12
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
13
|
+
params[key] = nextArg
|
|
14
|
+
i++ // Skip the next argument since we used it
|
|
15
|
+
} else {
|
|
16
|
+
params[key] = true // Boolean flag
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function public_file(r, s) {
|
|
22
|
+
if (r.url == '/') r.url = '/index.html'
|
|
23
|
+
const fn = `${params.public || './public'}${r.url.replace(/\.\./g, '')}`
|
|
24
|
+
if (fs.existsSync(fn)) {
|
|
25
|
+
const content = fs.readFileSync(fn, 'utf-8')
|
|
26
|
+
if (fn.match(/.js$/)) {
|
|
27
|
+
s.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
28
|
+
} else {
|
|
29
|
+
s.writeHead(200)
|
|
30
|
+
}
|
|
31
|
+
s.end(content)
|
|
32
|
+
return true // Indicate file was served
|
|
33
|
+
}
|
|
34
|
+
return false // Indicate no file was served
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1') {
|
|
38
|
+
const publicDir = params.public || './public'
|
|
39
|
+
if (publicDir.includes('..')) {
|
|
40
|
+
throw new Error('Public directory path cannot contain ".."')
|
|
41
|
+
}
|
|
42
|
+
if (!fs.existsSync(publicDir)) {
|
|
43
|
+
throw new Error(`Public directory "${publicDir}" does not exist`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const requestHandler = async (r, s) => {
|
|
47
|
+
let sdata = '', rrurl = r.url || ''
|
|
48
|
+
let responseSent = false
|
|
49
|
+
|
|
50
|
+
r.on('data', (s) => sdata += s.toString().trim())
|
|
51
|
+
r.on('end', (x) => {
|
|
52
|
+
try {
|
|
53
|
+
// Compose data object
|
|
54
|
+
const data = sdata ? JSON.parse(sdata) : {}
|
|
55
|
+
const qs = rrurl.split('?')
|
|
56
|
+
if(qs && qs[1]) {
|
|
57
|
+
const o = JSON.parse('{"' + decodeURI(qs[1].replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}')
|
|
58
|
+
Object.assign(data, o)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const midware = Object.keys(routes)
|
|
62
|
+
.filter((k) => k.startsWith('_'))
|
|
63
|
+
.find((k) => {
|
|
64
|
+
const result = routes[k](r, s, data)
|
|
65
|
+
if (result && !responseSent) {
|
|
66
|
+
responseSent = true
|
|
67
|
+
s.end(typeof result === 'string' ? result : JSON.stringify(result))
|
|
68
|
+
}
|
|
69
|
+
return result
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Response closed by middleware
|
|
73
|
+
if(responseSent || s.writableEnded) return
|
|
74
|
+
|
|
75
|
+
// Try to serve public file
|
|
76
|
+
if(public_file(r, s)) {
|
|
77
|
+
responseSent = true
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const url = rrurl.split('/')[1].split('?')[0]
|
|
82
|
+
if (routes[url]) {
|
|
83
|
+
const resp = routes[url](r, s, data)
|
|
84
|
+
if (!responseSent && !s.writableEnded) {
|
|
85
|
+
responseSent = true
|
|
86
|
+
s.end(typeof resp === 'string' ? resp:JSON.stringify(resp))
|
|
87
|
+
}
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!responseSent && !s.writableEnded) {
|
|
92
|
+
responseSent = true
|
|
93
|
+
s.writeHead(404);
|
|
94
|
+
s.end();
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error(e.stack)
|
|
98
|
+
if (!responseSent && !s.writableEnded) {
|
|
99
|
+
responseSent = true
|
|
100
|
+
s.writeHead(500).end()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let server
|
|
107
|
+
if (params.secure) {
|
|
108
|
+
const certPath = './cert.pem'
|
|
109
|
+
const keyPath = './key.pem'
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
|
|
112
|
+
throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const options = {
|
|
116
|
+
cert: fs.readFileSync(certPath),
|
|
117
|
+
key: fs.readFileSync(keyPath)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
server = https.createServer(options, requestHandler)
|
|
121
|
+
} else {
|
|
122
|
+
server = http.createServer(requestHandler)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
server.listen(port || 3000, ip || '')
|
|
126
|
+
|
|
127
|
+
const protocol = params.secure ? 'https' : 'http'
|
|
128
|
+
console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${Object.keys(routes).length > 0 ? `using routes: ${Object.keys(routes)}` : 'not using routes'}`)
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
routes: routes,
|
|
132
|
+
port: port,
|
|
133
|
+
server: server,
|
|
134
|
+
stop: () => { server.close(); return true }
|
|
135
|
+
}
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "instaserve",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "Instant web stack",
|
|
5
|
+
"main": "module.mjs",
|
|
6
|
+
"bin": "./instaserve",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node instaserve"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"chalk": "^5.6.2"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file defines the API routes and middleware for the Instaserve web server.
|
|
3
|
+
*
|
|
4
|
+
* The file exports a default object containing route handlers and middleware functions.
|
|
5
|
+
*
|
|
6
|
+
* Middleware functions (prefixed with _):
|
|
7
|
+
* - _debug: Logs request method, URL and data to console
|
|
8
|
+
* - _returnfalsy: Example middleware that returns true to stop request processing
|
|
9
|
+
* - _example: Demonstrates middleware behavior with console log
|
|
10
|
+
* - _end: Example of early response termination (commented out)
|
|
11
|
+
*
|
|
12
|
+
* Route handlers:
|
|
13
|
+
* - api: Returns a string response with the request data
|
|
14
|
+
* - testerror: Throws an error for testing error handling
|
|
15
|
+
* - testdata: Example route that returns the context parameter
|
|
16
|
+
*
|
|
17
|
+
* Each route handler receives:
|
|
18
|
+
* - req: HTTP request object
|
|
19
|
+
* - s: HTTP response object
|
|
20
|
+
* - data: Combined request data (POST body, query params, form data)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
// Middleware functions (prefixed with _) run on every request
|
|
25
|
+
// Return false to continue processing, or a value to use as response
|
|
26
|
+
|
|
27
|
+
// Example: Log all requests
|
|
28
|
+
_log: (req, res, data) => {
|
|
29
|
+
console.log(`${req.method} ${req.url}`)
|
|
30
|
+
return false // Continue to next middleware or route
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**** Example: Basic authentication
|
|
34
|
+
_auth: (req, res, data) => {
|
|
35
|
+
if (!data.token) {
|
|
36
|
+
res.writeHead(401)
|
|
37
|
+
return 'Unauthorized'
|
|
38
|
+
}
|
|
39
|
+
return false // Continue if authorized
|
|
40
|
+
}, */
|
|
41
|
+
|
|
42
|
+
// Regular route handlers
|
|
43
|
+
hello: (req, res, data) => {
|
|
44
|
+
return { message: 'Hello World' }
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
_debug: ({method, url}, s, data) => { console.log(method, url, data) },
|
|
48
|
+
_example: (r, s) => console.log('in routes.mjs, returning a truthy value (above) will stop the chain'),
|
|
49
|
+
// _returnfalsy: (r, s) => { return true }, // Commented out - interferes with static file serving
|
|
50
|
+
|
|
51
|
+
api: (r, s, data) => 'an example api response, data:' + JSON.stringify(data),
|
|
52
|
+
testerror: () => { throw new Error('this from testerror') },
|
|
53
|
+
testdata: (r, s, c, d) => c
|
|
54
|
+
}
|
package/index.mjs
DELETED