httpin

package module
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Apr 20, 2024 License: MIT Imports: 7 Imported by: 25

README

httpin logo

httpin - HTTP Input for Go

HTTP Request from/to Go Struct

Core Features

httpin helps you easily decode data from an HTTP request, including:

  • Query parameters, e.g. ?name=john&is_member=true
  • Headers, e.g. Authorization: xxx
  • Form data, e.g. username=john&password=******
  • JSON/XML Body, e.g. POST {"name":"john"}
  • Path variables, e.g. /users/{username}
  • File uploads

You only need to define a struct to receive/bind data from an HTTP request, without writing any parsing stuff code by yourself.

Since v0.15.0, httpin also supports creating an HTTP request (http.Request) from a Go struct instance.

httpin is:

How to decode an HTTP request to Go struct?

type ListUsersInput struct {
	Token    string `in:"query=access_token;header=x-access-token"`
	Page     int    `in:"query=page;default=1"`
	PerPage  int    `in:"query=per_page;default=20"`
	IsMember bool   `in:"query=is_member"`
}

func ListUsers(rw http.ResponseWriter, r *http.Request) {
	input := r.Context().Value(httpin.Input).(*ListUsersInput)

	if input.IsMember {
		// Do sth.
	}
	// Do sth.
}

How to encode a Go struct to HTTP request?

type ListUsersInput struct {
	Token    string `in:"query=access_token;header=x-access-token"`
	Page     int    `in:"query=page;default=1"`
	PerPage  int    `in:"query=per_page;default=20"`
	IsMember bool   `in:"query=is_member"`
}

func SDKListUsers() {
	payload := &ListUsersInput{
		Token:    os.Getenv("MY_APP_ACCESS_TOKEN"),
		Page:     2,
		IsMember: true,
	}

	// Easy to remember, http.NewRequest -> httpin.NewRequest
	req, err := httpin.NewRequest("GET", "/users", payload)
	// ...
}

Why this package?

Compared with using net/http package
func ListUsers(rw http.ResponseWriter, r *http.Request) {
	page, err := strconv.ParseInt(r.FormValue("page"), 10, 64)
	if err != nil {
		// Invalid parameter: page.
		return
	}
	perPage, err := strconv.ParseInt(r.FormValue("per_page"), 10, 64)
	if err != nil {
		// Invalid parameter: per_page.
		return
	}
	isMember, err := strconv.ParseBool(r.FormValue("is_member"))
	if err != nil {
		// Invalid parameter: is_member.
		return
	}

	// Do sth.
}
Benefits Before (use net/http package) After (use ggicci/httpin package)
⌛️ Developer Time 😫 Expensive (too much parsing stuff code) 🚀 Faster (define the struct for receiving input data and leave the parsing job to httpin)
♻️ Code Repetition Rate 😞 High 😍 Lower
📖 Code Readability 😟 Poor 🤩 Highly readable
🔨 Maintainability 😡 Poor 🥰 Highly maintainable

Alternatives and Similars

Documentation

Overview

Package httpin helps decoding an HTTP request to a custom struct by binding data with querystring (query params), HTTP headers, form data, JSON/XML payloads, URL path params, and file uploads (multipart/form-data).

Index

Constants

View Source
const (
	// Input is the key to get the input object from Request.Context() injected by httpin. e.g.
	//
	//     input := r.Context().Value(httpin.Input).(*InputStruct)
	Input contextKey = iota
)

Variables

View Source
var Option coreOptions = coreOptions{
	WithErrorHandler:            core.WithErrorHandler,
	WithMaxMemory:               core.WithMaxMemory,
	WithNestedDirectivesEnabled: core.WithNestedDirectivesEnabled,
}

Option is a collection of options for creating a Core instance.

Functions

func Decode added in v0.11.0

func Decode[T any](req *http.Request, opts ...core.Option) (*T, error)

Decode decodes an HTTP request to an instance of T and returns its pointer (*T). T must be a struct type. For example:

if user, err := Decode[User](req); err != nil { ... }
// now user is a *User instance, which has been populated with data from the request.

func DecodeTo added in v0.18.0

func DecodeTo(req *http.Request, input any, opts ...core.Option) error

DecodeTo decodes an HTTP request and populates input with data from the HTTP request. The input must be a pointer to a struct instance. For example:

input := &InputStruct{}
if err := DecodeTo(req, input); err != nil { ... }

input is now populated with data from the request.

func New

func New(inputStruct any, opts ...core.Option) (*core.Core, error)

New calls core.New to create a new Core instance. Which is responsible for both:

  • decoding an HTTP request to an instance of the inputStruct;
  • and encoding an instance of the inputStruct to an HTTP request.

Note that the Core instance is bound to the given specific type, it will not work for other types. If you want to decode/encode other types, you need to create another Core instance. Or directly use the following functions, which are just shortcuts of Core's methods, so you don't need to create a Core instance:

  • httpin.Decode(): decode an HTTP request to an instance of the inputStruct.
  • httpin.NewRequest() to encode an instance of the inputStruct to an HTTP request.

For best practice, we would recommend using httpin.NewInput() to create an HTTP middleware for a specific input type. The middleware can be bound to an API, chained with other middlewares, and also reused in other APIs. You even don't need to call the Deocde() method explicitly, the middleware will do it for you and put the decoded instance to the request's context.

func NewInput

func NewInput(inputStruct any, opts ...core.Option) func(http.Handler) http.Handler

NewInput creates an HTTP middleware handler. Which is a function that takes in an http.Handler and returns another http.Handler.

The middleware created by NewInput is to add the decoding function to an existing http.Handler. This functionality will decode the HTTP request into a struct instance and put its pointer to the request's context. So that the next hop can get the decoded struct instance from the request's context.

We recommend using https://github.com/justinas/alice to chain your middlewares. If you're using some popular web frameworks, they may have already provided a middleware chaining mechanism.

For example:

type ListUsersRequest struct {
	Page    int `in:"query=page,page_index,index"`
	PerPage int `in:"query=per_page,page_size"`
}

func ListUsersHandler(rw http.ResponseWriter, r *http.Request) {
	input := r.Context().Value(httpin.Input).(*ListUsersRequest)
	// ...
}

func init() {
	http.Handle("/users", alice.New(httpin.NewInput(&ListUsersRequest{})).ThenFunc(ListUsersHandler))
}

func NewRequest added in v0.15.0

func NewRequest(method, url string, input any, opts ...core.Option) (*http.Request, error)

NewRequest wraps NewRequestWithContext using context.Background(), see NewRequestWithContext.

func NewRequestWithContext added in v0.15.0

func NewRequestWithContext(ctx context.Context, method, url string, input any, opts ...core.Option) (*http.Request, error)

NewRequestWithContext turns the given input into an HTTP request. The input must be a struct instance. And its fields' "in" tags define how to bind the data from the struct to the HTTP request. Use it as the replacement of http.NewRequest().

addUserPayload := &AddUserRequest{...}
addUserRequest, err := NewRequestWithContext(context.Background(), "GET", "http://example.com", addUserPayload)
http.DefaultClient.Do(addUserRequest)

Types

type File added in v0.7.0

type File = core.File

File is the builtin type of httpin to manupulate file uploads. On the server side, it is used to represent a file in a multipart/form-data request. On the client side, it is used to represent a file to be uploaded.

func UploadFile added in v0.15.0

func UploadFile(path string) *File

UploadFile is a helper function to create a File instance from a file path. It is useful when you want to upload a file from the local file system.

func UploadStream added in v0.15.0

func UploadStream(r io.ReadCloser) *File

UploadStream is a helper function to create a File instance from a io.Reader. It is useful when you want to upload a file from a stream.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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