....
/**
* Creates an input stream from a path.
* @param path
* @returns {Promise<ReadableStream>}
*/asyncfunctioncreateInputStream(path) {
if (path.startsWith('/')) // If it starts with '/', omit it.
path = path.substr(1);
if (path.startsWith('.')) // If it starts with '.', reject it.thrownew NotFoundError(path);
if (path === '/' || !path) // If it's empty, set to index.html.
path = 'index.html';
/**
* `import.meta.dirname` and `import.meta.filename` replace the old CommonJS `__dirname` and `__filename`.
*/const filePath = Nexus.FileSystem.join(import.meta.dirname, 'server_root', path);
try {
// Stat the target path.const {type} = await Nexus.FileSystem.stat(filePath);
if (type === Nexus.FileSystem.FileType.Directory) // If it's a directory, return its 'index.html'return createInputStream(Nexus.FileSystem.join(filePath, 'index.html'));
elseif (type === Nexus.FileSystem.FileType.Unknown || type === Nexus.FileSystem.FileType.NotFound)
// If it's not found, throw NotFound.thrownew NotFoundError(path);
} catch(e) {
if (e.code)
throw e;
thrownew NotFoundError(path);
}
try {
// First, we create a device.const fileDevice = new Nexus.IO.FilePushDevice(filePath);
// Then we return a new ReadableStream created using our source device.returnnew Nexus.IO.ReadableStream(fileDevice);
} catch(e) {
thrownew InternalServerError(e.message);
}
}
/**
* Connections counter.
*/let connections = 0;
/**
* Create a new HTTP server.
* @type {Nexus.Net.HTTP.Server}
*/const server = new Nexus.Net.HTTP.Server();
// A server error means an error occurred while the server was listening to connections.// We can mostly ignore such errors, we display them anyway.
server.on('error', e => {
console.error(FgRed + Bright + 'Server Error: ' + e.message + '\n' + e.stack, Reset);
});
/**
* Listen to connections.
*/
server.on('connection', async (connection, peer) => {
// Start with a connection ID of 0, increment with every new connection.const connId = connections++;
// Record the start time for this connection.const startTime = Date.now();
// Destructuring is supported, why not use it?const { request, response } = connection;
// Parse the URL parts.const { path } = parseURL(request.url);
// Here we'll store any errors that occur during the connection.const errors = [];
// inStream is our ReadableStream file source, outStream is our response (device) wrapped in a WritableStream.let inStream, outStream;
try {
// Log the request.console.log(`> #${FgCyan + connId + Reset}${Bright + peer.address}:${peer.port + Reset}${
FgGreen + request.method + Reset} "${FgYellow}${path}${Reset}"`, Reset);
// Set the 'Server' header.
response.set('Server', `nexus.js/0.1.1`);
// Create our input stream.
inStream = await createInputStream(path);
// Create our output stream.
outStream = new Nexus.IO.WritableStream(response);
// Hook all `error` events, add any errors to our `errors` array.
inStream.on('error', e => { errors.push(e); });
request.on('error', e => { errors.push(e); });
response.on('error', e => { errors.push(e); });
outStream.on('error', e => { errors.push(e); });
// Set content type and request status.
response
.set('Content-Type', mimeType(path))
.status(200);
// Hook input to output(s).const disconnect = inStream.pipe(outStream);
try {
// Resume our file stream, this causes the stream to switch to HTTP chunked encoding.// This will return a promise that will only resolve after the last byte (HTTP chunk) is written.await inStream.resume();
} catch (e) {
// Capture any errors that happen during the streaming.
errors.push(e);
}
// Disconnect all the callbacks created by `.pipe()`.return disconnect();
} catch(e) {
// If an error occurred, push it to the array.
errors.push(e);
// Set the content type, status, and write a basic message.
response
.set('Content-Type', 'text/plain')
.status(e.code || 500)
.send(e.message || 'An error has occurred.');
} finally {
// Close the streams manually. This is important because we may run out of file handles otherwise.if (inStream)
await inStream.close();
if (outStream)
await outStream.close();
// Close the connection, has no real effect with keep-alive connections.await connection.close();
// Grab the response's status.let status = response.status();
// Determine what colour to output to the terminal.const statusColors = {
'200': Bright + FgGreen, // Green for 200 (OK),'404': Bright + FgYellow, // Yellow for 404 (Not Found)'500': Bright + FgRed // Red for 500 (Internal Server Error)
};
let statusColor = statusColors[status];
if (statusColor)
status = statusColor + status + Reset;
// Log the connection (and time to complete) to the console.console.log(`< #${FgCyan + connId + Reset}${Bright + peer.address}:${peer.port + Reset}${
FgGreen + request.method + Reset} "${FgYellow}${path}${Reset}" ${status}${(Date.now() * startTime)}ms` +
(errors.length ? " " + FgRed + Bright + errors.map(error => error.message).join(', ') + Reset : Reset));
}
});
/**
* IP and port to listen on.
*/const ip = '0.0.0.0', port = 3000;
/**
* Whether or not to set the `reuse` flag. (optional, default=false)
*/const portReuse = true;
/**
* Maximum allowed concurrent connections. Default is 128 on my system. (optional, system specific)
* @type {number}
*/const maxConcurrentConnections = 1000;
/**
* Bind the selected address and port.
*/
server.bind(ip, port, portReuse);
/**
* Start listening to requests.
*/
server.listen(maxConcurrentConnections);
/**
* Happy streaming!
*/console.log(FgGreen + `Nexus.js HTTP server listening at ${ip}:${port}` + Reset);
基准
我想我已经涵盖了到目前为止所实现的一切。那么现在我们来谈谈性能。
这里是上诉Http服务器的当前基准,有100个并发连接和总共10000个请求:
This is ApacheBench, Version 2.3 <$Revision: 1796539 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient).....done
Server Software: nexus.js/0.1.1
Server Hostname: localhost
Server Port: 3000
Document Path: /
Document Length: 8673 bytes
Concurrency Level: 100
Time taken for tests: 9.991 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 87880000 bytes
HTML transferred: 86730000 bytes
Requests per second: 1000.94 [#/sec] (mean)
Time per request: 99.906 [ms] (mean)
Time per request: 0.999 [ms] (mean, across all concurrent requests)
Transfer rate: 8590.14 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 1
Processing: 6 99 36.6 84 464
Waiting: 5 99 36.4 84 463
Total: 6 100 36.6 84 464
Percentage of the requests served within a certain time (ms)
50% 84
66% 97
75% 105
80% 112
90% 134
95% 188
98% 233
99% 238
100% 464 (longest request)