devd

package module
v0.0.0-...-c1a3bfb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 27, 2020 License: MIT Imports: 37 Imported by: 0

README

Travis Build Status

devd: a local webserver for developers

screenshot

Install

Go to the releases page, download the package for your OS, and copy the binary to somewhere on your PATH.

If you have a working Go installation, you can also say

go get github.com/cortesi/devd/cmd/devd

Quick start

Serve the current directory, open it in the browser (-o), and livereload when files change (-l):

devd -ol .

Reverse proxy to http://localhost:8080, and livereload when any file in the src directory changes:

devd -w ./src http://localhost:8080

Using devd with modd

Modd is devd's sister project - a dev tool that runs commands and manages daemons in response to filesystem changes. Devd can be used with modd to rebuild a project and reload the browser when filesystem changes are detected.

Here's a quick example of a simple modd.conf file to illustrate.

src/** {
    prep: render ./src ./rendered
}

rendered/*.css ./rendered/*.html {
    daemon: devd -m ./rendered
}

The first block runs the render script whenever anything in the src directory changes. The second block starts a devd instance, and triggers livereload with a signal whenever a .css or .html file in the rendered directory changes.

See the modd project page for details.

Features

Cross-platform and self-contained

Devd is a single statically compiled binary with no external dependencies, and is released for macOS, Linux and Windows. Don't want to install Node or Python in that light-weight Docker instance you're hacking in? Just copy over the devd binary and be done with it.

Designed for the terminal

This means no config file, no daemonization, and logs that are designed to be read in the terminal by a developer. Logs are colorized and log entries span multiple lines. Devd's logs are detailed, warn about corner cases that other daemons ignore, and can optionally include things like detailed timing information and full headers.

Convenient

To make quickly firing up an instance as simple as possible, devd automatically chooses an open port to run on (unless it's specified), and can open a browser window pointing to the daemon root for you (the -o flag in the example above). It also has utility features like the -s flag, which auto-generates a self-signed certificate for devd, stores it in ~/.devd.certs and enables TLS all in one step.

Livereload

When livereload is enabled, devd injects a small script into HTML pages, just before the closing head tag. The script listens for change notifications over a websocket connection, and reloads resources as needed. No browser addon is required, and livereload works even for reverse proxied apps. If only changes to CSS files are seen, devd will only reload external CSS resources, otherwise a full page reload is done. This serves the current directory with livereload enabled:

devd -l .

You can also trigger livereload for files that are not being served, letting you reload reverse proxied applications when source files change. So, this command watches the src directory tree, and reverse proxies to a locally running application:

devd -w ./src http://localhost:8888

The -x flag excludes files from triggering livereload based on a pattern specification. The following command disables livereload for all files with the ".less" extension:

devd -x "**.less" -l .

When livereload is enabled (with the -L, -l or -w flags), devd responds to a SIGHUP by issuing a livereload notice to all connected browsers. This allows external tools, like devd's sister project modd, to trigger livereload. If livereload is not enabled, SIGHUP causes the daemon to exit.

The closing head tag must be found within the first 30kb of the remote file, otherwise livereload is disabled for the file.

Reverse proxy + static file server + flexible routing

Modern apps tend to be collections of web servers, and devd caters for this with flexible reverse proxying. You can use devd to overlay a set of services on a single domain, add livereload to services that don't natively support it, add throttling and latency simulation to existing services, and so forth.

Here's a more complicated example showing how all this ties together - it overlays two applications and a tree of static files. Livereload is enabled for the static files (-l) and also triggered whenever source files for reverse proxied apps change (-w):

devd -l \
-w ./src/ \
/=http://localhost:8888 \
/api/=http://localhost:8889 \
/static/=./assets

The route specification syntax is compact but powerful enough to cater for most use cases.

Light-weight virtual hosting

Devd uses a dedicated domain - devd.io - to do simple virtual hosting. This domain and all its subdomains resolve to 127.0.0.1, which we use to set up virtual hosting without any changes to /etc/hosts or other local configuration. Route specifications that don't start with a leading / are taken to be subdomains of devd.io. So, the following command serves a static site from devd.io, and reverse proxies a locally running app on api.devd.io:

devd ./static api=http://localhost:8888
Latency and bandwidth simulation

Want to know what it's like to use your fancy 5mb HTML5 app from a mobile phone in Botswana? Look up the bandwidth and latency here, and invoke devd like so (making sure to convert from kilobits per second to kilobytes per second and account for the location of your server):

devd -d 114 -u 51 -n 275 .

Devd tries to be reasonably accurate in simulating bandwidth and latency - it uses a token bucket implementation for throttling, properly handles concurrent requests, and chunks traffic up so data flow is smooth.

Routes

The devd command takes one or more route specifications as arguments. Routes have the basic format root=endpoint. Roots can be fixed, like "/favicon.ico", or subtrees, like "/images/" (note the trailing slash). Endpoints can be filesystem paths or URLs to upstream HTTP servers.

Here's a route that serves the directory ./static under /assets on the server:

/assets/=./static

To use a devd.io subdomain (which will resolve to 127.0.0.1), just add it to the the front of the root specification. We recognize subdomains by the fact that they don't start with a leading /. So, this route serves the /static directory under static.devd.io/assets:

static/assets=./static

Reverse proxy specifications are similar, but the endpoint specification is a URL. The following serves a local URL from the root app.devd.io/login:

app/login=http://localhost:8888

If the root specification is omitted, it is assumed to be "/", i.e. a pattern matching all paths. So, a simple directory specification serves the directory tree directly under devd.io:

devd ./static

Similarly, a simple reverse proxy can be started like this:

devd http://localhost:8888

There is also a shortcut for reverse proxying to localhost:

devd :8888

Serving default content for files not found

The --notfound flag can be passed multiple times, and specifies a set of routes that are consulted when a requested file is not found by the static file server. The basic syntax is root=path, where root has the same semantics as route specification. As with routes, the root= component is optional, and if absent is taken to be equal to /. The path is always relative to the static directory being served. When it starts with a leading slash (/), devd will only look for a replacement file in a single location relative to the root of the tree. Otherwise, it will search for a matching file by joining the specified path with all path components up to the root of the tree.

Let's illustrate this with an example. Say we have a /static directory as follows:

./static
├── bar
│   └── index.html
└── index.html

We can specify that devd should look for an index.html anywhere on the path to the root of the static tree as follows:

devd --notfound index.html  /static

Now, the following happens:

  • A request for /nonexistent.html returns the contents of /index.html
  • A request for /bar/nonexistent.html returns the contents of /bar/index.html
  • A request for /foo/bar/voing/index.html returns the contents of /index.html

We could instead specify an absolute path in the route, in which case the contents of /index.html would be returned for all the examples above:

devd --notfound /index.html  /static

Devd won't serve an over-ride page if the expected type of the incoming request doesn't match that of the override specification. We do this by looking at the file extension and expected MIME types of the over-ride and request, defaulting to text/html if the type couldn't be positively established. This prevents issues where, for instance, an HTML over-ride page might be served where images are expected.

Excluding files from livereload

The -x flag supports the following terms:

Term Meaning
* matches any sequence of non-path-separators
** matches any sequence of characters, including path separators
? matches any single non-path-separator character
[class] matches any single non-path-separator character against a class of characters
{alt1,...} matches a sequence of characters if one of the comma-separated alternatives matches

Any character with a special meaning can be escaped with a backslash (\). Character classes support the following:

Class Meaning
[abc] matches any single character within the set
[a-z] matches any single character in the range
[^class] matches any single character which does not match the class

About reverse proxying

Devd does not validate upstream SSL certificates when reverse proxying. For our use case, development servers will usually be running locally, often with self-signed certificates for testing. You shouldn't use devd in cases where upstream cert validation matters.

The X-Forwarded-Host and X-Forwarded-Proto headers are set to the devd server's address and protocol for reverse proxied traffic. You might need to enable support for this in your application for redirects and the like to work correctly.

Development

The scripts used to build this package for distribution can be found here. External packages are vendored using dep.

Documentation

Index

Constants

View Source
const (
	// Version is the current version of devd
	Version = "0.9"
)

Variables

This section is empty.

Functions

func GenerateCert

func GenerateCert(dst string) error

GenerateCert generates a self-signed certificate bundle for devd

func HandleNotFound

func HandleNotFound(templates *template.Template) httpctx.Handler

HandleNotFound handles pages not found. In particular, this handler is used when we have no matching route for a request. This also means it's not useful to inject the livereload paraphernalia here.

func LogHeader

func LogHeader(log termlog.Logger, h http.Header)

LogHeader logs a header

func WatchPaths

func WatchPaths(paths, excludePatterns []string, reloader livereload.Reloader, log termlog.Logger) error

WatchPaths watches a set of paths, and broadcasts changes through reloader.

func WatchRoutes

func WatchRoutes(routes RouteCollection, reloader livereload.Reloader, excludePatterns []string, log termlog.Logger) error

WatchRoutes watches the route collection, and broadcasts changes through reloader.

Types

type Credentials

type Credentials struct {
	// contains filtered or unexported fields
}

Credentials is a simple username/password pair

func CredentialsFromSpec

func CredentialsFromSpec(spec string) (*Credentials, error)

CredentialsFromSpec creates a set of credentials from a spec

type Devd

type Devd struct {
	Routes RouteCollection

	// Shaping
	Latency       int
	DownKbps      uint
	UpKbps        uint
	ServingScheme string

	// Add headers
	AddHeaders *http.Header

	// Livereload and watch static routes
	LivereloadRoutes bool
	// Livereload, but don't watch static routes
	Livereload bool
	WatchPaths []string
	Excludes   []string

	// Add Access-Control-Allow-Origin header
	Cors bool

	// Logging
	IgnoreLogs []*regexp.Regexp

	// Password protection
	Credentials *Credentials
	// contains filtered or unexported fields
}

Devd represents the devd server options

func (*Devd) AddIgnores

func (dd *Devd) AddIgnores(specs []string) error

AddIgnores adds log ignore patterns to the server

func (*Devd) AddRoutes

func (dd *Devd) AddRoutes(specs []string, notfound []string) error

AddRoutes adds route specifications to the server

func (*Devd) HasLivereload

func (dd *Devd) HasLivereload() bool

HasLivereload tells us if livereload is enabled

func (*Devd) Router

func (dd *Devd) Router(logger termlog.TermLog, templates *template.Template) (http.Handler, error)

Router constructs the main Devd router that serves all requests

func (*Devd) Serve

func (dd *Devd) Serve(address string, port int, certFile string, logger termlog.TermLog, callback func(string)) error

Serve starts the devd server. The callback is called with the serving URL just before service starts.

func (*Devd) WrapHandler

func (dd *Devd) WrapHandler(log termlog.TermLog, next httpctx.Handler) http.Handler

WrapHandler wraps an httpctx.Handler in the paraphernalia needed by devd for logging, latency, and so forth.

type ResponseLogWriter

type ResponseLogWriter struct {
	Log     termlog.Logger
	Resp    http.ResponseWriter
	Flusher http.Flusher
	Timer   *timer.Timer
	// contains filtered or unexported fields
}

ResponseLogWriter is a ResponseWriter that logs

func (*ResponseLogWriter) Flush

func (rl *ResponseLogWriter) Flush()

func (*ResponseLogWriter) Header

func (rl *ResponseLogWriter) Header() http.Header

Header returns the header map that will be sent by WriteHeader. Changing the header after a call to WriteHeader (or Write) has no effect.

func (*ResponseLogWriter) Write

func (rl *ResponseLogWriter) Write(data []byte) (int, error)

Write writes the data to the connection as part of an HTTP reply. If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) before writing the data. If the Header does not contain a Content-Type line, Write adds a Content-Type set to the result of passing the initial 512 bytes of written data to DetectContentType.

func (*ResponseLogWriter) WriteHeader

func (rl *ResponseLogWriter) WriteHeader(code int)

WriteHeader sends an HTTP response header with status code. If WriteHeader is not called explicitly, the first call to Write will trigger an implicit WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly used to send error codes.

type Route

type Route struct {
	Host     string
	Path     string
	Endpoint endpoint
}

Route is a mapping from a (host, path) tuple to an endpoint.

func (Route) MuxMatch

func (f Route) MuxMatch() string

MuxMatch produces a match clause suitable for passing to a Mux

func (Route) Watch

func (r Route) Watch(
	ch chan []string,
	excludePatterns []string,
	log termlog.Logger,
) (*moddwatch.Watcher, error)

Watch watches an endpoint for changes, if it supports them.

type RouteCollection

type RouteCollection map[string]Route

RouteCollection is a collection of routes

func (RouteCollection) Add

func (f RouteCollection) Add(value string, notfound []string) error

Add a route to the collection

func (*RouteCollection) String

func (f *RouteCollection) String() string

Directories

Path Synopsis
cmd
Package fileserver provides a filesystem HTTP handler, based on the built-in Go FileServer.
Package fileserver provides a filesystem HTTP handler, based on the built-in Go FileServer.
Package httpctx provides a context-aware HTTP handler adaptor
Package httpctx provides a context-aware HTTP handler adaptor
Package inject gives the ability to copy data and inject a payload before a specified marker.
Package inject gives the ability to copy data and inject a payload before a specified marker.
Package livereload allows HTML pages to be dynamically reloaded.
Package livereload allows HTML pages to be dynamically reloaded.
Package reverseproxy is a reverse proxy implementation based on the built-in httuptil.Reverseproxy.
Package reverseproxy is a reverse proxy implementation based on the built-in httuptil.Reverseproxy.
Package ricetemp makes templates from a ricebox.
Package ricetemp makes templates from a ricebox.
Package slowdown provides an implementation of net.Listener that limits bandwidth.
Package slowdown provides an implementation of net.Listener that limits bandwidth.
Package timer adds HTTP request and response timing information to a context.
Package timer adds HTTP request and response timing information to a context.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL