This package is developed as an easy and quick mean to share files across my LAN with some more abilities like authorization / client upload / client range request.
- User-friendly interactive prompts powered by prompts
- Multithreaded download based on StreamSaver.js
- Composing router logic & cascading middlewares in Koa style
- HTTPS & HTTP over the same port
The one-line way:
npx @edfus/file-server
Alternatively, you can install this package either globally or locally
npm install @edfus/file-server -g
npm install @edfus/file-server
cd node_modules/@edfus/file-server
git clone --depth 1 https://github.com/edfus/file-server
cd file-server
npm install
And then run:
# global
serve
# local
npm run serve
A default HTML main page will be used unless file index.html
exists in the folder to be served.
Available command-line options:
--config [config_path]
: The path to your preferred config location for retriving/creating/updating settings.--password [passwd]
: The optional password for encrypting and decrypting config file. Passwords set by the authorization prompt take priority over this.--no-prompt
: Skip the prompts, use possible or default settings.--no-validate
: Do not check whether a path is valid.--no-fallback
: Exits immediately when any misconfiguration has been found.<folder_name>
: The first unpaired, non-option command line argument will be treated as the<folder_name>
, if exists. Specifying<folder_name>
will skip the prompts, serve what you want directly using possible or default settings.
When a encrypted config is encountered, a To recall your previous configuration, enter the password
prompt will always jump out regardless of the "will-skip-prompts"
options being set or not. Specify --password passwd
explicitly in this case.
Examples:
serve .
npx @edfus/file-server /var/www/localhost/ --config /var/www/docker_volume/config
serve --config /var/www/docker_volume/config --password K3qUrFS+h@G --no-prompt
npm run serve -- --no-prompt
Alias:
-c
:--config [config_path]
-p
:--password [passwd]
-h
:--help
-n
:--no-prompt
-l
,--loose
:--no-validate
-e
,--set-e
:--no-fallback
See files in folder ./env/ for behaviors that you can customize.
Some quick start snippets:
import { App, Serve } from "@edfus/file-server";
const app = new App();
const services = new Serve().mount("./");
for (const service of services)
app.use(service);
// simply sugar for http.createServer(app.callback()).listen();
app.listen(0, "localhost", function () {
console.info(`File server is running at http://localhost:${this.address().port}`);
});
import { App, Serve } from "@edfus/file-server";
const app = new App();
app.prepend(
async (ctx, next) => {
await next();
console.info(
new Date().toLocaleString(),
ctx.ip,
ctx.req.method,
ctx.url,
ctx.res.statusCode
);
}
);
app.use(new Serve().mount("./doc").serveFile).listen(
8080, "localhost", function () {
console.info(`File server is running at http://localhost:${this.address().port}`);
}
);
This package has two named exports:
Class App
is a minimal implementation of Koa.
Following properties are available in ctx for middlewares:
/**
* the prototype from which ctx is created.
* You may add additional properties to ctx by editing app.context
*/
interface BasicContext {
app: App;
/* parameter `properties` not supported */
throw(status?: number, message?: string): void;
/* parameter `properties` not supported */
assert(shouldBeTruthy: any, status?: number, message?: string): void;
}
interface Context extends BasicContext {
req: IncomingMessage;
res: ServerResponse;
state: {
pathname: string;
uriObject: URL;
};
url: string;
secure: boolean;
ip: string;
}
See https://github.com/edfus/file-server/blob/master/file-server.d.ts for more details.
Class Serve
is the core of this package, loosely coupled.
class Serve {
constructor();
implementedMethods: ["GET", "PUT", "HEAD"];
/**
* sugar for
* this.implementedMethods.includes(ctx.req.method)
*
* if (ctx.state.pathname === "/api") {
* switch (ctx.state.uriObject.searchParams.get("action")) {
* case "list":
* case "get-list": return this.getList(ctx);
* case "upload": return this.uploadFile(ctx);
* }
* }
*
* this.serveFile
*/
[Symbol.iterator](): IterableIterator<Middleware>;
_getList(ctx: Context): Promise<void>;
_uploadFile(ctx: Context): Promise<void>;
_serveFile(ctx: Context): Promise<void>;
/**
* sugar for _getList with correct `this` reference
*/
getList(ctx: Context): Promise<void>;
/**
* sugar for _uploadFile with correct `this` reference
*/
uploadFile(ctx: Context): Promise<void>;
/**
* _serveFile with correct `this` reference.
* Will silence errors with status 404
*/
serveFile(ctx: Context): Promise<void>;
/**
* sugar for
* this.pathnameRouter.dir.push(pathname => join(directory, normalize(pathname)));
*
* this.pathnameRouter.file.push(pathname => join(directory, normalize(pathname)));
*/
mount(directory: string): this;
pathnameRouter: Router<string>;
fileResHeadersRouter: Router<string>;
routeThrough<T>(input: T, ...routers: SubRouter<T>): T;
etag(stats: Stats): string;
listCache: Map<string, object>;
mimeCache: Map<string, string>;
}
Serve#pathnameRouter is where you can customize the routing logic. By default, following actions are used.
pathnameRouter = {
map: [
pathname => pathMap.has(pathname) ? pathMap.get(pathname) : pathname,
],
filter: [
// hide all files starting with . in their names
pathname => basename(pathname).startsWith(".") ? false : pathname
],
fs: [
pathname => pathname.replace(/<|>|:|"|\||\?|\*/g, "-")
],
file: [
pathname => pathname.endsWith("/") ? pathname.concat("index.html") : pathname
],
dir: [
pathname => pathname.endsWith("/") ? pathname : pathname.concat("/")
]
};
./lib/stream-saver is a modified version of StreamSaver.js, only browsers compatible with Transferable Streams are supported and a valid SSL certificate is required for service worker registration when serving via https (http is ok, though).
Strict CSP rules are applied for $indexHTML
. Delete lines in Serve#fileResHeadersRouter.CSP
in ./bin/cmd.mjs
if needed.
App#callback
trust proxy set headers
by default (e.g. X-Forwarded-Host, X-Forwarded-For)
HTTP/2 is not supported.
console.error
will be bound to App's intance if no error listener has been attached before invoking App#callback
and a warning message will be displayed. This is the intended behavior inherited from Koa.