ghttp路由功能改进中

This commit is contained in:
John 2018-07-29 22:01:29 +08:00
parent 0be2ddbbcb
commit 19606b8638
14 changed files with 541 additions and 520 deletions

View File

@ -9,7 +9,6 @@ package ghttp
import (
"io/ioutil"
"net/http"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/encoding/gjson"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/util/gregex"
@ -21,7 +20,8 @@ type Request struct {
http.Request
parsedGet *gtype.Bool // GET参数是否已经解析
parsedPost *gtype.Bool // POST参数是否已经解析
queries map[string][]string // GET参数
queryVars map[string][]string // GET参数
routerVars map[string][]string // 路由解析参数
exit *gtype.Bool // 是否退出当前请求流程执行
Id int // 请求id(唯一)
Server *Server // 请求关联的服务器对象
@ -41,7 +41,8 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
request := &Request{
parsedGet : gtype.NewBool(),
parsedPost : gtype.NewBool(),
queries : make(map[string][]string),
queryVars : make(map[string][]string),
routerVars : make(map[string][]string),
exit : gtype.NewBool(),
Id : s.servedCount.Add(1),
Server : s,
@ -58,29 +59,6 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
return request
}
// 初始化GET请求参数
func (r *Request) initGet() {
if !r.parsedGet.Val() {
if len(r.queries) == 0 {
r.queries = r.URL.Query()
} else {
for k, v := range r.URL.Query() {
r.queries[k] = v
}
}
}
}
// 初始化POST请求参数
func (r *Request) initPost() {
if !r.parsedPost.Val() {
// 快速保存,尽量避免并发问题
r.parsedPost.Set(true)
// MultiMedia表单请求解析允许最大使用内存1GB
r.ParseMultipartForm(1024*1024*1024)
}
}
// 获取Web Socket连接对象
func (r *Request) WebSocket() (*WebSocket, error) {
if conn, err := wsUpgrader.Upgrade(r.Response.ResponseWriter.ResponseWriter, &r.Request, nil); err == nil {
@ -92,203 +70,12 @@ func (r *Request) WebSocket() (*WebSocket, error) {
}
}
// 获得指定名称的参数字符串(GET/POST),同 GetRequestString
// 获得指定名称的参数字符串(Router/GET/POST),同 GetRequestString
// 这是常用方法的简化别名
func (r *Request) Get(k string) string {
return r.GetRequestString(k)
}
// 获得指定名称的get参数列表
func (r *Request) GetQuery(k string) []string {
r.initGet()
if v, ok := r.queries[k]; ok {
return v
}
return nil
}
func (r *Request) GetQueryBool(k string) bool {
return gconv.Bool(r.Get(k))
}
func (r *Request) GetQueryInt(k string) int {
return gconv.Int(r.Get(k))
}
func (r *Request) GetQueryUint(k string) uint {
return gconv.Uint(r.Get(k))
}
func (r *Request) GetQueryFloat32(k string) float32 {
return gconv.Float32(r.Get(k))
}
func (r *Request) GetQueryFloat64(k string) float64 {
return gconv.Float64(r.Get(k))
}
func (r *Request) GetQueryString(k string) string {
v := r.GetQuery(k)
if v == nil {
return ""
} else {
return v[0]
}
}
func (r *Request) GetQueryArray(k string) []string {
return r.GetQuery(k)
}
// 获取指定键名的关联数组,并且给定当指定键名不存在时的默认值
func (r *Request) GetQueryMap(defaultMap...map[string]string) map[string]string {
r.initGet()
m := make(map[string]string)
if len(defaultMap) == 0 {
for k, v := range r.queries {
m[k] = v[0]
}
} else {
for k, v := range defaultMap[0] {
v2 := r.GetQueryArray(k)
if v2 == nil {
m[k] = v
} else {
m[k] = v2[0]
}
}
}
return m
}
// 获得post参数
func (r *Request) GetPost(k string) []string {
r.initPost()
if v, ok := r.PostForm[k]; ok {
return v
}
return nil
}
func (r *Request) GetPostBool(k string) bool {
return gconv.Bool(r.GetPostString(k))
}
func (r *Request) GetPostInt(k string) int {
return gconv.Int(r.GetPostString(k))
}
func (r *Request) GetPostUint(k string) uint {
return gconv.Uint(r.GetPostString(k))
}
func (r *Request) GetPostFloat32(k string) float32 {
return gconv.Float32(r.GetPostString(k))
}
func (r *Request) GetPostFloat64(k string) float64 {
return gconv.Float64(r.GetPostString(k))
}
func (r *Request) GetPostString(k string) string {
v := r.GetPost(k)
if v == nil {
return ""
} else {
return v[0]
}
}
func (r *Request) GetPostArray(k string) []string {
return r.GetPost(k)
}
// 获取指定键名的关联数组,并且给定当指定键名不存在时的默认值
// 需要注意的是如果其中一个字段为数组形式那么只会返回第一个元素如果需要获取全部的元素请使用GetPostArray获取特定字段内容
func (r *Request) GetPostMap(defaultMap...map[string]string) map[string]string {
r.initPost()
m := make(map[string]string)
if len(defaultMap) == 0 {
for k, v := range r.PostForm {
m[k] = v[0]
}
} else {
for k, v := range defaultMap[0] {
if v2, ok := r.PostForm[k]; ok {
m[k] = v2[0]
} else {
m[k] = v
}
}
}
return m
}
// 获得post或者get提交的参数如果有同名参数那么按照get->post优先级进行覆盖
func (r *Request) GetRequest(k string) []string {
v := r.GetQuery(k)
if v == nil {
return r.GetPost(k)
}
return v
}
func (r *Request) GetRequestString(k string) string {
v := r.GetRequest(k)
if v == nil {
return ""
} else {
return v[0]
}
}
func (r *Request) GetRequestBool(k string) bool {
return gconv.Bool(r.GetRequestString(k))
}
func (r *Request) GetRequestInt(k string) int {
return gconv.Int(r.GetRequestString(k))
}
func (r *Request) GetRequestUint(k string) uint {
return gconv.Uint(r.GetRequestString(k))
}
func (r *Request) GetRequestFloat32(k string) float32 {
return gconv.Float32(r.GetRequestString(k))
}
func (r *Request) GetRequestFloat64(k string) float64 {
return gconv.Float64(r.GetRequestString(k))
}
func (r *Request) GetRequestArray(k string) []string {
return r.GetRequest(k)
}
// 获取指定键名的关联数组,并且给定当指定键名不存在时的默认值
// 需要注意的是如果其中一个字段为数组形式那么只会返回第一个元素如果需要获取全部的元素请使用GetRequestArray获取特定字段内容
func (r *Request) GetRequestMap(defaultMap...map[string]string) map[string]string {
m := r.GetQueryMap()
if len(defaultMap) == 0 {
for k, v := range r.GetPostMap() {
if _, ok := m[k]; !ok {
m[k] = v
}
}
} else {
for k, v := range defaultMap[0] {
v2 := r.GetRequest(k)
if v2 != nil {
m[k] = v2[0]
} else {
m[k] = v
}
}
}
return m
}
// 获取原始请求输入字符串,注意:只能获取一次,读完就没了
func (r *Request) GetRaw() []byte {
result, _ := ioutil.ReadAll(r.Body)

View File

@ -0,0 +1,84 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package ghttp
import (
"gitee.com/johng/gf/g/util/gconv"
)
// 初始化POST请求参数
func (r *Request) initPost() {
if !r.parsedPost.Val() {
// 快速保存,尽量避免并发问题
r.parsedPost.Set(true)
// MultiMedia表单请求解析允许最大使用内存1GB
r.ParseMultipartForm(1024*1024*1024)
}
}
// 获得post参数
func (r *Request) GetPost(k string) []string {
r.initPost()
if v, ok := r.PostForm[k]; ok {
return v
}
return nil
}
func (r *Request) GetPostBool(k string) bool {
return gconv.Bool(r.GetPostString(k))
}
func (r *Request) GetPostInt(k string) int {
return gconv.Int(r.GetPostString(k))
}
func (r *Request) GetPostUint(k string) uint {
return gconv.Uint(r.GetPostString(k))
}
func (r *Request) GetPostFloat32(k string) float32 {
return gconv.Float32(r.GetPostString(k))
}
func (r *Request) GetPostFloat64(k string) float64 {
return gconv.Float64(r.GetPostString(k))
}
func (r *Request) GetPostString(k string) string {
v := r.GetPost(k)
if v == nil {
return ""
} else {
return v[0]
}
}
func (r *Request) GetPostArray(k string) []string {
return r.GetPost(k)
}
// 获取指定键名的关联数组,并且给定当指定键名不存在时的默认值
// 需要注意的是如果其中一个字段为数组形式那么只会返回第一个元素如果需要获取全部的元素请使用GetPostArray获取特定字段内容
func (r *Request) GetPostMap(defaultMap...map[string]string) map[string]string {
r.initPost()
m := make(map[string]string)
if len(defaultMap) == 0 {
for k, v := range r.PostForm {
m[k] = v[0]
}
} else {
for k, v := range defaultMap[0] {
if v2, ok := r.PostForm[k]; ok {
m[k] = v2[0]
} else {
m[k] = v
}
}
}
return m
}

View File

@ -0,0 +1,88 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package ghttp
import (
"gitee.com/johng/gf/g/util/gconv"
)
// 初始化GET请求参数
func (r *Request) initGet() {
if !r.parsedGet.Val() {
if len(r.queryVars) == 0 {
r.queryVars = r.URL.Query()
} else {
for k, v := range r.URL.Query() {
r.queryVars[k] = v
}
}
}
}
// 获得指定名称的get参数列表
func (r *Request) GetQuery(k string) []string {
r.initGet()
if v, ok := r.queryVars[k]; ok {
return v
}
return nil
}
func (r *Request) GetQueryBool(k string) bool {
return gconv.Bool(r.Get(k))
}
func (r *Request) GetQueryInt(k string) int {
return gconv.Int(r.Get(k))
}
func (r *Request) GetQueryUint(k string) uint {
return gconv.Uint(r.Get(k))
}
func (r *Request) GetQueryFloat32(k string) float32 {
return gconv.Float32(r.Get(k))
}
func (r *Request) GetQueryFloat64(k string) float64 {
return gconv.Float64(r.Get(k))
}
func (r *Request) GetQueryString(k string) string {
v := r.GetQuery(k)
if v == nil {
return ""
} else {
return v[0]
}
}
func (r *Request) GetQueryArray(k string) []string {
return r.GetQuery(k)
}
// 获取指定键名的关联数组,并且给定当指定键名不存在时的默认值
func (r *Request) GetQueryMap(defaultMap...map[string]string) map[string]string {
r.initGet()
m := make(map[string]string)
if len(defaultMap) == 0 {
for k, v := range r.queryVars {
m[k] = v[0]
}
} else {
for k, v := range defaultMap[0] {
v2 := r.GetQueryArray(k)
if v2 == nil {
m[k] = v
} else {
m[k] = v2[0]
}
}
}
return m
}

View File

@ -0,0 +1,79 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package ghttp
import (
"gitee.com/johng/gf/g/util/gconv"
)
// 获得router、post或者get提交的参数如果有同名参数那么按照router->get->post优先级进行覆盖
func (r *Request) GetRequest(k string) []string {
v := r.GetRouterArray(k)
if v == nil {
v = r.GetQuery(k)
}
if v == nil {
v = r.GetPost(k)
}
return v
}
func (r *Request) GetRequestString(k string) string {
v := r.GetRequest(k)
if v == nil {
return ""
} else {
return v[0]
}
}
func (r *Request) GetRequestBool(k string) bool {
return gconv.Bool(r.GetRequestString(k))
}
func (r *Request) GetRequestInt(k string) int {
return gconv.Int(r.GetRequestString(k))
}
func (r *Request) GetRequestUint(k string) uint {
return gconv.Uint(r.GetRequestString(k))
}
func (r *Request) GetRequestFloat32(k string) float32 {
return gconv.Float32(r.GetRequestString(k))
}
func (r *Request) GetRequestFloat64(k string) float64 {
return gconv.Float64(r.GetRequestString(k))
}
func (r *Request) GetRequestArray(k string) []string {
return r.GetRequest(k)
}
// 获取指定键名的关联数组,并且给定当指定键名不存在时的默认值
// 需要注意的是如果其中一个字段为数组形式那么只会返回第一个元素如果需要获取全部的元素请使用GetRequestArray获取特定字段内容
func (r *Request) GetRequestMap(defaultMap...map[string]string) map[string]string {
m := r.GetQueryMap()
if len(defaultMap) == 0 {
for k, v := range r.GetPostMap() {
if _, ok := m[k]; !ok {
m[k] = v
}
}
} else {
for k, v := range defaultMap[0] {
v2 := r.GetRequest(k)
if v2 != nil {
m[k] = v2[0]
} else {
m[k] = v
}
}
}
return m
}

View File

@ -0,0 +1,24 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package ghttp
// 获得路由解析参数
func (r *Request) GetRouterString(k string) string {
if v := r.GetRouterArray(k); v != nil {
return v[0]
}
return ""
}
// 获得路由解析参数
func (r *Request) GetRouterArray(k string) []string {
if v, ok := r.routerVars[k]; ok {
return v
}
return nil
}

View File

@ -27,6 +27,7 @@ import (
"github.com/gorilla/websocket"
"gitee.com/johng/gf/g/os/gtime"
"time"
"container/list"
)
const (
@ -38,6 +39,8 @@ const (
gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年)
gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒)
gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称
gSERVER_STATUS_STOPPED = 0 // Server状态停止
gSERVER_STATUS_RUNNING = 1 // Server状态运行
)
// ghttp.Server结构体
@ -52,11 +55,9 @@ type Server struct {
servedCount *gtype.Int // 已经服务的请求数(4-8字节不考虑溢出情况)同时作为请求ID
closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象)
// 服务注册相关
hmmu sync.RWMutex // handler互斥锁
hmcmu sync.RWMutex // handlerCache互斥锁
handlerMap HandlerMap // 所有注册的回调函数(静态匹配)
handlerTree map[string]interface{} // 所有注册的回调函数(动态匹配,树型+链表优先级匹配)
handlerTree map[string]interface{} // 所有注册的回调函数(路由表,树型结构,哈希表+链表优先级匹配)
handlerCache *gcache.Cache // 服务注册路由内存缓存
hooksCache *gcache.Cache // 事件回调路由内存缓存
// 自定义状态码回调
hsmu sync.RWMutex // status handler互斥锁
statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法)
@ -76,9 +77,6 @@ type Server struct {
errorLogger *glog.Logger // error log日志对象
}
// 域名、URI与回调函数的绑定记录表
type HandlerMap map[string]*handlerRegisterItem
// 路由对象
type Router struct {
Uri string // 注册时的pattern - uri
@ -89,19 +87,36 @@ type Router struct {
Priority int // 优先级,用于链表排序,值越大优先级越高
}
// 域名、URI与回调函数的绑定记录表
type handlerMap map[string]*handlerItem
// http回调函数注册信息
type HandlerItem struct {
ctype reflect.Type // 控制器类型
type handlerItem struct {
ctype reflect.Type // 控制器类型(反射类型)
fname string // 回调方法名称
faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一)
}
// 路由注册项(这里使用了非并发安全的list.List因为该对象的使用统一是由htmu互斥锁保证并发安全)
type handlerRegisterItem struct {
handler *handlerItem // 准确的执行方法内存地址
hooks map[string]*list.List // 当前的事件回调注册,键名为事件名称,键值为事件执行方法链表
router *Router // 注册时绑定的路由对象
}
// 根据特定URL.Path解析后的路由检索结果项
type handlerParsedItem struct {
item *handlerRegisterItem // 路由注册项
values map[string][]string // 特定URL.Path的Router解析参数
}
// HTTP注册函数
type HandlerFunc func(r *Request)
// 文件描述符map
type listenerFdMap map[string]string
// Server表用以存储和检索名称与Server对象之间的关联关系
var serverMapping = gmap.NewStringInterfaceMap()
@ -144,10 +159,8 @@ func GetServer(name...interface{}) (*Server) {
paths : gspath.New(),
servers : make([]*gracefulServer, 0),
methodsMap : make(map[string]bool),
handlerMap : make(HandlerMap),
statusHandlerMap : make(map[string]HandlerFunc),
handlerTree : make(map[string]interface{}),
hooksTree : make(map[string]interface{}),
handlerCache : gcache.New(),
hooksCache : gcache.New(),
cookies : gmap.NewIntInterfaceMap(),
@ -191,7 +204,7 @@ func (s *Server) Start() error {
}
}
if s.status == 1 {
if s.status == gSERVER_STATUS_RUNNING {
return errors.New("server is already running")
}
// 底层http server配置
@ -341,7 +354,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
}(v)
}
s.status = 1
s.status = gSERVER_STATUS_RUNNING
}
// 获取当前监听的文件描述符信息构造成map返回
@ -369,19 +382,3 @@ func (s *Server) getListenerFdMap() map[string]string {
return m
}
// 清空当前的handlerCache
func (s *Server) clearHandlerCache() {
s.hmcmu.Lock()
defer s.hmcmu.Unlock()
s.handlerCache.Close()
s.handlerCache = gcache.New()
}
// 清空当前的hooksCache
func (s *Server) clearHooksCache() {
s.hhcmu.Lock()
defer s.hhcmu.Unlock()
s.hooksCache.Close()
s.hooksCache = gcache.New()
}

View File

@ -41,7 +41,7 @@ func (s *Server) Domain(domain string) *Domain {
// 注意该方法是直接绑定方法的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) error {
for domain, _ := range d.m {
if err := d.s.bindHandlerItem(pattern + "@" + domain, &HandlerItem{
if err := d.s.bindHandlerItem(pattern + "@" + domain, &handlerItem{
ctype : nil,
fname : "",
faddr : handler,

View File

@ -53,7 +53,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
}()
// 路由注册检索
handler := s.getHandler(request)
handler := s.getHandlerWithCache(request)
if handler == nil {
// 如果路由不匹配,那么执行静态文件检索
path := s.paths.Search(r.URL.Path)
@ -74,7 +74,9 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
s.callHookHandler(request, "BeforeServe")
// 执行回调控制器/执行对象/方法
s.callHandler(handler, request)
if handler.handler != nil {
s.callHandler(handler.handler, request)
}
// 事件 - AfterServe
s.callHookHandler(request, "AfterServe")
@ -93,7 +95,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
}
// 初始化控制器
func (s *Server)callHandler(h *HandlerItem, r *Request) {
func (s *Server)callHandler(h *handlerItem, r *Request) {
if h.faddr == nil {
// 新建一个控制器对象处理请求
c := reflect.New(h.ctype)

View File

@ -7,63 +7,54 @@
package ghttp
import (
"errors"
"strings"
"container/list"
"gitee.com/johng/gf/g/util/gregex"
)
// hook缓存项根据URL.Path进行缓存因此对象中带有缓存参数
type hookCacheItem struct {
faddr HandlerFunc // 准确的执行方法内存地址
values map[string][]string // GET解析参数
}
// 事件回调注册方法
// 因为有事件回调优先级的关系,叶子节点必须为一个链表,因此这里只有动态注册
func (s *Server) setHookHandler(pattern string, hook string, handler *HandlerItem) error {
func (s *Server) setHookHandler(pattern string, hook string, handler *handlerItem) error {
return s.setHandler(pattern, handler, hook)
}
// 事件回调 - 检索动态路由规则
// 检索事件回调方法
func (s *Server) searchHookHandler(r *Request, hook string) []*handlerItem {
if item := s.getHandlerWithCache(r); item != nil {
if l, ok := item.hooks[hook]; ok {
items := make([]*handlerItem, 0)
for e := l.Front(); e != nil; e = e.Next() {
items = append(items, e.Value.(*handlerItem))
}
return items
}
}
return nil
}
// 事件回调处理,内部使用了缓存处理.
// 并按照指定hook回调函数的优先级及注册顺序进行调用
func (s *Server) callHookHandler(r *Request, hook string) {
// 如果没有注册事件回调,那么不做后续处理
if len(s.hooksTree) == 0 {
return
}
s.hhcmu.RLock()
defer s.hhcmu.RUnlock()
var hookItems []*hookCacheItem
cacheKey := s.handlerHookKey(r.GetHost(), r.Method, r.URL.Path, hook)
var hookItems []*handlerItem
cacheKey := s.hookHandlerKey(hook, r.Method, r.URL.Path, r.GetHost())
if v := s.hooksCache.Get(cacheKey); v == nil {
hookItems = s.searchHookHandler(r, hook)
if hookItems != nil {
s.hooksCache.Set(cacheKey, hookItems, 0)
}
} else {
hookItems = v.([]*hookCacheItem)
hookItems = v.([]*handlerItem)
}
if hookItems != nil {
for _, item := range hookItems {
for k, v := range item.values {
r.queries[k] = v
}
item.faddr(r)
}
}
}
func (s *Server) searchHookHandler(r *Request, hook string) []*hookCacheItem {
// 生成hook key
func (s *Server) hookHandlerKey(hook, method, path, domain string) string {
return hook + "@" + s.handlerKey(method, path, domain)
}
// 绑定指定的hook回调函数, pattern参数同BindHandler支持命名路由hook参数的值由ghttp server设定参数不区分大小写
func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) error {
return s.setHookHandler(pattern, hook, &HandlerItem{
return s.setHookHandler(pattern, hook, &handlerItem{
ctype : nil,
fname : "",
faddr : handler,
@ -80,8 +71,3 @@ func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerF
}
return nil
}
// 构造用于hooksMap检索的键名
func (s *Server)handlerHookKey(domain, method, uri, hook string) string {
return strings.ToUpper(hook) + "^" + s.handlerKey(domain, method, uri)
}

View File

@ -12,46 +12,32 @@ import (
"strings"
"container/list"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gset"
"fmt"
)
// 路由注册项
type handlerRegisterItem struct {
handler *HandlerItem // 准确的执行方法内存地址
hooks map[string]*glist.List // 当前的事件回调注册,键名为事件名称,键值为事件执行方法链表
router *Router // 注册时绑定的路由对象
}
// 路由检索缓存项根据URL.Path进行缓存因此对象中带有缓存参数
type handlerCacheItem struct {
item *handlerRegisterItem // 路由注册项
values map[string][]string // 特定URL.Path的GET解析参数
}
// 查询请求处理方法
// 内部带锁机制可以并发读但是不能并发写并且有缓存机制按照Host、Method、Path进行缓存
func (s *Server) getHandler(r *Request) *HandlerItem {
// 缓存清空时是直接修改属性,因此必须使用互斥锁
s.hmcmu.RLock()
defer s.hmcmu.RUnlock()
var cacheItem *handlerCacheItem
cacheKey := s.handlerKey(r.GetHost(), r.Method, r.URL.Path)
// 查询请求处理方法.
// 内部带锁机制可以并发读但是不能并发写并且有缓存机制按照Host、Method、Path进行缓存.
func (s *Server) getHandlerWithCache(r *Request) *handlerRegisterItem {
var cacheItem *handlerParsedItem
cacheKey := s.handlerKey(r.Method, r.URL.Path, r.GetHost())
if v := s.handlerCache.Get(cacheKey); v == nil {
cacheItem = s.searchHandler(r)
cacheItem = s.searchHandler(r.Method, r.URL.Path, r.GetHost())
if cacheItem != nil {
s.handlerCache.Set(cacheKey, cacheItem, 0)
}
} else {
cacheItem = v.(*handlerCacheItem)
cacheItem = v.(*handlerParsedItem)
}
if cacheItem != nil {
for k, v := range cacheItem.values {
r.queries[k] = v
if r.Router == nil {
for k, v := range cacheItem.values {
r.routerVars[k] = v
}
r.Router = cacheItem.item.router
}
r.Router = cacheItem.item.router
return cacheItem.item.handler
return cacheItem.item
}
return nil
}
@ -81,7 +67,11 @@ func (s *Server)parsePattern(pattern string) (domain, method, uri string, err er
// 路由注册处理方法。
// 如果带有hook参数表示是回调注册方法否则为普通路由执行方法。
func (s *Server) setHandler(pattern string, handler *HandlerItem, hook ... string) error {
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) error {
// Web Server正字运行时无法动态注册路由方法
if s.status == gSERVER_STATUS_RUNNING {
return errors.New("cannnot bind handler while server running")
}
var hookName string
if len(hook) > 0 {
hookName = hook[0]
@ -103,166 +93,143 @@ func (s *Server) setHandler(pattern string, handler *HandlerItem, hook ... strin
// 注册对象
registerItem := &handlerRegisterItem {
handler : handler,
hooks : make(map[string]*glist.List),
hooks : make(map[string]*list.List),
router : router,
}
if len(hookName) > 0 {
registerItem.handler = nil
registerItem.hooks[hookName] = glist.New()
registerItem.hooks[hookName].PushBack(registerItem)
registerItem.handler = nil
registerItem.hooks[hookName] = list.New()
registerItem.hooks[hookName].PushBack(handler)
}
s.hmmu.Lock()
defer s.hmmu.Unlock()
defer s.clearHandlerCache()
if s.isPatternUriHasFuzzRule(uri) {
// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中。
// 非叶节点为哈希表检索节点按照URI注册的层级进行高效检索直至到叶子链表节点
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
if _, ok := s.handlerTree[domain]; !ok {
s.handlerTree[domain] = make(map[string]interface{})
// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中。
// 非叶节点为哈希表检索节点按照URI注册的层级进行高效检索直至到叶子链表节点
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
if _, ok := s.handlerTree[domain]; !ok {
s.handlerTree[domain] = make(map[string]interface{})
}
// 用于遍历的指针
p := s.handlerTree[domain]
// 当前节点的规则链表
lists := make([]*list.List, 0)
array := strings.Split(uri[1:], "/")
// 键名"*fuzz"代表模糊匹配节点,其下会有一个链表;
// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性;
for k, v := range array {
if len(v) == 0 {
continue
}
// 用于遍历的指针
p := s.handlerTree[domain]
// 当前节点的规则链表
lists := make([]*list.List, 0)
array := strings.Split(uri[1:], "/")
// 键名"*fuzz"代表模糊匹配节点,其下会有一个链表;
// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性;
for k, v := range array {
if len(v) == 0 {
continue
}
// 判断是否模糊匹配规则
if gregex.IsMatchString(`^[:\*]|{[\w\.\-]+}`, v) {
v = "*fuzz"
// 由于是模糊规则,因此这里会有一个*list用以将后续的路由规则加进来
// 检索会从叶子节点的链表往根节点按照优先级进行检索
if v, ok := p.(map[string]interface{})["*list"]; !ok {
p.(map[string]interface{})["*list"] = list.New()
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
} else {
lists = append(lists, v.(*list.List))
}
}
// 属性层级数据写入
if _, ok := p.(map[string]interface{})[v]; !ok {
p.(map[string]interface{})[v] = make(map[string]interface{})
}
p = p.(map[string]interface{})[v]
// 到达叶子节点往list中增加匹配规则(条件 v != "*fuzz" 是因为模糊节点的话在前面已经添加了*list链表)
if k == len(array) - 1 && v != "*fuzz" {
if v, ok := p.(map[string]interface{})["*list"]; !ok {
p.(map[string]interface{})["*list"] = list.New()
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
} else {
lists = append(lists, v.(*list.List))
}
// 判断是否模糊匹配规则
if gregex.IsMatchString(`^[:\*]|{[\w\.\-]+}`, v) {
v = "*fuzz"
// 由于是模糊规则,因此这里会有一个*list用以将后续的路由规则加进来
// 检索会从叶子节点的链表往根节点按照优先级进行检索
if v, ok := p.(map[string]interface{})["*list"]; !ok {
p.(map[string]interface{})["*list"] = list.New()
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
} else {
lists = append(lists, v.(*list.List))
}
}
// 得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)
// 从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在前面)
if len(hookName) == 0 {
// 普通方法路由注册
for _, l := range lists {
pushed := false
for e := l.Front(); e != nil; e = e.Next() {
item := e.Value.(*handlerRegisterItem)
// 判断是否已存在相同的路由注册项
if strings.EqualFold(router.Domain, item.router.Domain) &&
strings.EqualFold(router.Method, item.router.Method) &&
strings.EqualFold(router.Uri, item.router.Uri) {
item.handler = handler
pushed = true
break
}
if s.compareRouterPriority(router, item.router) {
l.InsertBefore(registerItem, e)
pushed = true
break
}
// 属性层级数据写入
if _, ok := p.(map[string]interface{})[v]; !ok {
p.(map[string]interface{})[v] = make(map[string]interface{})
}
p = p.(map[string]interface{})[v]
// 到达叶子节点往list中增加匹配规则(条件 v != "*fuzz" 是因为模糊节点的话在前面已经添加了*list链表)
if k == len(array) - 1 && v != "*fuzz" {
if v, ok := p.(map[string]interface{})["*list"]; !ok {
p.(map[string]interface{})["*list"] = list.New()
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
} else {
lists = append(lists, v.(*list.List))
}
}
}
// 得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)
// 从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在前面)
item := (*handlerRegisterItem)(nil)
// 用以标记 *handlerRegisterItem 指向的对象是否已经处理过,因为多个节点可能会关联同一个该对象
pushedItemSet := gset.NewStringSet()
if len(hookName) == 0 {
// 普通方法路由注册,追加或者覆盖
for _, l := range lists {
pushed := false
address := ""
for e := l.Front(); e != nil; e = e.Next() {
item = e.Value.(*handlerRegisterItem)
address = fmt.Sprintf("%p", item)
if pushedItemSet.Contains(address) {
pushed = true
break
}
if !pushed {
l.PushBack(registerItem)
// 判断是否已存在相同的路由注册项
if strings.EqualFold(router.Domain, item.router.Domain) &&
strings.EqualFold(router.Method, item.router.Method) &&
strings.EqualFold(router.Uri, item.router.Uri) {
item.handler = handler
pushed = true
break
}
if s.compareRouterPriority(router, item.router) {
l.InsertBefore(registerItem, e)
pushed = true
break
}
}
} else {
// 回调方法路由注册
for _, l := range lists {
pushed := false
for e := l.Front(); e != nil; e = e.Next() {
item := e.Value.(*handlerRegisterItem)
// 判断是否已存在相同的路由注册项
if strings.EqualFold(router.Domain, item.router.Domain) &&
strings.EqualFold(router.Method, item.router.Method) &&
strings.EqualFold(router.Uri, item.router.Uri) {
if _, ok := item.hooks[hookName]; !ok {
item.hooks[hookName] = glist.New()
}
item.hooks[hookName].PushBack(handler)
pushed = true
break
}
if s.compareRouterPriority(router, item.router) {
l.InsertBefore(registerItem, e)
pushed = true
break
}
}
if !pushed {
l.PushBack(registerItem)
if pushed {
if len(address) > 0 {
pushedItemSet.Add(address)
}
} else {
l.PushBack(registerItem)
}
}
} else {
// 静态注册
if len(hookName) == 0 {
// 普通方法注册
if method == gDEFAULT_METHOD {
for v, _ := range s.methodsMap {
key := s.handlerKey(domain, v, uri)
if v, ok := s.handlerMap[key]; ok {
v.handler = handler
} else {
s.handlerMap[key] = registerItem
}
// 回调方法路由注册,将方法追加到链表末尾
for _, l := range lists {
pushed := false
address := ""
for e := l.Front(); e != nil; e = e.Next() {
item = e.Value.(*handlerRegisterItem)
address = fmt.Sprintf("%p", item)
if pushedItemSet.Contains(address) {
pushed = true
break
}
} else {
key := s.handlerKey(domain, method, uri)
if v, ok := s.handlerMap[key]; ok {
v.handler = handler
} else {
s.handlerMap[key] = registerItem
// 判断是否已存在相同的路由注册项
if strings.EqualFold(router.Domain, item.router.Domain) &&
strings.EqualFold(router.Method, item.router.Method) &&
strings.EqualFold(router.Uri, item.router.Uri) {
if _, ok := item.hooks[hookName]; !ok {
item.hooks[hookName] = list.New()
}
item.hooks[hookName].PushBack(handler)
pushed = true
break
}
if s.compareRouterPriority(router, item.router) {
l.InsertBefore(registerItem, e)
pushed = true
break
}
}
} else {
// 回调方法注册
if method == gDEFAULT_METHOD {
for v, _ := range s.methodsMap {
key := s.handlerKey(domain, v, uri)
if v, ok := s.handlerMap[key]; ok {
} else {
s.handlerMap[key] = registerItem
}
if pushed {
if len(address) > 0 {
pushedItemSet.Add(address)
}
} else {
key := s.handlerKey(domain, method, uri)
if v, ok := s.handlerMap[key]; ok {
v.handler = handler
} else {
s.handlerMap[key] = registerItem
}
l.PushBack(registerItem)
}
}
}
//b, _ := gparser.VarToJsonIndent(s.handlerTree)
//fmt.Println(string(b))
return nil
}
// 对比两个HandlerItem的优先级需要非常注意的是注意新老对比项的参数先后顺序
// 对比两个handlerItem的优先级需要非常注意的是注意新老对比项的参数先后顺序
// 优先级比较规则:
// 1、层级越深优先级越高(对比/数量)
// 2、模糊规则优先级{xxx} > :xxx > *xxx
@ -289,34 +256,12 @@ func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
}
// 服务方法检索
func (s *Server) searchHandler(r *Request) *handlerCacheItem {
item := s.searchHandlerStatic(r)
if item == nil {
item = s.searchHandlerDynamic(r)
func (s *Server) searchHandler(method, path, domain string) *handlerParsedItem {
domains := []string{ gDEFAULT_DOMAIN }
if !strings.EqualFold(gDEFAULT_DOMAIN, domain) {
domains = append(domains, domain)
}
return item
}
// 检索静态路由规则
func (s *Server) searchHandlerStatic(r *Request) *handlerCacheItem {
s.hmmu.RLock()
defer s.hmmu.RUnlock()
domains := []string{r.GetHost(), gDEFAULT_DOMAIN}
// 首先进行静态匹配
for _, domain := range domains {
if f, ok := s.handlerMap[s.handlerKey(domain, r.Method, r.URL.Path)]; ok {
return &handlerCacheItem{f, nil}
}
}
return nil
}
// 检索动态路由规则
func (s *Server) searchHandlerDynamic(r *Request) *handlerCacheItem {
s.hmmu.RLock()
defer s.hmmu.RUnlock()
domains := []string{gDEFAULT_DOMAIN, r.GetHost()}
array := strings.Split(r.URL.Path[1:], "/")
array := strings.Split(path[1:], "/")
for _, domain := range domains {
p, ok := s.handlerTree[domain]
if !ok {
@ -356,17 +301,22 @@ func (s *Server) searchHandlerDynamic(r *Request) *handlerCacheItem {
for e := lists[i].Front(); e != nil; e = e.Next() {
item := e.Value.(*handlerRegisterItem)
// 动态匹配规则带有gDEFAULT_METHOD的情况不会像静态规则那样直接解析为所有的HTTP METHOD存储
if strings.EqualFold(item.router.Method, gDEFAULT_METHOD) || strings.EqualFold(item.router.Method, r.Method) {
if match, err := gregex.MatchString(item.router.RegRule, r.URL.Path); err == nil && len(match) > 1 {
if strings.EqualFold(item.router.Method, gDEFAULT_METHOD) || strings.EqualFold(item.router.Method, method) {
if match, err := gregex.MatchString(item.router.RegRule, path); err == nil && len(match) > 1 {
//gutil.Dump(match)
//gutil.Dump(names)
handlerItem := &handlerCacheItem{item, nil}
handlerItem := &handlerParsedItem{item, nil}
// 如果需要query匹配那么需要重新正则解析URL
if len(item.router.RegNames) > 0 {
if len(match) > len(item.router.RegNames) {
handlerItem.values = make(map[string][]string)
for index, name := range item.router.RegNames {
handlerItem.values[name] = []string{match[index + 1]}
// 如果存在存在同名路由参数名称,那么执行数组追加
for i, name := range item.router.RegNames {
if _, ok := handlerItem.values[name]; ok {
handlerItem.values[name] = append(handlerItem.values[name], match[i + 1])
} else {
handlerItem.values[name] = []string{match[i + 1]}
}
}
}
}
@ -413,16 +363,8 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string)
return
}
// 判断URI中是否包含动态注册规则
func (s *Server) isPatternUriHasFuzzRule(uri string) bool {
if len(uri) > 1 && gregex.IsMatchString(`^/[:\*]|{[\w\.\-]+}`, uri) {
return true
}
return false
}
// 生成回调方法查询的Key
func (s *Server) handlerKey(domain, method, uri string) string {
return strings.ToUpper(method) + ":" + uri + "@" + strings.ToLower(domain)
func (s *Server) handlerKey(method, path, domain string) string {
return strings.ToUpper(method) + ":" + path + "@" + strings.ToLower(domain)
}

View File

@ -17,7 +17,7 @@ import (
// 绑定URI到操作函数/方法
// pattern的格式形如/user/list, put:/user, delete:/user, post:/user@johng.cn
// 支持RESTful的请求格式具体业务逻辑由绑定的处理方法来执行
func (s *Server)bindHandlerItem(pattern string, item *HandlerItem) error {
func (s *Server)bindHandlerItem(pattern string, item *handlerItem) error {
if s.status == 1 {
return errors.New("server handlers cannot be changed while running")
}
@ -25,7 +25,7 @@ func (s *Server)bindHandlerItem(pattern string, item *HandlerItem) error {
}
// 通过映射数组绑定URI到操作函数/方法
func (s *Server)bindHandlerByMap(m HandlerMap) error {
func (s *Server)bindHandlerByMap(m handlerMap) error {
for p, h := range m {
if err := s.bindHandlerItem(p, h); err != nil {
return err
@ -73,7 +73,7 @@ func (s *Server) nameToUrlPart(name string) string {
// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (s *Server)BindHandler(pattern string, handler HandlerFunc) error {
return s.bindHandlerItem(pattern, &HandlerItem{
return s.bindHandlerItem(pattern, &handlerItem{
ctype : nil,
fname : "",
faddr : handler,
@ -83,21 +83,21 @@ func (s *Server)BindHandler(pattern string, handler HandlerFunc) error {
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义
func (s *Server)BindObject(pattern string, obj interface{}) error {
m := make(HandlerMap)
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
sname := t.Elem().Name()
for i := 0; i < v.NumMethod(); i++ {
method := t.Method(i).Name
key := s.mergeBuildInNameToPattern(pattern, sname, method)
m[key] = &HandlerItem {
m[key] = &handlerItem {
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(method, "Index") {
m[pattern] = &HandlerItem {
m[pattern] = &handlerItem {
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
@ -110,7 +110,7 @@ func (s *Server)BindObject(pattern string, obj interface{}) error {
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数methods支持多个方法注册多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string) error {
m := make(HandlerMap)
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
sname := t.Elem().Name()
@ -121,14 +121,14 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string
return errors.New("invalid method name:" + mname)
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname)
m[key] = &HandlerItem{
m[key] = &handlerItem{
ctype : nil,
fname : "",
faddr : fval.Interface().(func(*Request)),
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
m[pattern] = &HandlerItem {
m[pattern] = &handlerItem {
ctype : nil,
fname : "",
faddr : fval.Interface().(func(*Request)),
@ -141,7 +141,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义
func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
m := make(HandlerMap)
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
for i := 0; i < v.NumMethod(); i++ {
@ -151,7 +151,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
continue
}
key := name + ":" + pattern
m[key] = &HandlerItem {
m[key] = &handlerItem {
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
@ -164,7 +164,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
func (s *Server)BindController(pattern string, c Controller) error {
// 遍历控制器获取方法列表并构造成uri
m := make(HandlerMap)
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
sname := t.Elem().Name()
@ -174,14 +174,14 @@ func (s *Server)BindController(pattern string, c Controller) error {
continue
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname)
m[key] = &HandlerItem {
m[key] = &handlerItem {
ctype : v.Elem().Type(),
fname : mname,
faddr : nil,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
m[pattern] = &HandlerItem {
m[pattern] = &handlerItem {
ctype : v.Elem().Type(),
fname : mname,
faddr : nil,
@ -194,7 +194,7 @@ func (s *Server)BindController(pattern string, c Controller) error {
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
// 第三个参数methods支持多个方法注册多个方法以英文“,”号分隔,不区分大小写
func (s *Server)BindControllerMethod(pattern string, c Controller, methods string) error {
m := make(HandlerMap)
m := make(handlerMap)
v := reflect.ValueOf(c)
e := v.Type().Elem()
t := v.Elem().Type()
@ -205,14 +205,14 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, methods strin
return errors.New("invalid method name:" + mname)
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname)
m[key] = &HandlerItem {
m[key] = &handlerItem {
ctype : t,
fname : mname,
faddr : nil,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
m[pattern] = &HandlerItem {
m[pattern] = &handlerItem {
ctype : t,
fname : mname,
faddr : nil,
@ -228,7 +228,7 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, methods strin
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
func (s *Server)BindControllerRest(pattern string, c Controller) error {
// 遍历控制器获取方法列表并构造成uri
m := make(HandlerMap)
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
// 如果存在与HttpMethod对应名字的方法那么绑定这些方法
@ -239,7 +239,7 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
continue
}
key := name + ":" + pattern
m[key] = &HandlerItem {
m[key] = &handlerItem {
ctype : v.Elem().Type(),
fname : name,
faddr : nil,

View File

@ -6,7 +6,8 @@ import (
)
func main() {
p := "/"
// 基本事件回调使用
p := "/:name/info/{uid}"
s := ghttp.GetServer()
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
"BeforeServe" : func(r *ghttp.Request){ fmt.Println("BeforeServe") },
@ -17,7 +18,7 @@ func main() {
"AfterClose" : func(r *ghttp.Request){ fmt.Println("AfterClose") },
})
s.BindHandler(p, func(r *ghttp.Request) {
r.Response.Write("哈喽世界!")
r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
})
s.SetPort(8199)
s.Run()

View File

@ -0,0 +1,34 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := ghttp.GetServer()
// 多事件回调示例事件1
p1 := "/:name/info/{uid}"
s.BindHookHandlerByMap(p1, map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request){
fmt.Println("打印到Server端终端")
},
})
s.BindHandler(p1, func(r *ghttp.Request) {
r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
})
// 多事件回调示例事件2
p2 := "/{object}/list/{page}.java"
s.BindHookHandlerByMap(p2, map[string]ghttp.HandlerFunc{
"BeforeOutput" : func(r *ghttp.Request){
r.Response.SetBuffer([]byte(
fmt.Sprintf("通过事件修改输出内容, object: %s, page: %s",
r.Get("object"), r.GetRouterString("page"))),
)
},
})
s.SetPort(8199)
s.Run()
}

View File

@ -1,12 +1,9 @@
package main
import (
"gitee.com/johng/gf/g/os/gfile"
)
import "fmt"
func main() {
ghttp.
gfile.PutContentsAppend("/tmp/test", "1")
gfile.PutContentsAppend("/tmp/test", "2")
gfile.PutContentsAppend("/tmp/test", "3")
var a []int
a = append(a, 1)
fmt.Println(a)
}