httputils

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 5, 2021 License: MIT Imports: 8 Imported by: 9

README

go-http-utils

GoDoc Go Report Card Build Status codecov

The library that provides HTTP utility functions.

Features

  • wrapper for the http.ResponseWriter interface for catching writing errors;
  • simplified interface of the http.Client structure for mocking purposes;
  • middlewares:
    • middleware for catching writing errors;
    • middleware that fallback of requests to static assets to the index.html file (useful in a SPA);
  • analogs:
    • analog of the http.Redirect() function with catching writing errors;
    • analog of the http.FileServer() function with applied above-mentioned middlewares;
  • function to start a server with support for graceful shutdown by a signal.

Installation

Prepare the directory:

$ mkdir --parents "$(go env GOPATH)/src/github.com/thewizardplusplus/"
$ cd "$(go env GOPATH)/src/github.com/thewizardplusplus/"

Clone this repository:

$ git clone https://github.com/thewizardplusplus/go-http-utils.git
$ cd go-http-utils

Install dependencies with the dep tool:

$ dep ensure -vendor-only

Examples

httputils.CatchingResponseWriter:

package main

import (
	"log"
	"net/http"

	httputils "github.com/thewizardplusplus/go-http-utils"
)

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		catchingWriter := httputils.NewCatchingResponseWriter(writer)

		// use the catchingWriter object as the usual http.ResponseWriter interface
		http.Redirect(
			catchingWriter,
			request,
			"http://example.com/",
			http.StatusMovedPermanently,
		)

		if err := catchingWriter.LastError(); err != nil {
			log.Printf("unable to write the HTTP response: %v", err)
		}
	})
}

httputils.CatchingMiddleware():

package main

import (
	"fmt"
	stdlog "log"
	"net/http"
	"os"

	"github.com/go-log/log/print"
	httputils "github.com/thewizardplusplus/go-http-utils"
)

func main() {
	// use the standard logger for error handling
	logger := stdlog.New(os.Stderr, "", stdlog.LstdFlags)
	catchingMiddleware := httputils.CatchingMiddleware(
		// wrap the standard logger via the github.com/go-log/log package
		print.New(logger),
	)

	var handler http.Handler
	handler = http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
		// writing error will be handled by the catching middleware
		fmt.Fprintln(writer, "Hello, world!")
	})
	handler = catchingMiddleware(handler)

	http.Handle("/", handler)
	logger.Fatal(http.ListenAndServe(":8080", nil))
}

httputils.SPAFallbackMiddleware():

package main

import (
	"log"
	"net/http"

	httputils "github.com/thewizardplusplus/go-http-utils"
)

func main() {
	staticAssetHandler := http.FileServer(http.Dir("/var/www/example.com"))
	staticAssetHandler = httputils.SPAFallbackMiddleware()(staticAssetHandler)

	http.Handle("/", staticAssetHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

httputils.StaticAssetHandler():

package main

import (
	stdlog "log"
	"net/http"
	"os"

	"github.com/go-log/log/print"
	httputils "github.com/thewizardplusplus/go-http-utils"
)

func main() {
	// use the standard logger for error handling
	logger := stdlog.New(os.Stderr, "", stdlog.LstdFlags)
	staticAssetHandler := httputils.StaticAssetHandler(
		http.Dir("/var/www/example.com"),
		// wrap the standard logger via the github.com/go-log/log package
		print.New(logger),
	)

	http.Handle("/", staticAssetHandler)
	stdlog.Fatal(http.ListenAndServe(":8080", nil))
}

httputils.RunServer():

package main

import (
	"context"
	stdlog "log"
	"net/http"
	"os"

	"github.com/go-log/log/print"
	httputils "github.com/thewizardplusplus/go-http-utils"
)

func main() {
	server := &http.Server{Addr: ":8080"}
	// use the standard logger for error handling
	logger := stdlog.New(os.Stderr, "", stdlog.LstdFlags)
	if ok := httputils.RunServer(
		context.Background(),
		server,
		// wrap the standard logger via the github.com/go-log/log package
		print.New(logger),
		os.Interrupt,
	); !ok {
		// the error is already logged, so just end the program with the error status
		os.Exit(1)
	}
}

Bibliography

  1. Check Errors When Calling http.ResponseWriter.Write()
  2. Mocking HTTP Requests in Golang
  3. Proxying API Requests in Development
  4. https://golang.org/pkg/net/http/#Server.Shutdown

License

The MIT License (MIT)

Copyright © 2020 thewizardplusplus

Documentation

Overview

Package httputils provides HTTP utility functions.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CatchingMiddleware

func CatchingMiddleware(logger log.Logger) mux.MiddlewareFunc

CatchingMiddleware ...

It's a middleware that captures the last error from the Write() method of the http.ResponseWriter interface and logs it via the provided log.Logger interface.

Example
// use the standard logger for error handling
logger := stdlog.New(os.Stderr, "", stdlog.LstdFlags)
catchingMiddleware := CatchingMiddleware(
	// wrap the standard logger via the github.com/go-log/log package
	print.New(logger),
)

var handler http.Handler // nolint: staticcheck
handler = http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
	// writing error will be handled by the catching middleware
	fmt.Fprintln(writer, "Hello, world!") // nolint: errcheck
})
handler = catchingMiddleware(handler)

http.Handle("/", handler)
logger.Fatal(http.ListenAndServe(":8080", nil))
Output:

func CatchingRedirect

func CatchingRedirect(
	writer http.ResponseWriter,
	request *http.Request,
	url string,
	statusCode int,
) error

CatchingRedirect ...

It's a complete analog of the http.Redirect() function with capturing the last error from the Write() method of the http.ResponseWriter interface.

func RunServer

func RunServer(
	shutdownCtx context.Context,
	server Server,
	logger log.Logger,
	interruptSignals ...os.Signal,
) (ok bool)

RunServer ...

It runs the provided Server interface via its ListenAndServe() method, then waits for specified signals. If there're no signals specified, the function will wait for any possible signals.

After receiving the signal, the function will stop the server via its Shutdown() method. The provided context will be used for calling this method, and just for that.

Attention! The function will return only after completing of ListenAndServe() and Shutdown() methods of the server.

Errors that occurred when calling ListenAndServe() and Shutdown() methods of the server will be processed by the provided log.Logger interface. (Use the github.com/go-log/log package to wrap the standard logger.) The fact that an error occurred will be reflected in the boolean flag returned by the function.

Example
server := &http.Server{Addr: ":8080"}
// use the standard logger for error handling
logger := stdlog.New(os.Stderr, "", stdlog.LstdFlags)
if ok := RunServer(
	context.Background(),
	server,
	// wrap the standard logger via the github.com/go-log/log package
	print.New(logger),
	os.Interrupt,
); !ok {
	// the error is already logged, so just end the program with the error status
	os.Exit(1)
}
Output:

func SPAFallbackMiddleware

func SPAFallbackMiddleware() mux.MiddlewareFunc

SPAFallbackMiddleware ...

A SPA often manages its routing itself, so all relevant requests must be handled by an index.html file. A backend, in turn, must be responsible for an API and distribution of static files.

To separate these types of requests, the following heuristic is used. If the request uses the GET method and contains the text/html value in the Accept header, the index.html file is returned (more precisely, a path part of a request URL is replaced to "/"). All other requests are processed as usual. The fact is that any routing requests sent from a modern browser meet described requirements.

This solution is based on the proxy implementation in the development server of the Create React App project. See: https://create-react-app.dev/docs/proxying-api-requests-in-development/

Example
staticAssetHandler := http.FileServer(http.Dir("/var/www/example.com"))
staticAssetHandler = SPAFallbackMiddleware()(staticAssetHandler)

http.Handle("/", staticAssetHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
Output:

func StaticAssetHandler

func StaticAssetHandler(
	fileSystem http.FileSystem,
	logger log.Logger,
) http.Handler

StaticAssetHandler ...

It's a complete analog of the http.FileServer() function with applied SPAFallbackMiddleware() and CatchingMiddleware() middlewares.

Example
// use the standard logger for error handling
logger := stdlog.New(os.Stderr, "", stdlog.LstdFlags)
staticAssetHandler := StaticAssetHandler(
	http.Dir("/var/www/example.com"),
	// wrap the standard logger via the github.com/go-log/log package
	print.New(logger),
)

http.Handle("/", staticAssetHandler)
stdlog.Fatal(http.ListenAndServe(":8080", nil))
Output:

Types

type CatchingResponseWriter

type CatchingResponseWriter struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

CatchingResponseWriter ...

It wraps the http.ResponseWriter interface to save the last error that occurred while calling the http.ResponseWriter.Write() method.

Errors of writing via the http.ResponseWriter interface are important to handle. See for details: https://stackoverflow.com/a/43976633

Attention! The CatchingResponseWriter structure only supports methods that are declared directly in the http.ResponseWriter interface. Any optional interfaces (http.Flusher, http.Hijacker, etc.) aren't supported.

Example
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
	catchingWriter := NewCatchingResponseWriter(writer)

	// use the catchingWriter object as the usual http.ResponseWriter interface
	http.Redirect(
		catchingWriter,
		request,
		"http://example.com/",
		http.StatusMovedPermanently,
	)

	if err := catchingWriter.LastError(); err != nil {
		log.Printf("unable to write the HTTP response: %v", err)
	}
})
Output:

func NewCatchingResponseWriter

func NewCatchingResponseWriter(
	writer http.ResponseWriter,
) *CatchingResponseWriter

NewCatchingResponseWriter ...

It allocates and returns a new CatchingResponseWriter object wrapping the provided http.ResponseWriter interface.

func (CatchingResponseWriter) LastError

func (writer CatchingResponseWriter) LastError() error

LastError ...

It returns the last saved error from the Write() method of the wrapped http.ResponseWriter interface.

func (*CatchingResponseWriter) Write

func (writer *CatchingResponseWriter) Write(p []byte) (n int, err error)

type File

type File interface {
	http.File
}

File ...

It's used only for mock generating.

type FileInfo

type FileInfo interface {
	os.FileInfo
}

FileInfo ...

It's used only for mock generating.

type FileSystem

type FileSystem interface {
	http.FileSystem
}

FileSystem ...

It's used only for mock generating.

type HTTPClient

type HTTPClient interface {
	Do(request *http.Request) (*http.Response, error)
}

HTTPClient ...

It represents the simplified interface of the http.Client structure (the primary method only). It is useful for mocking the latter.

See for details: https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/

type Handler

type Handler interface {
	http.Handler
}

Handler ...

It's used only for mock generating.

type Logger

type Logger interface {
	log.Logger
}

Logger ...

It's used only for mock generating.

type ResponseWriter

type ResponseWriter interface {
	http.ResponseWriter
}

ResponseWriter ...

It's used only for mock generating.

type Server

type Server interface {
	ListenAndServe() error
	Shutdown(ctx context.Context) error
}

Server ...

It represents the interface of the http.Server structure used by the RunServer() function.

Jump to

Keyboard shortcuts

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