一个go的web框架 boy

slclub · 2020-06-16 15:31:37 · 1223 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2020-06-16 15:31:37 的主题,其中的信息可能已经有所发展或是发生改变。

Boy framework

源码 English 中文

概述

一个轻型的go web 框架。80% 的代码都是用接口实现的。支持自定义执行节点 以及中间件。

这个Boy 框架 像是是一个demo,仅仅是定义一些别名简写方便使用,定义了一些执行节点。

您也可以迅速的用它重写一个属于自己的框架。可重可轻,不过需要知道相关的几个包都是

做什么的,以及如何使用他们。

通过ini配置文件我们可以 同时监听http 和 https 甚至是websocket, 以及静态服务配置等

比较工程化。

通过这些年的工作,一个可伸缩的框架才是我理想中的框架。所以我们此框架最终主要为了

以下几点:

1 伸缩性:框架具有伸缩性,可重,可轻,满足不同的需求。

2 可变:框架可以根据不同配置参数适当的变化运行方式,不需要再去改框架代码。减少依赖和修改源码。

3 简洁:更为简洁统一的写法,很多时候不必关系内部处理。

4 最小重写:当重写时尽可能的少写代码,仅改需要改的部分即可。

安装

你需要先安装go 环境。暂时还没有做docker 支持

Go 版本 1.14+

go get -u github.com/slclub/boy

如果您使用go mod 管理您的项目。那么仅仅需要import此包即可

快速开始

package main
import (
    "github.com/slclub/boy"
    "github.com/slclub/gnet"
)

func main() {
    boy.R.GET("/example/ping", func(this gnet.Contexter) {
        this.Response().WriteString("Hello World!")
    }) 
    boy.Run()
}

Content List

Installation

To install boy framework, you should install Go first.

Go required 1.14+

go get -u github.com/slclub/boy

If you use "go mod" to manager your project. just import the packeage.

import "github.com/slclub/boy"

If you want to use the latest code. please change to latest version in the go.mod file.

require (
    github.com/slclub/boy latest
)

Quick start

package main                                                                                                                                                                                                                    

import (
    "github.com/slclub/boy"
    "github.com/slclub/gnet"
)

func main() {
    boy.R.GET("/example/ping", func(this gnet.Contexter) {
        this.Response().WriteString("Hello World!")
    }) 
    boy.Run()
}

Benchmarks

This benchmark results come from gcore bench testing.

BenchmarkServer-4         1000000          1012 ns/op         528 B/op           7 allocs/op
PASS
ok        github.com/slclub/gcore    1.029s

Configration

Install from boy.Install()

Config file template

If there is no config file. It will automaticlly generate one.

You don't have to worry about it.

Router

Import from grouter

  • Restfull router added.

      boy.R.
    
      GET(url string, ctx gnet.HandleFunc)
      POST(url string, ctx gnet.HandleFunc)
      PUT(url string, ctx gnet.HandleFunc)
      DELETE(url string, ctx gnet.HandleFunc)
      PATCH(url string, ctx gnet.HandleFunc)
      HEAD(url string, ctx gnet.HandleFunc)
      OPTIONS(url string, ctx gnet.HandleFunc)
      // can accept any http.Method request.
      ANY(url string, ctx gnet.HandleFunc)
    
  • Use Group

    // 
    boy.R.Group(func(group grouter.IGroup){
        // add this group routes a middlerware.
        group.Use(func(ctx gnet.Contexter) {})
        // Deny a middlerware for these routes.
        group.Deny(gnet.HandleFunc)
        boy.R.GET(url string, ctx gnet.HandleFunc)
        boy.R.POST(url string, ctx gnet.HandleFunc)
        ...
    })
  • Use Defined code handle, 404, 405,500;
    boy.R.BindCodeHandle(404, gnet.HandleFunc)
    boy.R.BindCodeHandle(405, gnet.HandleFunc)
    ...

Context

gnet.Contexter

Request

Request object. Obtain all kinds of requested information and parameter routes in the object here

Source Code, You can read it from this link.

Request Paramters

You can use these methods get paramters from path param, query, form, and so on.

You don't care about where the paramters come from.

type RequestParameter interface {
    // Get param inetface ----------------------------------------------------------
    // just get string by key string.
    // q=a
    // return a
    GetString(key string, args ...string) (value string, ret bool)
    // q[]=a&q[]=b
    // return []string{a, b}
    GetArray(key string, args ...[]string) ([]string, bool)
    // q[a]=a&q[b]=b
    // return map[string]string{"a":"a", "b":"b"}
    GetMapString(key string, args ...map[string]string) (map[string]string, bool)
    GetInt64(key string, args ...int64) (int64, bool)
    GetInt(key string, args ...int) (int, bool)

    // set
    SetParam(key, value string)

    // input :// data
    BodyByte() ([]byte, error)
}
    // example
    boy.R.GET(url, func(ctx gnet.Contexter){
        s1, ok := ctx.Request().GetString(key, default_value string)
    })
Request Interface
type IRequest interface {
    GetHttpRequest() *http.Request
    RequestParameter

    // init and reset
    InitWithHttp(*http.Request)
    Reset()

    // header
    GetHeader(key string) string
    ContentType(args ...bool) string
    GetRemoteAddr() string
    //file
    FormFile(key string) (*multipart.FileHeader, error)
}
    // example
    boy.R.GET(url, func(ctx gnet.Contexter){
        s1 := ctx.Request().ContentType()
    })

Response

Rewrite the interface of http.ResponseWriter.

type IResponse interface {
    http.ResponseWriter
    http.Hijacker
    http.Flusher
    http.CloseNotifier

    // Returns the HTTP response status code of the current request.
    Status() int

    // Returns the number of bytes already written into the response http body.
    // See Written()
    Size() int

    // Writes the string into the response body.
    WriteString(string) (int, error)

    // Returns true if the response body was already written.
    Written() bool

    // Forces to write the http header (status code + headers).
    FlushHeader()

    // get the http.Pusher for server push
    Pusher() http.Pusher

    // init reset
    Reset()
    InitSelf(http.ResponseWriter)

    // update ResponseWriter.Header()
    Headers(key, value string)
}
    // example
    boy.R.GET(url, func(ctx gnet.Contexter){
        ctx.Response().Write([]bytes{"hello girls"})
        ctx.Response().WriteString("hello girls")
    })

Contexter Api

Source Code

These methods been used in the same way.

    // example
    boy.R.GET(url, func(ctx gnet.Contexter){
        ctx.Xxx(args ...)
    })
    Reset()
    // gerror.StackError([]error) support:
    // Push(err error)
    // Pop() error
    // Size() int
    GetStackError() gerror.StackError
    SetSameSite(st http.SameSite)

    ClientIP() string
    //
    GetHandler() HandleFunc
    SetHandler(HandleFunc)

    GetExecute() Executer
    SetExecute(exe Executer)

    //redirect
    Redirect(location string, args ...int)
Contexter Setter Getter

For custome key-value pairs extension.

type SetterGetter interface {
    // setter
    Set(key string, val interface{})
    // getter
    Get(key string) (interface{}, bool)
    GetString(key string) string
    GetInt(key string) int
    GetInt64(key string) int64
}
Contexter Get Set Request
// request from other place.
type IContextRequest interface {
    Request() IRequest
    SetRequest(IRequest) bool

    // cookie
    SetCookie(name, value string, args ...interface{})
    Cookie(string) (string, error)
}
Contexter Get Set Response
// response to client or other server.
type IContextResponse interface {
    Response() IResponse
    SetResponse(IResponse) bool
}
Contexter Abort

Execution process jump control.

// interrupt interface.
type Aborter interface {
    // abort current handle .
    Abort()
    AbortStatus(int)
    // Jump out of the whole execution process.
    // break whole work flow.
    Exit()
}

Save File

Upload file.

f1 = func(ctx gnet.Contexter) {
    f, err := ctx.Request().FormFile("file")
    gnet.SaveUploadFile(f, "/tmp/glog/test")
}

MiddlerWare

Source Code. You can use or deny any flow node or url handle middlerware.

No matter where the middleware is used.

  • interface
type Middler interface {
    // public excuter interface.
    flow.IExecuteNode
    // middle ware interface
    Use(gnet.HandleFunc)
    Deny(gnet.HandleFunc)

    GetHandle(i int) (gnet.HandleFunc, string)
    Combine(Middler)
    Size() int
}
  • Use Deny ```go example: mf1 := func(ctx gnet.Contexter){} mf2 := func(ctx gnet.Contexter){} mf3 := func(ctx gnet.Contexter){}

    // public before node use or deny middlerware. boy.MiddlerBefore.Use(mf1) boy.MiddlerBefore.Use(mf2)

    // The url1 handle will only execute the first gnet.HandleFunc. // flow: mf1(ctx), handle boy.R.Deny(mf2) boy.R.GET(url1, func(ctx gnet.Contexter){})

    // The url2 will execute boths handles. // flow: mf1(ctx), mf2(ctx), mf3(ctx) handle boy.R.Use(mf3) boy.R.GET(url2, func...)

    // after node use or deny middlerware. boy.MiddlerAfter.Use ...


- Group Router Use Deny

Router Group use or deny middlerware.

```go
    example:
    mf1 := func(ctx gnet.Contexter){}
    mf2 := func(ctx gnet.Contexter){}
    mf3 := func(ctx gnet.Contexter){}

    boy.MiddlerBefore.Use(f1)
    boy.MiddlerBefore.Use(f2)

    // These routes of group have to same flow way.
    // flow: mf1(ctx), mf3(ctx) , handle, mf5(ctx)
    boy.R.Group(func(group grouter.IGroup){
        group.Deny(mf2)
        group.Use(mf3)
        group.Deny(mf4)
        boy.R.GET(url1, gnet.HandleFunc)
        boy.R.GET(url2, gnet.HandleFunc)
        boy.R.GET(url3, gnet.HandleFunc)
    })

    mf4 := func(ctx gnet.Contexter){}
    mf5 := func(ctx gnet.Contexter){}

    boy.MiddlerAfter.Use(f4)
    boy.MiddlerAfter.Use(f5)

Static

Static service listening

You just need to change the file of etc/go.ini

# static service setting.
[static_service]
# Static root path.
# Default value is empty
# If you set the value of this field. It should be an absoluted path.
root=

# example: 
# Listening to multiple static folders needs to be separated by backspaces
service=sa  sb  
# param     @1  aliase of static path that is used to url.
#           @2  actual floder path.
#           @3  folder listing. true,on,yes,false; Where can browse directories.
sa=source  source      true
sb=static  assets     true

Custom

// examples:

func NewRouter() grouter.Router {

    r := &router{}
    r.initself(grouter.NewRouter())
    //r.SetStore(grouter.NewStore())
    //r.SetDecoder(grouter.NewPath())
    //r.code_handles = make(map[int]gnet.HandleFunc)
    //bind code handle
    r.BindCodeHandle(http.StatusNotFound, func(ctx gnet.Contexter) {
        ctx.Response().WriteHeader(404)
        ctx.Response().WriteString("grouter 404 not found")
    })  
    r.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(404)
        w.Write([]byte("grouter 404 not found"))
    }   
    //http.StatusMethodNotAllowed
    //r.BindCodeHandle(http.StatusMethodNotAllowed, grouter.http_405_handle)
    //r.BindCodeHandle(http.StatusInternalServerError, grouter.http_500_handle)
    return r
}

type router struct {
    grouter.Router
    NotFoundHandler func(http.ResponseWriter, *http.Request)
}

func (r *router) initself(rr grouter.Router) {
    r.Router = rr
}



router := NewRouter()
router.SetKey("router")
boy.App.DriverRegister(router)

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1223 次点击  ∙  1 赞  
加入收藏 微博
5 回复  |  直到 2020-06-17 00:21:50
focusonline
focusonline · #1 · 5年之前

这个框架有啥特色吗? 和别的框架相比有什么优势?

slclub
slclub · #2 · 5年之前

@focusonline 性能上应该可以占一些优势; 获取参数方法统一;多一些自定义;分为多个执行节点 可以在 handle 前后随意 添加或者禁用中间件。 一些功能,尽力从配置ini 中配置即可,不需要修改代码。如静态服务等配置下即可。

slclub
slclub · #3 · 5年之前

还有端口 http https 启用禁用等 都是在 ini中直接配置即可,无需重新编译; 即使不重启服务,修改过的配置过一会也会生效。

focusonline
focusonline · #4 · 5年之前
slclubslclub #2 回复

@focusonline 性能上应该可以占一些优势; 获取参数方法统一;多一些自定义;分为多个执行节点 可以在 handle 前后随意 添加或者禁用中间件。 一些功能,尽力从配置ini 中配置即可,不需要修改代码。如静态服务等配置下即可。

那不错, 期待你的benchmark数据了.

slclub
slclub · #5 · 5年之前

@focusonline 嗯,我也刚刚写; 有很多测试以及数据还不全;还缺不少基础的中间件。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传