Skip to content

Commit

Permalink
Added preferences support, cache support, and other features to Wire
Browse files Browse the repository at this point in the history
This is a major commit that adds the following features to Aluminum Wire:
- Preferences file support
- Support for clients caching files (transmitting `Last-Modified` header, serving 304 response codes when appropriate, etc.)
- A default 500 error page
- A default homepage for Wire
- Fixed a bug where requesting a directory caused a file to be served with a `Content-Type` header of `false`
- Added `Server` headers to responses
- Documentation updates
- More changes: see the diffs and new documentation for details
  • Loading branch information
ZelnickB committed Sep 6, 2020
1 parent 42c63db commit 26c4d15
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 25 deletions.
14 changes: 14 additions & 0 deletions defaults/prefs/wire.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"protocol": "http",
"indexRedirect": true,
"errorPages": {
"notFound": {
"URI": "../../usr/resources/wire/errors/404.html",
"encoding": "utf8"
},
"serverError": {
"URI": "../../usr/resources/wire/errors/500.html",
"encoding": "utf8"
}
}
}
File renamed without changes.
21 changes: 21 additions & 0 deletions defaults/resources/wire/errors/500.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>500 Error | Aluminum Wire</title>
</head>
<body>
<h1>500 Error</h1>
<p>The server encountered an internal error. Please contact the system administrator or try again later.</p>
<p>
Error Message: $errmessage$<br />
Error Code: $errcode$<br />
Error Number: $errno$
</p>
<hr />
<p>Aluminum Wire on $osplatform$ ($ostype$) version $osversion$ at port $port$</p>
<div style="text-align: center">
<img src="/aluminum-internals/logo.svg" alt="Aluminum logo" style="width: 150pt" />
</div>
</body>
</html>
26 changes: 26 additions & 0 deletions defaults/resources/wire/serve/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome | Aluminum Wire</title>
</head>
<body>
<h1>Welcome to Aluminum Wire!</h1>
<p>
Congratulations! If you're seeing this page, it means that you have successfully installed and set up Aluminum Wire. Feel free to delete this file from your <code>/usr/resources/wire/serve</code> directory. You can always find it again at <code>/defaults/resources/wire/serve/index.html</code>.
</p>
<p>
Here are some links to help you get started:<br />
<a href="https://github.com/NovaDevelopment/aluminum">The Aluminum Repository on GitHub</a><br />
<a href="https://aluminumjs.readthedocs.io/">Aluminum Documentation</a>
</p>
<hr />
<div style="text-align: center; margin-bottom:10pt;">
<img src="/aluminum-internals/logo.svg" alt="Aluminum logo" style="width: 150pt;" />
</div>
<small>
The Aluminum project is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br />
The Aluminum project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the <a href="https://www.gnu.org/licenses/agpl-3.0.txt">GNU Affero General Public License</a> for more details.
</small>
</body>
</html>
89 changes: 89 additions & 0 deletions docs/features/wire.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,92 @@ Aluminum Wire
*************

Aluminum Wire is a highly-customizable and extensible static file server that also includes support for dynamic files (using both Node.js and PHP).

.. note:: Dynamic file support is not yet available

Configuration
=============

The configuration file for Aluminum Wire is read from ``/usr/prefs/wire.json``. The supported configuration options are described below.

.. json:object:: wireConfig
:property String protocol: The protocol for Wire to use, either ``http`` or ``https``

.. note:: ``protocol`` is not yet implemented.

:property Boolean indexRedirect: Whether to respond to requests for a directory by serving the ``index.html`` file in that directory (if it exists; otherwise, a 404 response code will be served)
:property errorPages: Configuration options for the error pages served by Wire
:proptype errorPages: :json:object:`wireErrPgs`

.. json:object:: wireErrPages
:property notFound: Describes the page that Wire should serve if a resource cannot be found
:proptype notFound: :json:object:`errPageConf`
:property serverError: Describes the page that Wire should serve in the event of an internal server error
:proptype serverError: :json:object:`errPageConf`

.. json:object:: errPageConf
:property String URI: The URI of the file to serve, given relative to ``/src/wire/main.js``
:property String encoding: The encoding of the file to serve. If the MIME type that corresponds to ``URI`` is not ``text``, this property will be ignored.

Serving Files
=============

Wire serves files from ``/usr/resources/wire/serve/``.

Client Cache Support
--------------------

When Wire serves a static file, it retrieves the date that the file was last modified and sends this information in the ``Last-Modified`` HTTP header.

Wire automatically reads the ``If-Modified-Since`` HTTP header from the request when serving static files. If the requested resource exists and has not been modified since the time indicated by ``If-Modified-Since``, a ``304 Not Modified`` response code is served, indicating that the cached version of the file is up-to-date. Otherwise (or if the request does not contain an ``If-Modified-Since`` header), the resource is served.

Error Handling
--------------

The following table shows the types of errors that Wire will handle when serving files:

+----------------------+------------+--------------------+-------------------------------------------------------+
| Problematic Function | Error Code | HTTP Response Code | Response Body |
+======================+============+====================+=======================================================+
| ``fs.readFile`` | Any | 404 | The file at ``wireConfig.errorPages.notFound.URI`` |
+----------------------+------------+--------------------+-------------------------------------------------------+
| ``fs.stat`` | Any | 500 | The file at ``wireConfig.errorPages.serverError.URI`` |
+----------------------+------------+--------------------+-------------------------------------------------------+

.. caution:: Errors with reading the Wire configuration file (e.g., the file cannot be accessed at ``/usr/prefs/wire.json``, a required configuration option is missing) or error pages (e.g., the error page cannot be accessed) will raise an exception and cause Wire to crash.

Error Page Variables
^^^^^^^^^^^^^^^^^^^^

In error pages with MIME type ``text``, information about the server and error may be included in the response sent. Variables may be inserted anywhere within the file and are surrounded by the dollar (``$``) symbol.

.. note:: If two variables need to be inserted in an error page back-to-back, then each variable should have its own set of ``$`` symbols.

.. note:: Variables are case-sensitive.

The possible variables that may be used within an error page are described below. Each variable may be used zero, one, or multiple times.

+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable | Description |
+==================+==================================================================================================================================================================================================+
| ``$requrl$`` | The request URL |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$adjrequrl$`` | The request URL with \"index.html\" appended, if ``wireConfig.indexRedirect`` is set to ``true``. Otherwise, this is the same as ``$requrl$``. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$osplatform$`` | The operating system platform of the server. See `os.platform() <https://nodejs.org/api/os.html#os_os_platform>`_. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$ostype$`` | The operating system type of the server. See `os.type() <https://nodejs.org/api/os.html#os_os_type>`_. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$osversion$`` | The operating system release of the server. See `os.release() <https://nodejs.org/api/os.html#os_os_version>`_. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$port$`` | The port on which the Wire server is listening. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$errcode$`` | The code of the error. See `error.code <https://nodejs.org/api/errors.html#errors_error_code>`_. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$errno$`` | The number of the error. See `error.errno <https://nodejs.org/api/errors.html#errors_error_errno>`_. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``$errmessage$`` | The error message. Note that this may contain information such as the absolute path to a resource on a server. See `error.message <https://nodejs.org/api/errors.html#errors_error_message_1>`_. |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
99 changes: 74 additions & 25 deletions src/wire/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,39 @@ const path = require('path') // For manipulating request paths
const mimeTypes = require('mime-types') // For sending the correct Content-Type HTTP header

const ports = JSON.parse(fs.readFileSync('../../usr/prefs/listen.json', 'utf8')).wire
const errorPage = fs.readFileSync('../../defaults/errors/404.html', 'utf8') // Default error page content
const preferences = JSON.parse(fs.readFileSync('../../usr/prefs/wire.json', 'utf8'))

const logo = fs.readFileSync('../../logo.svg', 'utf8')

function resolveErrorPage (path, encoding) {
const resolution = {}
resolution.mimeType = mimeTypes.lookup(path)
if (resolution.mimeType.split('/')[0] === 'text') {
resolution.text = true
resolution.content = fs.readFileSync(path, encoding)
} else {
resolution.text = false
resolution.content = fs.readFileSync(path)
}
return resolution
}

const errorPage = {
notFound: resolveErrorPage(preferences.errorPages.notFound.URI, preferences.errorPages.notFound.encoding),
serverError: resolveErrorPage(preferences.errorPages.serverError.URI, preferences.errorPages.serverError.encoding)
}

function serverHandler (req, res) {
let filename = path.join(__dirname, '..', '..', '..', path.normalize(req.url))
const mimetype = mimeTypes.lookup(filename)
let filename = path.join(__dirname, '..', '..', 'usr', 'resources', 'wire', 'serve', path.normalize(req.url))
const reqURLArray = req.url.split('/')
if (reqURLArray[1] === 'aluminum-internals') {
switch (reqURLArray[2]) {
case 'logo.svg':
res.writeHead(200, { 'Content-Type': 'image/svg+xml' })
res.writeHead(200, { Server: 'Aluminum Wire', 'Content-Type': 'image/svg+xml' })
res.write(logo)
return res.end()
default:
res.writeHead(404, { 'Content-Type': 'text/plain' })
res.writeHead(404, { Server: 'Aluminum Wire', 'Content-Type': 'text/plain' })
res.write('404 ERROR | NOT FOUND' +
os.EOL +
os.EOL +
Expand All @@ -34,33 +52,64 @@ function serverHandler (req, res) {
return res.end()
}
}
if (reqURLArray[reqURLArray.length - 1] === '') {
if (preferences.indexRedirect && reqURLArray[reqURLArray.length - 1] === '') {
filename += 'index.html'
}
fs.readFile(filename, function (err, data) {
if (err) {
const mimetype = mimeTypes.lookup(filename)
fs.readFile(filename, function (readErr, data) {
if (readErr) {
let adjustedReqURL = req.url
if (reqURLArray[reqURLArray.length - 1] === '') {
if (preferences.indexRedirect && reqURLArray[reqURLArray.length - 1] === '') {
adjustedReqURL += 'index.html'
}
res.writeHead(404, { 'Content-Type': 'html' })
res.write(
errorPage
.replace(/\$requrl\$/g, req.url)
.replace(/\$adjrequrl\$/g, adjustedReqURL)
.replace(/\$osplatform\$/g, os.platform())
.replace(/\$ostype\$/g, os.type())
.replace(/\$osversion\$/g, os.release())
.replace(/\$port\$/g, ports.HTTP.toString())
.replace(/\$errcode\$/, err.code)
.replace(/\$errno\$/, err.errno.toString())
.replace(/\$errmessage\$/, err.message)
)
res.writeHead(404, { Server: 'Aluminum Wire', 'Content-Type': errorPage.notFound.mimeType })
if (errorPage.notFound.text) {
res.write(
errorPage.notFound.content
.replace(/\$requrl\$/g, req.url)
.replace(/\$adjrequrl\$/g, adjustedReqURL)
.replace(/\$osplatform\$/g, os.platform())
.replace(/\$ostype\$/g, os.type())
.replace(/\$osversion\$/g, os.release())
.replace(/\$port\$/g, ports.HTTP.toString())
.replace(/\$errcode\$/g, readErr.code)
.replace(/\$errno\$/g, readErr.errno.toString())
.replace(/\$errmessage\$/g, readErr.message)
)
} else {
res.write(errorPage.notFound.content)
}
return res.end()
}
fs.stat(filename, function (statErr, stats) {
if (statErr) {
res.writeHead(500, { Server: 'Aluminum Wire', 'Content-Type': errorPage.serverError.mimeType })
if (errorPage.serverError.text) {
res.write(
errorPage.serverError.content
.replace(/\$requrl\$/g, req.url)
.replace(/\$osplatform\$/g, os.platform())
.replace(/\$ostype\$/g, os.type())
.replace(/\$osversion\$/g, os.release())
.replace(/\$port\$/g, ports.HTTP.toString())
.replace(/\$errcode\$/g, statErr.code)
.replace(/\$errno\$/, statErr.errno.toString())
.replace(/\$errmessage\$/g, statErr.message)
)
} else {
res.write(errorPage.serverError.content)
}
return res.end()
}
if (typeof req.headers['if-modified-since'] === 'undefined' || (Math.trunc((stats.mtime.getTime()) / 1000) > Math.trunc((new Date(req.headers['if-modified-since']).getTime()) / 1000))) {
res.writeHead(200, { Server: 'Aluminum Wire', 'Content-Type': mimetype, 'Last-Modified': stats.mtime.toUTCString() })
res.write(data)
return res.end()
}
res.writeHead(304, { Server: 'Aluminum Wire' })
return res.end()
}
res.writeHead(200, { 'Content-Type': mimetype })
res.write(data)
return res.end()
)
}
)
}
Expand Down

0 comments on commit 26c4d15

Please sign in to comment.