-r for serverless rendering

pull/1/head
chris west 4 years ago
parent b527304e67
commit 822e1e0f26

@ -23,31 +23,31 @@
`phd` is a small, easy-to-use gopher server.
Point it at a directory and it'll serve up all its text files,
Point it at a directory and it'll serve up all the text files,
sub-directories, and binary files over Gopher. Any `.gph` files will
be served up as [gopermaps][map] and executable `.gph` files will be
run as a program with their output served to the client, like the
glorious cgi-bin days of yore!
### special files:
### ~ special files ~
- **header.gph**: If it exists in a directory, its content will be
shown above the directory's content. Put ASCII art in it.
- **footer.gph**: Same, but will be shown below a directory's content.
- **index.gph**: Completely replaces a directory's content with what's
in this file.
- **??.gph**: Visiting gopher://yoursite/1/dog/ will try to render
`dog.gph` from disk. Visiting /1/dog.gph will render the raw content
of the .gph file.
- **??.gph**: Visiting `gopher://yoursite/1/dog/` will try to render
`dog.gph` from disk. Visiting `/1/dog.gph` will render the raw
content of the .gph file.
- **.reverse**: If this exists, the directory contents will be listed
in reverse alphanumeric order. Useful for phloggin', if you date
your posts.
Any line in a `.gph` file that doesn't contain tabs (`\t`) and doesn't
start with an `i` will get an `i` automatically prefixed, turning it
into a gopher information item.
Any line in a `.gph` file that doesn't contain tabs (`\t`) will get an
`i` automatically prefixed, turning it into a Gopher information item.
Alternatively, phd supports [geomyidae][gmi] syntax:
For your convenience, phd supports **[geomyidae][gmi]** syntax for
creating links:
This is an info line.
[1|This is a link|/help|server|port]
@ -56,7 +56,10 @@ Alternatively, phd supports [geomyidae][gmi] syntax:
`server` and `port` will get translated into the server and port of
the actively running server, eg `localhost` and `7070`.
### dynamic content:
Any line containing a tab character (`\t`) will be sent as-is to the
client, meaning you can write and serve up raw Gophermap files too.
### ~ dynamic content ~
Any `.gph` file that is marked **executable** with be run as if it
were a standalone program and its output will be sent to the client.
@ -92,7 +95,7 @@ then:
[INFO] |_| |_|_| \__, |\___/| .__/|_| |_|\___|_|
[INFO] |___/ |_|
### ruby on rails:
### ~ ruby on rails ~
`sh` is fun, but for serious work you need a serious scripting
language like Ruby or PHP or Node.JS:
@ -127,14 +130,17 @@ of Gopher!
isizes.gph 276B (null) 127.0.0.1 7070
isrc 224B (null) 127.0.0.1 7070
## usage
## ~ usage ~
Usage:
phd [options] <root directory>
Options:
-p, --port Port to bind to.
-h, --host Hostname to use when generating links.
-r, --render SELECTOR Render and print SELECTOR to stdout only.
-p, --port Port to bind to. [Default: {port}]
-h, --host Hostname for links. [Default: {host}]
Other flags:
@ -146,9 +152,10 @@ of Gopher!
phd ./path/to/site # Serve directory over port 7070.
phd -p 70 docs # Serve 'docs' directory on port 70
phd -h gopher.com # Serve current directory over port 7070
# using hostname "gopher.com"
# using hostname 'gopher.com'
phd -r / ./site # Render local gopher site to stdout.
## installation
## ~ installation ~
On macOS you can install with [Homebrew](https://brew.sh/):
@ -161,22 +168,25 @@ gopher://phkt.io/1/releases/phd and https://github.com/xvxx/phd/releases:
- [phd-v0.1.9-linux-armv7.tar.gz (Raspberry Pi)][1]
- [phd-v0.1.9-macos.zip][2]
Just unzip/untar the `phd` program into your \$PATH and get going!
Just unzip/untar the `phd` program into your `$PATH` and get going!
If you have **[cargo][rustup]**, you can install the crate directly:
cargo install phd
## development
## ~ development ~
cargo run -- ./path/to/gopher/site
## resources
## ~ resources ~
- gopher://bitreich.org/1/scm/geomyidae/files.gph
- https://github.com/gophernicus/gophernicus/blob/master/README.Gophermap
- https://gopher.zone/posts/how-to-gophermap/
- [rfc 1436](https://tools.ietf.org/html/rfc1436)
## todo
## ~ todo ~
- [ ] script/serverless mode
- [ ] systemd config, or something
- [ ] TLS support
- [ ] man page
@ -187,4 +197,5 @@ Just unzip/untar the `phd` program into your \$PATH and get going!
[1]: https://github.com/xvxx/phd/releases/download/v0.1.9/phd-v0.1.9-linux-armv7.tar.gz
[2]: https://github.com/xvxx/phd/releases/download/v0.1.9/phd-v0.1.9-macos.zip
[map]: https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu
[gmi]: gopher://bitreich.org/1/scm/geomyidae
[gmi]: http://r-36.net/scm/geomyidae/
[rustup]: https://rustup.rs

@ -5,17 +5,25 @@ const DEFAULT_HOST: &str = "127.0.0.1";
const DEFAULT_PORT: u16 = 7070;
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let args = std::env::args().skip(1).collect::<Vec<_>>();
let mut args = args.iter();
let mut root = ".";
let mut iter = args.iter();
let mut host = DEFAULT_HOST;
let mut port = DEFAULT_PORT;
while let Some(arg) = iter.next() {
let mut render = "";
while let Some(arg) = args.next() {
match arg.as_ref() {
"--version" | "-v" | "-version" => return print_version(),
"--help" | "-help" => return print_help(),
"--render" | "-render" | "-r" => {
if let Some(path) = args.next() {
render = path;
} else {
render = "/";
}
}
"--port" | "-p" | "-port" => {
if let Some(p) = iter.next() {
if let Some(p) = args.next() {
port = p
.parse()
.map_err(|_| {
@ -26,15 +34,15 @@ fn main() {
}
}
"-h" => {
if let Some(h) = iter.next() {
host = h;
if let Some(h) = args.next() {
host = &h;
} else {
return print_help();
}
}
"--host" | "-host" => {
if let Some(h) = iter.next() {
host = h;
if let Some(h) = args.next() {
host = &h;
}
}
_ => {
@ -42,12 +50,19 @@ fn main() {
eprintln!("unknown flag: {}", arg);
process::exit(1);
} else {
root = arg;
root = &arg;
}
}
}
}
if !render.is_empty() {
return match phd::server::render(host, port, root, &render) {
Ok(out) => println!("{}", out),
Err(e) => eprintln!("{}", e),
};
}
if let Err(e) = phd::server::start(host, port, root) {
eprintln!("{}", e);
}
@ -61,13 +76,23 @@ fn print_help() {
Options:
-p, --port Port to bind to. [Default: {port}]
-h, --host Hostname when generating links. [Default: {host}]
-r, --render SELECTOR Render and print SELECTOR to stdout only.
-p, --port Port to bind to. [Default: {port}]
-h, --host Hostname for links. [Default: {host}]
Other flags:
-h, --help Print this screen.
-v, --version Print phd version.",
-v, --version Print phd version.
Examples:
phd ./path/to/site # Serve directory over port 7070.
phd -p 70 docs # Serve 'docs' directory on port 70
phd -h gopher.com # Serve current directory over port 7070
# using hostname 'gopher.com'
phd -r / ./site # Render local gopher site to stdout.
",
host = DEFAULT_HOST,
port = DEFAULT_PORT,
);

@ -1,7 +1,7 @@
//! A simple multi-threaded Gopher server.
use crate::{color, Request, Result};
use gophermap::{GopherMenu, ItemType};
use gophermap::ItemType;
use std::{
cmp::Ordering,
fs::{self, DirEntry},
@ -62,7 +62,7 @@ pub fn start(host: &str, port: u16, root: &str) -> Result<()> {
}
/// Reads from the client and responds.
fn accept(stream: TcpStream, mut req: Request) -> Result<()> {
fn accept(mut stream: TcpStream, mut req: Request) -> Result<()> {
let reader = BufReader::new(&stream);
let mut lines = reader.lines();
if let Some(Ok(line)) = lines.next() {
@ -75,15 +75,24 @@ fn accept(stream: TcpStream, mut req: Request) -> Result<()> {
color::Reset
);
req.parse_request(&line);
write_response(&stream, req)?;
write_response(&mut stream, req)?;
}
Ok(())
}
/// Render a response to a String.
pub fn render(host: &str, port: u16, root: &str, selector: &str) -> Result<String> {
let mut req = Request::from(host, port, root)?;
req.parse_request(&selector);
let mut out = vec![];
write_response(&mut out, req)?;
Ok(String::from_utf8_lossy(&out).into())
}
/// Writes a response to a client based on a Request.
fn write_response<'a, W>(w: &'a W, mut req: Request) -> Result<()>
fn write_response<W>(w: &mut W, mut req: Request) -> Result<()>
where
&'a W: Write,
W: Write,
{
let path = req.file_path();
@ -121,9 +130,9 @@ where
}
/// Send a directory listing (menu) to the client based on a Request.
fn write_dir<'a, W>(w: &'a W, req: Request) -> Result<()>
fn write_dir<W>(w: &mut W, req: Request) -> Result<()>
where
&'a W: Write,
W: Write,
{
let path = req.file_path();
if !fs_exists(&path) {
@ -144,7 +153,6 @@ where
)?;
}
let mut menu = GopherMenu::with_write(w);
let rel_path = req.relative_file_path();
// show directory entries
@ -161,8 +169,10 @@ where
rel_path.trim_end_matches('/'),
file_name.to_string_lossy()
);
menu.write_entry(
file_type(&entry),
write!(
w,
"{}{}\t{}\t{}\t{}\r\n",
file_type(&entry).to_char(),
&file_name.to_string_lossy(),
&path,
&req.host,
@ -182,7 +192,8 @@ where
)?;
}
menu.end()?;
write!(w, ".\r\n");
println!(
"{}│{} Server reply:\t{}DIR {}{}{}",
color::Green,
@ -196,13 +207,13 @@ where
}
/// Send a file to the client based on a Request.
fn write_file<'a, W>(mut w: &'a W, req: Request) -> Result<()>
fn write_file<W>(w: &mut W, req: Request) -> Result<()>
where
&'a W: Write,
W: Write,
{
let path = req.file_path();
let mut f = fs::File::open(&path)?;
io::copy(&mut f, &mut w)?;
io::copy(&mut f, w)?;
println!(
"{}│{} Server reply:\t{}FILE {}{}{}",
color::Green,
@ -216,9 +227,9 @@ where
}
/// Send a gophermap (menu) to the client based on a Request.
fn write_gophermap<'a, W>(mut w: &'a W, req: Request) -> Result<()>
fn write_gophermap<W>(w: &mut W, req: Request) -> Result<()>
where
&'a W: Write,
W: Write,
{
let path = req.file_path();
@ -230,7 +241,7 @@ where
};
for line in reader.lines() {
w.write_all(gph_line_to_gopher(line, &req).as_bytes())?;
write!(w, "{}", gph_line_to_gopher(line, &req))?;
}
println!(
"{}│{} Server reply:\t{}MAP {}{}{}",
@ -298,9 +309,9 @@ fn gph_line_to_gopher(line: &str, req: &Request) -> String {
line
}
fn write_not_found<'a, W>(mut w: &'a W, req: Request) -> Result<()>
fn write_not_found<W>(w: &mut W, req: Request) -> Result<()>
where
&'a W: Write,
W: Write,
{
let line = format!("3Not Found: {}\t/\tnone\t70\r\n", req.selector);
println!(
@ -310,7 +321,7 @@ where
req.relative_file_path(),
color::Reset,
);
w.write_all(line.as_bytes())?;
write!(w, "{}", line)?;
Ok(())
}

Loading…
Cancel
Save