easyhttpserver

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2020 License: 0BSD Imports: 13 Imported by: 0

README

Easily run Let's Encrypt-enabled HTTP server in Go

Documentation on Go Dev

Production-ready HTTP/HTTPS server in a few lines of code.

Modes:

  • development mode (http + dev port);
  • standalone production mode (http + https + obtains certificates from Let's Encrypt);
  • slave production mode (http + custom port), this runs behind your reverse proxy like nginx, Apache, Caddy or on PaaS like Heroku.

Features:

  • one call to auto-detect the mode and start both HTTP and HTTPS servers;
  • one line to read configuration from Heroku-style environment variables (12-factor), or provide a custom configuration your own way;
  • optional graceful shutdown on Ctrl-C, SIGKILL, SIGHUP.

Usage

Recommended for 12-factor apps, load all options from environment variables:

import (
    "github.com/andreyvit/easyhttpserver"
)

func main() {
    serverOpt := easyhttpserver.Options{
        DefaultDevPort:          3001,
        GracefulShutdownTimeout: 2 * time.Second, // no long-lived requests
    }

    // loads HOST, LETSENCRYPT_ENABLED, etc from environment
    err := serverOpt.LoadEnv()
    if err != nil {
        log.Fatalf("** ERROR: invalid configuration: %v", err)
    }

    srv, err := easyhttpserver.Start(http.HandlerFunc(helloWorld), serverOpt)
    if err != nil {
        log.Fatalf("** ERROR: server startup: %v", err)
    }

    // shut down on Ctrl-C, SIGKILL, SIGHUP
    easyhttpserver.InterceptShutdownSignals(srv.Shutdown)

    log.Printf("HelloWorld server running at %s", strings.Join(srv.Endpoints(), ", "))
    err = srv.Wait()
    if err != nil {
        log.Fatalf("** ERROR: %v", err)
    }
}

Or specify the options manually:

import (
    "github.com/andreyvit/easyhttpserver"
)

func main() {
    srv, err := easyhttpserver.Start(http.HandlerFunc(helloWorld), easyhttpserver.Options{
        DefaultDevPort:          3001,
        GracefulShutdownTimeout: 2 * time.Second, // no long-lived requests
        Host:                    "myhost.example.com",
        LetsEncrypt:             true,
        LetsEncryptEmail:        "you@example.com",
        LetsEncryptCacheDir:     "~/.local/share/easyhttpserver_example/",
    })
    if err != nil {
        log.Fatalf("** ERROR: server startup: %v", err)
    }

    // shut down on Ctrl-C, SIGKILL, SIGHUP
    easyhttpserver.InterceptShutdownSignals(srv.Shutdown)

    log.Printf("HelloWorld server running at %s", strings.Join(srv.Endpoints(), ", "))
    err = srv.Wait()
    if err != nil {
        log.Fatalf("** ERROR: %v", err)
    }
}

0BSD License

Copyright 2020 Andrey Tarantsov.

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Documentation

Overview

easyhttpserver runs production-ready, Let's Encrypt-enabled HTTP server in a few lines of code.

Features HTTP, optional HTTPS with Let's Encrypt, graceful shutdown, Heroku-style 12-factor configuration, and development mode.

Example
package main

import (
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/andreyvit/easyhttpserver"
)

func main() {
	// normally you'd set up the environment externally, and create the dir manually
	os.Setenv("LETSENCRYPT_ENABLED", "1")
	os.Setenv("LETSENCRYPT_EMAIL", "you@example.com")                            // set your email here
	os.Setenv("LETSENCRYPT_CACHE_DIR", "~/.local/share/easyhttpserver_example/") // set your dir (mod 0700) here
	os.Setenv("HOST", "myhost.example.com")                                      // set HTTPS hostname to respond to
	must(os.MkdirAll(os.Getenv("LETSENCRYPT_CACHE_DIR"), 0700))

	serverOpt := easyhttpserver.Options{
		DefaultDevPort:          3099,
		GracefulShutdownTimeout: 2 * time.Second, // no long-lived requests
	}
	must(serverOpt.LoadEnv()) // loads HOST, LETSENCRYPT_ENABLED, etc from environment

	srv, err := easyhttpserver.Start(http.HandlerFunc(helloWorld), serverOpt)
	must(err)

	easyhttpserver.InterceptShutdownSignals(srv.Shutdown) // shut down on Ctrl-C, SIGKILL, SIGHUP
	after500ms(srv.Shutdown)                              // end test after 500 ms, real servers don't do this

	fmt.Printf("HelloWorld server running at %s\n", strings.Join(srv.Endpoints(), ", "))
	must(srv.Wait())

}

func helloWorld(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}

func after500ms(f func()) {
	go func() {
		time.Sleep(500 * time.Millisecond)
		f()
	}()
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}
Output:

HelloWorld server running at https://myhost.example.com
Example (DefaultDevServer)
package main

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/andreyvit/easyhttpserver"
)

func main() {
	srv, err := easyhttpserver.Start(http.HandlerFunc(helloWorld), easyhttpserver.Options{
		DefaultDevPort:          3099,
		GracefulShutdownTimeout: 2 * time.Second, // no long-lived requests
	})
	must(err)

	easyhttpserver.InterceptShutdownSignals(srv.Shutdown) // shut down on Ctrl-C, SIGKILL, SIGHUP
	after500ms(srv.Shutdown)                              // end test after 500 ms, real servers don't do this

	fmt.Printf("HelloWorld server running at %s\n", strings.Join(srv.Endpoints(), ", "))
	must(srv.Wait())

}

func helloWorld(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}

func after500ms(f func()) {
	go func() {
		time.Sleep(500 * time.Millisecond)
		f()
	}()
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}
Output:

HelloWorld server running at http://localhost:3099
Example (ManualConfig)
package main

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/andreyvit/easyhttpserver"
)

func main() {
	srv, err := easyhttpserver.Start(http.HandlerFunc(helloWorld), easyhttpserver.Options{
		DefaultDevPort:          3099,
		GracefulShutdownTimeout: 2 * time.Second, // no long-lived requests
		Host:                    "myhost.example.com",
		LetsEncrypt:             true,
		LetsEncryptEmail:        "you@example.com",
		LetsEncryptCacheDir:     "~/.local/share/easyhttpserver_example/",
	})
	must(err)

	easyhttpserver.InterceptShutdownSignals(srv.Shutdown) // shut down on Ctrl-C, SIGKILL, SIGHUP
	after500ms(srv.Shutdown)                              // end test after 500 ms, real servers don't do this

	fmt.Printf("HelloWorld server running at %s\n", strings.Join(srv.Endpoints(), ", "))
	must(srv.Wait())

}

func helloWorld(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}

func after500ms(f func()) {
	go func() {
		time.Sleep(500 * time.Millisecond)
		f()
	}()
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}
Output:

HelloWorld server running at https://myhost.example.com

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func InterceptShutdownSignals

func InterceptShutdownSignals(shutdown func())

InterceptShutdownSignals invokes the given function the first time INT (Ctrl-C), KILL or HUP signal is received. This only happens once; the next signal received will kill the app.

Types

type Options

type Options struct {

	// Log is a log.Printf-style function to output logs
	Log func(format string, v ...interface{})
	// DefaultDevPort is the HTTP port to use when running on localhost.
	DefaultDevPort int
	// GracefulShutdownTimeout is the time to allow existing requests to complete
	// when trying to shut down gracefully. After this delay, all existing
	// connections are killed.
	GracefulShutdownTimeout time.Duration

	// Port sets the HTTP port to use. Let's Encrypt mode requires port 80.
	Port int
	// Host sets the domain name to provide HTTPS certificates for.
	Host string
	// LetsEncrypt is the master toggle that enables Let's Encrypt.
	LetsEncrypt bool
	// LetsEncryptCacheDir is the directory to store Let's Encrypt certificates
	// and keys in. This directory should be secured as much as possible.
	LetsEncryptCacheDir string
	// LetsEncryptEmail is the email address to use for Let's Encrypt certificates.
	// Let's Encrypt might send important notifications to this email.
	LetsEncryptEmail string

	// IsLocalDevelopmentHost signals that Host is a localhost address. In this
	// mode, Port defaults to DefaultDevPort, and scheme is http. Incompatible
	// with LetsEncrypt mode.
	IsLocalDevelopmentHost bool
	// PrimaryScheme is either https or http, depending on whether Let's Encrypt
	// is enabled.
	PrimaryScheme string
}

Options provide the necessary settings for starting a server and talking to Let's Encrypt.

func (Options) BaseURL

func (sopt Options) BaseURL() string

Returns the preferred scheme and host to contact this server. This can be used for links in emails, etc.

func (*Options) LoadEnv

func (sopt *Options) LoadEnv() error

LoadEnv reads configuration options from the environment variables.

func (*Options) Verify

func (sopt *Options) Verify() error

Verify makes sure the options are set correctly, sets up default values for Host, Port and IsLocalDevelopmentHost, and sets the PrimaryScheme.

type Server

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

Server represents both the HTTP and the HTTPS servers started via this package.

func Start

func Start(handler http.Handler, sopt Options) (*Server, error)

Start creates a server and starts listening. It makes sure that everything is set up properly, and returns after both HTTP and HTTPS (if configured) servers start accepting connections.

After this call returns, you are supposed to log a message saying the server is running. Call Wait() to block until the server shuts down.

func (*Server) BaseURL

func (srv *Server) BaseURL() string

BaseURL returns the preferred scheme and host of the server. Depending on the current settings, it might be something like https://your.externalhost.com/ or something more like http://localhost:3001/.

func (*Server) Endpoints

func (srv *Server) Endpoints() []string

Endpoints returns the list of URLs the server can be reached at, meant to be used for internal messaging and logging. The first item is BaseURL() and is the preferred endpoint; extra endpoints provide the alternatives.

Note that these values are for ease of debugging, and aren't necessarily valid URLs. E.g. some will lack a scheme.

func (*Server) Log

func (srv *Server) Log(format string, args ...interface{})

Log logs to the opt.Log function provided when starting the server.

func (*Server) Shutdown

func (srv *Server) Shutdown()

Shutdown stops accepting new connections, then waits for GracefulShutdownTimeout for existing requests to be finished, and then forcefully closes all connections.

func (*Server) Wait

func (srv *Server) Wait() error

Wait waits until the server shuts down (either because Shutdown was called, or there's an error accepting connections).

Jump to

Keyboard shortcuts

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