ianthehenry / httprintf

Some scripts for creating a development HTTP server.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

httprintf

There are lots of ways to start up a local server when you're doing web client development. See SimpleHTTPServer, knod, gulp, brunch, and countless others.

Use them, if you can.

This is my personal invented-here tool that scratches an itch that only I have.

httprintf has very few dependencies. It is written entirely in shell. It only uses common executables, like file and awk. Things you have. I assume.

It's probably not portable and probably only works in bash. It, you know, weakly tries.

But why?

For fun! Though there is a more reasonable answer: I really liked the look of redo, and wanted to try it out for web stuff.

But I didn't want to manually recompile my assets, nor recompile on file save (to prevent the frustrating situation where you request an asset after saving the source file but before you finish generating the target).

Thus it fills a tiny gap between "static file server" and "dynamic HTTP server," and allows me to redo-ifchanged my files at request time.

Reference

The server

I recommend that you use this in conjunction with ncat, if you actually want to talk to it from a web browser:

ncat -kl localhost 8080 -c http-handler

Linux netcats may work as well, but BSD netcats (including the one provided with macOS) will not. If you can get it working in a way that can respond to multiple requests in rapid succession, please tell me. I was unable to figure it out.

http-handler

This script expects an HTTP request on stdin, and will try to produce one on stdout.

First it parses the HTTP method and path, then it tries to find a handler file to invoke. It will look for a file matching the exact verb anywhere in the path, starting from the directory of the file specified. If that's not found, it will repeat the search for a file called ANY.

So, for a request like DELETE /foo/bar/baz.json HTTP/1.1, it will try to invoke:

./foo/bar/DELETE ./baz.json         DELETE
./foo/DELETE     ./bar/baz.json     DELETE
./DELETE         ./foo/bar/baz.json DELETE
./foo/bar/ANY    ./baz.json         DELETE
./foo/ANY        ./bar/baz.json     DELETE
./ANY            ./foo/bar/baz.json DELETE

In that order. If it's a request for a directory, like POST /foo/bars/ HTTP/1.1, it will try:

./foo/bars/POST .           POST
./foo/POST      ./bars/     POST
./POST          ./foo/bars/ POST
./foo/bars/ANY  .           POST
./foo/ANY       ./bars/     POST
./ANY           ./foo/bars/ POST

If no handler is found, it will send a 405 with an empty Allow header. If this is emotionally upsetting to you, provide a root ANY that sets the correct Allow header.

It will log some information about each request and response to stderr.

If it invokes a handler that returns a non-zero exit code, chaos will reign.

It won't currently send anything else to the script. Want to do something based on HTTP headers, or the request body? You probably want to use something real.

http-handler makes no attempt to protect against escaping the directory from which it's invoked. GET /../../passwords.txt will work just fine. This is by design. You could attempt to sanitize the path in your handler script, but if you're using http-handler for anything but local development you're doing it wrong.

Examples

http-handler has no default implementations for any methods. A simple static server might look like this, with a single root GET:

#!/usr/bin/sh
http-file "$1"

The HTTP-generating shell scripts

Sorted roughly in order of usefulness.

http-file (helper)

Prints an entire HTTP response to stdout based on the filepath provided.

http-file path/to/index.html

Sends 200 if the file exists and 404 otherwise. A future version may send 307s for symlinks because that would be pretty whimsical.

If the file exists, it sets the Content-Type header using http-guess-mime-type.

http-response (helper)

Prints an entire HTTP response to stdout using a body supplied via stdin.

http-response application/json <foo.json

The first argument specifies the Content-Type, which is required. No Content-Length header is sent when using http-response.

http-status (primitive)

Prints an HTTP status line to stdout.

http-status 200
http-status 418 "I'm a teapot"

The status code is required. If the reason is omitted, http-status will supply a reason based on RFC 7231. It is an error to call http-status without a status code, or to call it with an unknown status code without specifying a reason.

http-header (primitive)

Prints an HTTP header line to stdout.

http-header "Content-Type" "application/json"

It is an error to omit either the header name or value.

http-body (helper)

Reads a file and prints a Content-Length header, the header separator line, and the entire contents of stdin to stdout.

http-body index.html

May be helpful in cases where http-file is not sufficient.

I'd love for it to take input on stdin, but I can't figure out how to make that work with binary files without first creating a temporary file.

http-guess-mime-type (helper)

Prints the guessed MIME type of the specified file to stdout, followed by a newline.

http-guess-mime-type index.html
http-guess-mime-type path/to/layout.css
http-guess-mime-type app.js

Tries to guess based on the file's extension, with a dictionary of commonly served types. If that fails, it falls back to using file -I.

The file doesn't need to exist so long as the extension is recognized, but the fallback detection requires the file to exist.

http-begin-body (primitive)

Prints \r\n to stdout.

http-begin-body

A trivial helper for separating the response headers from the response body. Unnecessary if using http-body. Takes no arguments.

TODO

  • clean up http-handler
  • pass request headers and body to handler scripts
  • test that anything actually works

About

Some scripts for creating a development HTTP server.

License:MIT License


Languages

Language:Shell 100.0%