goa

package
v0.0.0-...-d31700d Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2022 License: MIT Imports: 20 Imported by: 0

README

goa

A golang http router with regexp and document generation support.

Usage

demo main package
package main

import (
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/go-better/dev/os/fs"
	"gitee.com/go-better/dev/net/goa"
	"gitee.com/go-better/dev/net/goa/benchmark/example/users"
	"gitee.com/go-better/dev/net/goa/middlewares"
	"gitee.com/go-better/dev/net/goa/server"
	"gitee.com/go-better/dev/net/goa/utilroutes"
	"gitee.com/go-better/dev/debug/logger"
)

func main() {
	router := goa.New()
	// logger should comes first, to handle panic and log all requests
	router.Use(middlewares.NewLogger(logger.New(os.Stdout)).Record)
	router.Use(middlewares.NewCORS(allowOrigin).Check)
	utilroutes.Setup(&router.RouterGroup)

	if os.Getenv("GOA_DOC") != "" {
		router.DocDir(filepath.Join(fs.SourceDir(), "docs", "apis"))
	}

	// If don't need document, use this simple style.
	router.Get("/", func(c *goa.Context) {
		c.Data("index", nil)
	})

	// If need document, use this style for automated routes document generation.
	router.Group("/users", "用户", "用户相关的接口").
		Get("/", func(req struct {
			Title   string        `用户列表`
			Desc    string        `根据搜索条件获取用户列表`
			Query   users.ListReq
			Session users.Session
		}, resp *struct {
			Data  users.ListResp
			Error error
		}) {
			resp.Data, resp.Error = req.Query.Run(&req.Session)
		}).
		Get(`/(\d+)`, func(req struct {
			Title string       `用户详情`
			Desc  string       `根据用户ID获取用户详情`
			Param int64        `用户ID`
			Ctx   *goa.Context 
		}, resp *struct {
			Data  users.DetailResp
			Error error
		}) {
			resp.Data, resp.Error = users.Detail(req.Param)
		})

	if os.Getenv("GOA_DOC") != "" {
		return
	}

	server.ListenAndServe(router)
}

func allowOrigin(origin string, c *goa.Context) bool {
	u, err := url.Parse(origin)
	if err != nil {
		return false
	}
	hostname := u.Hostname()
	return strings.HasSuffix(hostname, ".example.com") ||
		hostname == "example.com" || hostname == "localhost"
}
demo users package
package users

import "time"

type ListReq struct {
	Name     string `c:"用户名称"`
	Type     string `c:"用户类型"`
	Page     int    `c:"页码"`
	PageSize int    `c:"每页数据条数"`
}

type ListResp struct {
	TotalSize int `c:"总数据条数"`
	TotalPage int `c:"总页数"`
	Rows      []struct {
		Id    int    `c:"ID"`
		Name  string `c:"名称"`
		Phone string `c:"电话号码"`
	}
}

type Session struct {
	UserId  int64
	LoginAt time.Time
}

func (l *ListReq) Run(sess *Session) (ListResp, error) {
	return ListResp{}, nil
}

type DetailResp struct {
	TotalSize int `c:"总数据条数"`
	TotalPage int `c:"总页数"`
	Rows      []struct {
		Id    int    `c:"ID"`
		Name  string `c:"名称"`
		Phone string `c:"电话号码"`
	}
}

func Detail(userId int64) (DetailResp, error) {
	return DetailResp{}, nil
}

Handler func

The req (request) parameter

The req parameter must be a struct, and it can have the following 8 fields. The Title and Desc fields are just used for document generation, so can be of any type, and their values are untouched; The other fields's values are set in advance from the http.Request, so can be used directly in the handler. Except Session and Ctx, other fields's full tag is used as description for the corresponding object in the document. For Param, Query, Header and Body, if it's a struct, the struct fields's comment or c tag is used as the description for the fields in the document.

  1. Title: It's full tag is used as title of the route in document.
  2. Desc: It's full tag is used as description the route in document.
  3. Param: Subexpression parameters in regular expression path. If there is only one subexpression in the path and it's not named, the whole Param is set to the match properly. Otherwise, the Param must be a struct, and it's fields are set properly to the corresponding named subexpression. The first letter of the subexpression name is changed to uppercase to find the corresponding field.
  4. Query: Query parameters in the the request, Query must be a struct, and it's fields are set properly to the corresponding query paramter. The first letter of the query parameter name is changed to uppercase to find the corresponding field in the struct.
  5. Header: Headers in the request. Header must be a struct, and it's fields are set properly to the corresponding header. The field's header tag(if present) or it's name is used as the corresponding header name.
  6. Body: The request body is set to Body using json.Unmarshal.
  7. Session: Session is set to goa.Context.Get("session"), so the type must be exactly the same.
  8. Ctx: Ctx must be of type *goa.Context.
The resp (response) parameter.

The resp parameter must be a struct pointer, and it can have the following 3 fields. The fields's full tag is used as description for the corresponding object in the document. For Data and Header, if it's a struct, the struct fields's comment or c tag is used as the description for the fields in the document.

  1. Data: Data is writed in response body as a data field using json.Marshal.
  2. Error: Error is writed in response body as the code and message fields.
  3. Header: Header is writed in response headers.

see full examples and the generated documents.

Default middlewares

  • logging with error alarm support
  • list of requests in processing
  • CORS check

Attentions

  • static route is always matched before regexp route.
  • call c.Next() in middleware to pass control to the next midlleware or route, if you don't call c.Next() no remaining midlleware or route will be executed.
  • generally don't use midlleware after routes, because generally the routes don't call c.Next().

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Context

type Context struct {
	ContextBeforeLookup
	// contains filtered or unexported fields
}

func (*Context) Next

func (c *Context) Next()

run the next midllware or route handler.

Example
c := &Context{}
c.Next()
c.Next()
Output:

func (*Context) Param

func (c *Context) Param(i int) string

Param returns captured subpatterns. index begin at 0, so pass 0 to get the first captured subpatterns.

Example
c := &Context{params: []string{"123", "sdf"}}
fmt.Println(c.Param(0), "-")
fmt.Println(c.Param(1), "-")
fmt.Println(c.Param(2), "-")
Output:

123 -
sdf -
 -

type ContextBeforeLookup

type ContextBeforeLookup struct {
	*http.Request
	http.ResponseWriter
	// contains filtered or unexported fields
}

func (*ContextBeforeLookup) ClientAddr

func (c *ContextBeforeLookup) ClientAddr() string

func (*ContextBeforeLookup) Context

func (c *ContextBeforeLookup) Context() context.Context

func (*ContextBeforeLookup) Data

func (c *ContextBeforeLookup) Data(data interface{}, err error)

func (*ContextBeforeLookup) Flush

func (c *ContextBeforeLookup) Flush()

func (*ContextBeforeLookup) Get

func (c *ContextBeforeLookup) Get(key string) interface{}

func (*ContextBeforeLookup) GetError

func (c *ContextBeforeLookup) GetError() error

func (*ContextBeforeLookup) Hijack

func (c *ContextBeforeLookup) Hijack() (net.Conn, *bufio.ReadWriter, error)

func (*ContextBeforeLookup) Json

func (c *ContextBeforeLookup) Json(data interface{})

func (*ContextBeforeLookup) Json2

func (c *ContextBeforeLookup) Json2(data interface{}, err error)

func (*ContextBeforeLookup) Ok

func (c *ContextBeforeLookup) Ok(message string)

func (*ContextBeforeLookup) Origin

func (c *ContextBeforeLookup) Origin() string

func (*ContextBeforeLookup) ParseForm

func (c *ContextBeforeLookup) ParseForm() error

func (*ContextBeforeLookup) Redirect

func (c *ContextBeforeLookup) Redirect(url string)

func (*ContextBeforeLookup) RequestBody

func (c *ContextBeforeLookup) RequestBody() ([]byte, error)

func (*ContextBeforeLookup) RequestId

func (c *ContextBeforeLookup) RequestId() string

func (*ContextBeforeLookup) ResponseBody

func (c *ContextBeforeLookup) ResponseBody() []byte

func (*ContextBeforeLookup) ResponseBodySize

func (c *ContextBeforeLookup) ResponseBodySize() int64

func (*ContextBeforeLookup) Scheme

func (c *ContextBeforeLookup) Scheme() string

func (*ContextBeforeLookup) Set

func (c *ContextBeforeLookup) Set(key string, value interface{})

func (*ContextBeforeLookup) SetError

func (c *ContextBeforeLookup) SetError(err error)

func (*ContextBeforeLookup) Status

func (c *ContextBeforeLookup) Status() int64

func (*ContextBeforeLookup) StatusJson

func (c *ContextBeforeLookup) StatusJson(status int, data interface{})

func (*ContextBeforeLookup) Url

func (c *ContextBeforeLookup) Url() string

func (*ContextBeforeLookup) Write

func (c *ContextBeforeLookup) Write(content []byte) (int, error)

type HandlerFuncs

type HandlerFuncs []func(*Context)

func (HandlerFuncs) String

func (handlers HandlerFuncs) String() string

to print regexptree.Node in unit tests.

func (HandlerFuncs) StringIndent

func (handlers HandlerFuncs) StringIndent(indent string) string

type Router

type Router struct {
	RouterGroup
	// contains filtered or unexported fields
}
Example
router := New()

router.Get("/", func(c *Context) {
	fmt.Println("root")
})
users := router.Group("/users")

users.Get("/", func(c *Context) {
	fmt.Println("list users")
})
users.Get(`/(\d+)`, func(c *Context) {
	fmt.Printf("show user: %s\n", c.Param(0))
})

users.Post(`/`, func(c *Context) {
	fmt.Println("create a user")
})
users.Post(`/postx`, func(c *Context) {
})

users = users.Group(`/(\d+)`)

users.Put(`/`, func(c *Context) {
	fmt.Printf("fully update user: %s\n", c.Param(0))
})
users.Put(`/putx`, func(c *Context) {
})

users.Patch(`/`, func(c *Context) {
	fmt.Printf("partially update user: %s\n", c.Param(0))
})
users.Patch(`/patchx`, func(c *Context) {
})

users.Delete(`/`, func(c *Context) {
	fmt.Printf("delete user: %s\n", c.Param(0))
})
users.Delete(`/deletex`, func(c *Context) {
})

request, err := http.NewRequest("GET", "http://localhost/", nil)
if err != nil {
	log.Panic(err)
}
for _, route := range [][2]string{
	{"GET", "/"},
	{"GET", "/users"},
	{"POST", "/users"},
	{"GET", "/users/101/"}, // with a trailing slash
	{"PUT", "/users/101"},
	{"PATCH", "/users/101"},
	{"DELETE", "/users/101"},
} {
	request.Method = route[0]
	request.URL.Path = route[1]
	router.ServeHTTP(nil, request)
}
Output:

root
list users
create a user
show user: 101
fully update user: 101
partially update user: 101
delete user: 101

func New

func New() *Router

func (*Router) BeforeLookup

func (r *Router) BeforeLookup(fun func(ctx *ContextBeforeLookup))

BeforeLookup regiter a function to be run before every route Lookup.

func (*Router) NotFound

func (r *Router) NotFound(handler func(*Context))
Example
router := New()
router.Use(func(c *Context) {
	fmt.Println("middleware")
	c.Next()
})
router.Get("/", func(c *Context) {
	fmt.Println("root")
})

request, err := http.NewRequest("GET", "http://localhost/404", nil)
if err != nil {
	log.Panic(err)
}
rw := httptest.NewRecorder()
router.ServeHTTP(rw, request)

response := rw.Result()
if body, err := ioutil.ReadAll(response.Body); err != nil {
	fmt.Println(err)
} else {
	fmt.Println(response.StatusCode, string(body))
}

router.NotFound(func(c *Context) {
	fmt.Println("404 not found")
})
router.ServeHTTP(nil, request)

request.URL.Path = "/"
router.ServeHTTP(nil, request)
Output:

middleware
404 {"code":"404","message":"Not Found."}
middleware
404 not found
middleware
root

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request)

func (*Router) String

func (r *Router) String() string
Example
router := New()
router.Use(func(c *Context) {
	c.Next()
})

router.Get("/", func(c *Context) {
	fmt.Println("root")
})
users := router.Group("/users")

users.Get("/", func(c *Context) {
	fmt.Println("list users")
})
users.Get(`/(\d+)`, func(c *Context) {
	fmt.Printf("show user: %s\n", c.Param(0))
})

users.Post(`/`, func(c *Context) {
	fmt.Println("create a user")
})
users.Post(`/postx`, func(c *Context) {
})

fmt.Println(router)
Output:

{
  handlers: [
    gitee.com/go-better/dev/net/goa.ExampleRouter_String.func1
  ]
  routes: {
    GET:
    { static: /, data: [
      gitee.com/go-better/dev/net/goa.ExampleRouter_String.func1
      gitee.com/go-better/dev/net/goa.ExampleRouter_String.func2
    ], children: [
      { static: users, data: [
        gitee.com/go-better/dev/net/goa.ExampleRouter_String.func1
        gitee.com/go-better/dev/net/goa.ExampleRouter_String.func3
      ], children: [
        { dynamic: ^/([0-9]+), data: [
          gitee.com/go-better/dev/net/goa.ExampleRouter_String.func1
          gitee.com/go-better/dev/net/goa.ExampleRouter_String.func4
        ] }
      ] }
    ] }
    POST:
    { static: /users, data: [
      gitee.com/go-better/dev/net/goa.ExampleRouter_String.func1
      gitee.com/go-better/dev/net/goa.ExampleRouter_String.func5
    ], children: [
      { static: /postx, data: [
        gitee.com/go-better/dev/net/goa.ExampleRouter_String.func1
        gitee.com/go-better/dev/net/goa.ExampleRouter_String.func6
      ] }
    ] }
  }
  notFound: gitee.com/go-better/dev/net/goa.defaultNotFound
}

func (*Router) Use

func (r *Router) Use(handlers ...func(*Context))
Example
router := New()
router.Use(func(c *Context) {
	fmt.Println("middleware 1 pre")
	c.Next()
	fmt.Println("middleware 1 post")
})
router.Use(func(c *Context) {
	fmt.Println("middleware 2 pre")
	c.Next()
	fmt.Println("middleware 2 post")
})
router.Get("/", func(c *Context) {
	fmt.Println("root")
})

request, err := http.NewRequest("GET", "http://localhost/", nil)
if err != nil {
	log.Panic(err)
}
router.ServeHTTP(nil, request)
Output:

middleware 1 pre
middleware 2 pre
root
middleware 2 post
middleware 1 post

type RouterGroup

type RouterGroup struct {
	// contains filtered or unexported fields
}
Example
g := &RouterGroup{basePath: "/users", routes: make(map[string]*regexptree.Node)}
g.Use(func(*Context) {})
g.Get("/nil", nil)
g.Get("/", func(*Context) {})

fmt.Println(g)
fmt.Println(g.Lookup("HEAD", "/users"))
fmt.Println(g.Lookup("POST", "/users"))
Output:

{
  basePath: /users
  handlers: [
    gitee.com/go-better/dev/net/goa.ExampleRouterGroup.func1
  ]
  routes: {
    GET:
    { static: /users, data: [
      gitee.com/go-better/dev/net/goa.ExampleRouterGroup.func1
      gitee.com/go-better/dev/net/goa.ExampleRouterGroup.func2
    ] }
  }
}

[
  gitee.com/go-better/dev/net/goa.ExampleRouterGroup.func1
  gitee.com/go-better/dev/net/goa.ExampleRouterGroup.func2
] []
[ ] []
Example (ConcatPath_basic)
fmt.Println(RouterGroup{}.concatPath("/"))
fmt.Println(RouterGroup{}.concatPath("/users/"))
fmt.Println(RouterGroup{basePath: "/admin"}.concatPath(`/users/(\d+)`))
Output:

/
/users
/admin/users/(\d+)
Example (ConcatPath_error1)
defer func() {
	fmt.Println(recover())
}()
fmt.Println(RouterGroup{}.concatPath(""))
Output:

router path must not be empty.
Example (ConcatPath_error2)
defer func() {
	fmt.Println(recover())
}()
fmt.Println(RouterGroup{}.concatPath("abc"))
Output:

router path must begin with "/".

func (*RouterGroup) Add

func (g *RouterGroup) Add(method, path string, handler interface{}, args ...interface{}) *RouterGroup
Example (Error1)
defer func() {
	fmt.Println(recover())
}()
g := &RouterGroup{routes: make(map[string]*regexptree.Node)}
g.Add("GET", "/(", func(*Context) {})
Output:

error parsing regexp: missing closing ): `/(`
Example (Error2)
defer func() {
	fmt.Println(recover())
}()
g := &RouterGroup{routes: make(map[string]*regexptree.Node)}
g.Add("GET", "/", func(*Context) {})
g.Add("GET", "/", func(*Context) {})
Output:

path already exists

func (*RouterGroup) Child

func (g *RouterGroup) Child(path string, descs ...string) *RouterGroup

Child returns a new RouterGroup with inline docs. That means, its docs is generated in its parent's REAMED file.

func (*RouterGroup) Delete

func (g *RouterGroup) Delete(path string, handler interface{}, args ...interface{}) *RouterGroup

func (*RouterGroup) DocDir

func (g *RouterGroup) DocDir(dir string) *RouterGroup

func (*RouterGroup) Get

func (g *RouterGroup) Get(path string, handler interface{}, args ...interface{}) *RouterGroup

func (*RouterGroup) GetPost

func (g *RouterGroup) GetPost(path string, handler interface{}, args ...interface{}) *RouterGroup

func (*RouterGroup) Group

func (g *RouterGroup) Group(path string, descs ...string) *RouterGroup

Group returns a new RouterGroup with separate docs. That means, its docs is generated in its own README file unless path is "", ".", or "/".

func (*RouterGroup) Lookup

func (g *RouterGroup) Lookup(method, path string) (HandlerFuncs, []string)

func (*RouterGroup) Patch

func (g *RouterGroup) Patch(path string, handler interface{}, args ...interface{}) *RouterGroup

func (*RouterGroup) Post

func (g *RouterGroup) Post(path string, handler interface{}, args ...interface{}) *RouterGroup

func (*RouterGroup) Put

func (g *RouterGroup) Put(path string, handler interface{}, args ...interface{}) *RouterGroup

func (*RouterGroup) RoutesString

func (g *RouterGroup) RoutesString() string

func (*RouterGroup) String

func (g *RouterGroup) String() string

func (*RouterGroup) Use

func (g *RouterGroup) Use(handlers ...func(*Context)) *RouterGroup

Use adds middlewares to the group, which will be executed for all routes in this group.

func (*RouterGroup) Watch

func (g *RouterGroup) Watch(
	watchers ...func(method, fullPath string, args []interface{}) func(*Context),
) *RouterGroup

Watch watchs every route in the group, and optionally return a middleware to be executed only for this route.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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