///|
priv suberror UsageError {
UsageError(String)
}
///|
let command : @argparse.Command = Command(
"serve",
about="Serve regular files and directory index.html documents over HTTP.",
version="serve " + MODULE_VERSION,
options=[
OptionArg(
"host",
long="host",
about="Numeric IPv4 or IPv6 listen address.",
default_values=["127.0.0.1"],
),
OptionArg(
"port",
short='p',
long="port",
about="Listen port. Use 0 to choose an available port.",
default_values=["8000"],
allow_hyphen_values=true,
),
],
positionals=[
PositionArg("directory", about="Directory to serve.", default_values=["."]),
],
)
///|
fn options(args : @argparse.Matches) -> (String, @socket.Addr) raise {
let host = args.values["host"][0]
let raw_port = args.values["port"][0]
let port = @string.parse_int(raw_port) catch {
_ => raise UsageError("invalid port: " + raw_port)
}
guard port >= 0 && port <= 65535 else {
raise UsageError("invalid port: " + raw_port)
}
let address = if host.contains(":") {
"[" + host + "]:" + port.to_string()
} else {
host + ":" + port.to_string()
}
(args.values["directory"][0], @socket.Addr::parse(address))
}
///|
async fn main {
let args = match command.parse_outcome(argv=@env.args()[1:]) {
Parsed(args) => args
Help(text) | Version(text) => {
@stdio.stdout.write(text + "\n")
return
}
Failed(error) => {
@stdio.stderr.write(error.to_string())
@sys.exit(1)
return
}
}
try {
let (directory, address) = options(args)
let root = @fs.realpath(directory)
guard @fs.kind(root) is Directory else {
fail("not a directory: " + directory)
}
let server = @http.Server(address)
defer server.close()
@stdio.stderr.write(
"Serving " + root + " on http://" + server.addr.to_string() + "\n",
)
server.run_forever(
(request, _body, connection) => serve_file(root, request, connection),
max_connections=64,
)
} catch {
UsageError(message) => {
@stdio.stderr.write("serve: " + message + "\n")
@sys.exit(1)
}
error => {
@stdio.stderr.write("serve: " + error.to_string() + "\n")
@sys.exit(1)
}
}
}