Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/pingcap/fn
http-api json-api
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/pingcap/fn
- Owner: pingcap
- License: apache-2.0
- Created: 2019-06-18T02:27:31.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-08-30T14:25:25.000Z (about 1 year ago)
- Last Synced: 2024-06-18T15:19:47.293Z (5 months ago)
- Topics: http-api, json-api
- Language: Go
- Size: 29.3 KB
- Stars: 35
- Watchers: 27
- Forks: 14
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- go-awesome - pingcap/fn - supports binding any method that is built into a POST+JSON interface (Open source library / Web Framework)
README
# 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
```go
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
```go
package examplesimport (
"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
```go
package examplesimport (
"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`
```go
package examplesimport (
"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
```go
package examplesimport (
"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
}
```