• This repository has been archived on 14/Jan/2022
  • Stars
    star
    795
  • Rank 54,963 (Top 2 %)
  • Language
    Go
  • License
    MIT License
  • Created almost 10 years ago
  • Updated about 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Reflectionless data binding for Go's net/http (not actively maintained)

binding is reflectionless data binding for Go

GoDoc

binding

Reflectionless data binding for Go's net/http

Features

  • HTTP request data binding
  • Data validation (custom and built-in)
  • Error handling

Benefits

  • Moves data binding, validation, and error handling out of your application's handler
  • Reads Content-Type to deserialize form, multipart form, and JSON data from requests
  • No middleware: just a function call
  • Usable in any setting where net/http is present (Negroni, gocraft/web, std lib, etc.)
  • No reflection

Usage example

package main

import (
	"fmt"
	"net/http"

	"github.com/mholt/binding"
)

// First define a type to hold the data
// (If the data comes from JSON, see: http://mholt.github.io/json-to-go)
type ContactForm struct {
	User struct {
		ID int
	}
	Email   string
	Message string
}

// Then provide a field mapping (pointer receiver is vital)
func (cf *ContactForm) FieldMap(req *http.Request) binding.FieldMap {
	return binding.FieldMap{
		&cf.User.ID: "user_id",
		&cf.Email:   "email",
		&cf.Message: binding.Field{
			Form:     "message",
			Required: true,
		},
	}
}

// Now your handlers can stay clean and simple
func handler(resp http.ResponseWriter, req *http.Request) {
	contactForm := new(ContactForm)
	if errs := binding.Bind(req, contactForm); errs != nil {
		http.Error(resp, errs.Error(), http.StatusBadRequest)
		return
	}
	fmt.Fprintf(resp, "From:    %d\n", contactForm.User.ID)
	fmt.Fprintf(resp, "Message: %s\n", contactForm.Message)
}

func main() {
	http.HandleFunc("/contact", handler)
	http.ListenAndServe(":3000", nil)
}

Multipart/form-data usage example

package main

import (
	"bytes"
	"fmt"
	"github.com/mholt/binding"
	"io"
	"log"
	"mime/multipart"
	"net/http"
)

// We expect a multipart/form-data upload with
// a file field named 'data'
type MultipartForm struct {
	Data *multipart.FileHeader `json:"data"`
}

func (f *MultipartForm) FieldMap(req *http.Request) binding.FieldMap {
	return binding.FieldMap{
		&f.Data: "data",
	}
}

// Handlers are still clean and simple
func handler(resp http.ResponseWriter, req *http.Request) {
	multipartForm := new(MultipartForm)
	if errs := binding.Bind(req, multipartForm); errs != nil {
		http.Error(resp, errs.Error(), http.StatusBadRequest)
		return
	}

	// To access the file data you need to Open the file
	// handler and read the bytes out.
	var fh io.ReadCloser
	var err error
	if fh, err = multipartForm.Data.Open(); err != nil {
		http.Error(resp,
			fmt.Sprint("Error opening Mime::Data %v", err),
			http.StatusInternalServerError)
		return
	}
	defer fh.Close()
	dataBytes := bytes.Buffer{}
	var size int64
	if size, err = dataBytes.ReadFrom(fh); err != nil {
		http.Error(resp,
			fmt.Sprint("Error reading Mime::Data %v", err),
			http.StatusInternalServerError)
		return
	}
	// Now you have the attachment in databytes.
	// Maximum size is default is 10MB.
	log.Printf("Read %v bytes with filename %s",
		size, multipartForm.Data.Filename)
}

func main() {
	http.HandleFunc("/upload", handler)
	http.ListenAndServe(":3000", nil)
}

You can test from CLI using the excellent httpie client

http -f POST localhost:3000/upload data@myupload

Custom data validation

You may optionally have your type implement the binding.Validator interface to perform your own data validation. The .Validate() method is called after the struct is populated.

func (cf ContactForm) Validate(req *http.Request) error {
	if cf.Message == "Go needs generics" {
		return binding.Errors{
			binding.NewError([]string{"message"}, "ComplaintError", "Go has generics. They're called interfaces.")
		}
	}
	return nil
}

Binding custom types

For types you've defined, you can bind form data to it by implementing the Binder interface. Here's a contrived example:

type MyBinder map[string]string

func (t MyBinder) Bind(fieldName string, strVals []string) error {
	t["formData"] = strVals[0]
	return nil
}

If you can't add a method to the type, you can still specify a Binder func in the field spec. Here's a contrived example that binds an integer (not necessary, but you get the idea):

func (t *MyType) FieldMap(req *http.Request) binding.FieldMap {
	return binding.FieldMap{
		"a-key": binding.Field{
			Form: "number",
			Binder: func(fieldName string, formVals []string) error {
				val, err := strconv.Atoi(formVals[0])
				if err != nil {
					return binding.Errors{binding.NewError([]string{fieldName}, binding.DeserializationError, err.Error())}
				}
				t.SomeNumber = val
				return nil
			},
		},
	}
}

The Errors type has a convenience method, Add, which you can use to append to the slice if you prefer.

Supported types (forms)

The following types are supported in form deserialization by default. (JSON requests are delegated to encoding/json.)

  • uint, *uint, []uint, uint8, *uint8, []uint8, uint16, *uint16, []uint16, uint32, *uint32, []uint32, uint64, *uint64, []uint64
  • int, *int, []int, int8, *int8, []int8, int16, *int16, []int16, int32, *int32, []int32, int64, *int64, []int64
  • float32, *float32, []float32, float64, *float64, []float64
  • bool, *bool, []bool
  • string, *string, []string
  • time.Time, *time.Time, []time.Time
  • *multipart.FileHeader, []*multipart.FileHeader

More Repositories

1

PapaParse

Fast and powerful CSV (delimited text) parser that gracefully handles large files and malformed input
JavaScript
12,123
star
2

json-to-go

Translates JSON into a Go type in your browser instantly (original)
JavaScript
4,355
star
3

archiver

Easily create & extract archives, and compress & decompress files of various formats
Go
4,055
star
4

timeliner

All your digital life on a single timeline, stored locally
Go
3,436
star
5

curl-to-go

Convert curl commands to Go code in your browser
JavaScript
1,773
star
6

caddy-l4

Layer 4 (TCP/UDP) app for Caddy
Go
765
star
7

photobak

Back up your content from Google Photos - DEPRECATED: use Timeliner
Go
304
star
8

acmez

Premier ACME client library for Go
Go
216
star
9

caddy-dynamicdns

Caddy app that keeps your DNS records (A/AAAA) pointed at itself.
Go
207
star
10

caddy-webdav

WebDAV handler module for Caddy
Go
182
star
11

golang-graphics

Community-contributed Go graphics files
138
star
12

caddy-ratelimit

HTTP rate limiting module for Caddy 2
Go
131
star
13

conncept

Project Conncept: A layer 4 app for Caddy that multiplexes raw TCP/UDP streams
58
star
14

caddy-embed

Caddy plugin for embedding static files directly into the server binary
Go
38
star
15

meetupchat

Simple chat using TCP, as a quick workshop for beginner (Go) programmers
Go
20
star
16

caddy-events-exec

Run commands on Caddy events
Go
19
star
17

vidagent

Easily filter your video files for content (requires ffmpeg)
Go
15
star
18

caddy-grpc-web

Caddy module to Convert gRPC-Web requests to normal gRPC for servers
Go
14
star
19

diskspace

A little Go package for measuring disk space/usage
Go
13
star
20

phpile

A file-system-based trie data structure that's persistent, portable, and super-fast. Experimental. Not for production use.
PHP
12
star
21

chessml

PGN file parser and Chess engine for machine learning, CS 478 group project
Go
6
star
22

dhall-adapter

Configure Caddy with Dhall
Go
4
star
23

caddy-psl

A public suffix list module for Caddy
Go
3
star
24

mholt.github.io

3
star
25

ysaward

An entire website for managing high-turnover YSA wards, with multi-stake support
PHP
1
star
26

blogtest

Testing testing 123
Shell
1
star
27

caddy-sqlite-fs

Go
1
star
28

caddy-hitcounter

Add a classic retro hit counter to your modern Caddy site
Go
1
star