rest

package module
v0.0.0-...-04a2455 Latest Latest
Warning

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

Go to latest
Published: May 4, 2026 License: MIT Imports: 9 Imported by: 107

README

rest

This library contains a HTTP client, and a number of useful middlewares for writing a HTTP client and server in Go. For more information and package documentation, please see the godoc documentation.

Client

The Client struct makes it easy to interact with a JSON API.

client := restclient.New("username", "password", "http://ipinfo.io")
req, _ := client.NewRequest("GET", "/json", nil)
type resp struct {
    City string `json:"city"`
    Ip   string `json:"ip"`
}
var r resp
client.Do(req, &r)
fmt.Println(r.Ip)
Transport

Use the restclient.Transport as the http.Transport to easily inspect the raw HTTP request and response. Set DEBUG_HTTP_TRAFFIC=true in your environment to dump HTTP requests and responses to stderr.

Inspecting Response Headers (e.g. Rate Limits)

restclient.Client.Do consumes the response body and returns just the decoded value (or an error parsed by ErrorParser), so it does not surface response headers to its caller. When you need to read headers from every response — for example, to track API rate limit headers like GitHub's X-RateLimit-Remaining / X-RateLimit-Reset — wrap the underlying http.RoundTripper instead of changing Client. The RoundTripper sees every *http.Response before Do consumes the body, and the headers are already parsed at that point, so it's the right place to record them.

type rateLimitTransport struct {
    base http.RoundTripper
    last atomic.Pointer[RateLimit] // your own snapshot type
}

func (t *rateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    resp, err := t.base.RoundTrip(req)
    if resp != nil {
        if rl := parseRateLimit(resp.Header); rl != nil {
            t.last.Store(rl)
        }
    }
    return resp, err
}

rc := restclient.NewBearerClient(token, "https://api.github.com")
rlt := &rateLimitTransport{base: rc.Client.Transport}
rc.Client.Transport = rlt
// ... now rlt.last.Load() always reflects the most recent response.

This pattern works for successful responses, redirects, and errors alike, and stacks cleanly with other transport-level middleware (retries, debug logging, etc.). Pair it with a custom ErrorParser if you also want to return a typed error (e.g. RateLimitError) when the limit is exhausted — the parser receives the same *http.Response and can read the same headers.

Defining Custom Error Responses

rest exposes a number of HTTP error handlers - for example, rest.ServerError(w, r, err) will write a 500 server error to w. By default, these error handlers will write a generic JSON response over the wire, using fields specified by the HTTP problem spec.

You can define a custom error handler if you like (say if you want to return a HTML server error, or 404 error or similar) by calling RegisterHandler:

rest.RegisterHandler(500, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    err := rest.CtxErr(r)
    fmt.Println("Server error:", err)
    w.Header().Set("Content-Type", "text/html")
    w.WriteHeader(500)
    w.Write([]byte("<html><body>Server Error</body></html>"))
}))
Debugging

Set the DEBUG_HTTP_TRAFFIC environment variable to print out all request/response traffic being made by the client.

rest also includes a Transport that is a drop in for a http.Transport, but includes support for debugging HTTP requests. Add it like so:

client := http.Client{
    Transport: &restclient.Transport{
        Debug: true,
        Output: os.Stderr,
        Transport: http.DefaultTransport,
    },
}

Donating

Donations free up time to make improvements to the library, and respond to bug reports. You can send donations via Paypal's "Send Money" feature to kev@inburke.com. Donations are not tax deductible in the USA.

Documentation

Overview

Package rest implements responses and a HTTP client for API consumption.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	NewClient          = restclient.New
	NewBearerClient    = restclient.NewBearerClient
	DefaultTransport   = restclient.DefaultTransport
	JSON               = restclient.JSON
	FormURLEncoded     = restclient.FormURLEncoded
	Version            = restclient.Version
	DefaultErrorParser = restclient.DefaultErrorParser
)
View Source
var Logger *slog.Logger

Logger logs information about incoming requests.

Functions

func BadRequest

func BadRequest(w http.ResponseWriter, r *http.Request, err *Error)

BadRequest logs a 400 error and then returns a 400 response to the client.

func CtxDomain

func CtxDomain(r *http.Request) string

CtxDomain returns a domain that's been set on the request. Use it to get the domain set on a 401 error handler.

func CtxErr

func CtxErr(r *http.Request) error

CtxErr returns an error that's been stored in the Request context.

func Forbidden

func Forbidden(w http.ResponseWriter, r *http.Request, err *Error)

Forbidden returns a 403 Forbidden status code to the client, with the given Error object in the response body.

func Gone

func Gone(w http.ResponseWriter, r *http.Request)

Gone responds to the request with a 410 Gone error message

func NoContent

func NoContent(w http.ResponseWriter)

NoContent returns a 204 No Content message.

func NotAllowed

func NotAllowed(w http.ResponseWriter, r *http.Request)

NotAllowed returns a generic HTTP 405 Not Allowed status and response body to the client.

func NotFound

func NotFound(w http.ResponseWriter, r *http.Request)

NotFound returns a 404 Not Found error to the client.

func RegisterHandler

func RegisterHandler(code int, f http.Handler)

RegisterHandler registers the given HandlerFunc to serve HTTP requests for the given status code. Use CtxErr and CtxDomain to retrieve extra values set on the request in f (if any).

Despite registering the handler for the code, f is responsible for calling WriteHeader(code) since it may want to set response headers first.

To delete a Handler, call RegisterHandler with nil for the second argument.

Example
package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/kevinburke/rest"
)

func main() {
	rest.RegisterHandler(500, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := rest.CtxErr(r)
		fmt.Println("Server error:", err)
		w.Header().Set("Content-Type", "text/html")
		w.WriteHeader(500)
		w.Write([]byte("<html><body>Server Error</body></html>"))
	}))

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/", nil)
	rest.ServerError(w, req, errors.New("Something bad happened"))
}
Output:
Server error: Something bad happened

func ServerError

func ServerError(w http.ResponseWriter, r *http.Request, err error)

ServerError logs the error to the Logger, and then responds to the request with a generic 500 server error message. ServerError panics if err is nil.

func Unauthorized

func Unauthorized(w http.ResponseWriter, r *http.Request, domain string)

Unauthorized sets the Domain in the request context

Types

type Client

type Client = restclient.Client
Example
package main

import (
	"fmt"

	"github.com/kevinburke/rest/restclient"
)

func main() {
	client := restclient.New("jobs", "secretpassword", "http://ipinfo.io")
	req, _ := client.NewRequest("GET", "/json", nil)
	type resp struct {
		City string `json:"city"`
		Ip   string `json:"ip"`
	}
	var r resp
	client.Do(req, &r)
	fmt.Println(r.Ip)
}

type Error

type Error = resterror.Error

Backwards compatibility

type Transport

type Transport = restclient.Transport
Example
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/kevinburke/rest"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello World"))
	}))
	defer server.Close()
	b := new(bytes.Buffer)
	client := http.Client{
		Transport: &rest.Transport{Debug: true, Output: b},
	}
	req, _ := http.NewRequest("GET", server.URL+"/bar", nil)
	client.Do(req)

	// Dump the HTTP request from the buffer, but skip the lines that change.
	scanner := bufio.NewScanner(b)
	for scanner.Scan() {
		text := scanner.Text()
		if strings.HasPrefix(text, "Host:") || strings.HasPrefix(text, "Date:") {
			continue
		}
		fmt.Println(text)
	}
}
Output:
GET /bar HTTP/1.1
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/plain; charset=utf-8

Hello World

type UploadType

type UploadType = restclient.UploadType

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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