GithubHelp home page GithubHelp logo

fn's Introduction

fn

This library aims to simplify the construction of JSON API service, fn.Wrap is able to wrap any function to adapt the interface of http.Handler, which unmarshals POST data to a struct automatically.

Benchmark

BenchmarkIsBuiltinType-8                50000000                33.5 ns/op             0 B/op          0 allocs/op
BenchmarkSimplePlainAdapter_Invoke-8     2000000               757 ns/op             195 B/op          3 allocs/op
BenchmarkSimpleUnaryAdapter_Invoke-8     2000000               681 ns/op             946 B/op          5 allocs/op
BenchmarkGenericAdapter_Invoke-8         2000000               708 ns/op             946 B/op          5 allocs/op

Support types

io.ReadCloser      // request.Body
http.Header        // request.Header
fn.Form        // request.Form
fn.PostForm    // request.PostForm
*fn.Form       // request.Form
*fn.PostForm   // request.PostForm
*url.URL           // request.URL
*multipart.Form    // request.MultipartForm
*http.Request      // raw request

Usage

http.Handle("/test", fn.Wrap(test))

func test(io.ReadCloser, http.Header, fn.Form, fn.PostForm, *CustomizedRequestType, *url.URL, *multipart.Form) (*CustomizedResponseType, error)

Examples

Basic

package examples

import (
	"io"
	"mime/multipart"
	"net/http"
	"net/url"

	"github.com/pingcap/fn"
)

type Request struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type Response struct {
	Token string `json:"token"`
}

func api1() (*Response, error) {
	return &Response{Token: "token"}, nil
}

func api2(request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api3(rawreq *http.Request, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api4(rawreq http.Header, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api5(form *fn.Form, request *Request) (*Response, error) {
	token := request.Username + request.Password + form.Get("type")
	return &Response{Token: token}, nil
}

func api6(body io.ReadCloser, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api7(form *multipart.Form, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api7(urls *url.URL, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api8(urls *url.URL, form *multipart.Form, body io.ReadCloser, rawreq http.Header, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

Plugins

package examples

import (
	"context"
	"errors"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/fn"
)

var PermissionDenied = errors.New("permission denied")

func logger(ctx context.Context, req *http.Request) (context.Context, error) {
	log.Println("Request", req.RemoteAddr, req.URL.String())
	return ctx, nil
}

func ipWhitelist(ctx context.Context, req *http.Request) (context.Context, error) {
	if strings.HasPrefix(req.RemoteAddr, "172.168") {
		return ctx, PermissionDenied
	}
	return ctx, nil
}

func auth(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	return ctx, nil
}

type Request struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type Response struct {
	Token string `json:"token"`
}

func example() {
	fn.Plugin(logger, ipWhitelist, auth)
	http.Handle("/api1", fn.Wrap(api1))
	http.Handle("/api2", fn.Wrap(api2))
}

// api1 and api2 request have be validated by `ipWhitelist` and `auth`

func api1() (*Response, error) {
	return &Response{Token: "token"}, nil
}

func api2(request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

fn.Group

package examples

import (
	"context"
	"errors"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/fn"
)

var PermissionDenied = errors.New("permission denied")

func logger(ctx context.Context, req *http.Request) (context.Context, error) {
	log.Println("Request", req.RemoteAddr, req.URL.String())
	return ctx, nil
}

func ipWhitelist(ctx context.Context, req *http.Request) (context.Context, error) {
	if strings.HasPrefix(req.RemoteAddr, "172.168") {
		return ctx, PermissionDenied
	}
	return ctx, nil
}

func auth(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	return ctx, nil
}

type User struct {
	Balance int64
}

func queryUserFromRedis(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	user := &User{
		Balance: 10000, // balance from redis
	}
	return context.WithValue(ctx, "user", user), nil
}

type Response struct {
	Balance int64 `json:"balance"`
}

func example() {
	// Global plugins
	fn.Plugin(logger, ipWhitelist, auth)

	group := fn.NewGroup()

	// Group plugins
	group.Plugin(queryUserFromRedis)
	http.Handle("/user/balance", group.Wrap(fetchBalance))
	http.Handle("/user/buy", group.Wrap(buy))
}

func fetchBalance(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	return &Response{Balance: user.Balance}, nil
}

func buy(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	if user.Balance < 100 {
		return nil, errors.New("please check balance")
	}
	user.Balance -= 100
	return &Response{Balance: user.Balance}, nil
}

ResponseEncoder

package examples

import (
	"context"
	"errors"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/fn"
)

var PermissionDenied = errors.New("permission denied")

func logger(ctx context.Context, req *http.Request) (context.Context, error) {
	log.Println("Request", req.RemoteAddr, req.URL.String())
	return ctx, nil
}

func ipWhitelist(ctx context.Context, req *http.Request) (context.Context, error) {
	if strings.HasPrefix(req.RemoteAddr, "172.168") {
		return ctx, PermissionDenied
	}
	return ctx, nil
}

func auth(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	return ctx, nil
}

func injectRequest(ctx context.Context, req *http.Request) (context.Context, error) {
	return context.WithValue(ctx, "_rawreq", req), nil
}

type User struct {
	Balance int64
}

func queryUserFromRedis(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	user := &User{
		Balance: 10000, // balance from redis
	}
	return context.WithValue(ctx, "user", user), nil
}

type Response struct {
	Balance int64 `json:"balance"`
}

type ResponseMessage struct {
	Code int         `json:"code"`
	Data interface{} `json:"data"`
}

type ErrorMessage struct {
	Code  int    `json:"code"`
	Error string `json:"error"`
}

func example() {
	// Global plugins
	fn.Plugin(logger, ipWhitelist, auth, injectRequest)
	// Uniform all responses
	fn.SetErrorEncoder(func(ctx context.Context, err error) interface{} {
		req := ctx.Value("_rawreq").(*http.Request)
		log.Println("Error occurred: ", req.URL, err)
		return &ErrorMessage{
			Code:  -1,
			Error: err.Error(),
		}
	})

	fn.SetResponseEncoder(func(ctx context.Context, payload interface{}) interface{} {
		return &ResponseMessage{
			Code: 1,
			Data: payload,
		}
	})

	group := fn.NewGroup()

	// Group plugins
	group.Plugin(queryUserFromRedis)
	http.Handle("/user/balance", group.Wrap(fetchBalance))
	http.Handle("/user/buy", group.Wrap(buy))
}

func fetchBalance(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	return &Response{Balance: user.Balance}, nil
}

func buy(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	if user.Balance < 100 {
		return nil, errors.New("please check balance")
	}
	user.Balance -= 100
	return &Response{Balance: user.Balance}, nil
}

fn's People

Contributors

deardrops avatar lonng avatar lucklove avatar maplefu avatar zeromake avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.