设计一个用于RESTFUL API 路由器

设计一个用于RESTFUL API 路由器
1. 什么是REST

REST是一组架构约束条件和原则,一般满足以下条件则可以说某个资源是restful风格的:

  1. 每一个URI代表一种资源;
  2. 客户端和服务器之间,传递这种资源的某种表现层;
  3. 客户端通过四个HTTP(GET、PUT、POST、DELETE)动词,对服务器端资源进行操作,实现"表现层状态转化"。
  4. Web应用要满足REST最重要的原则是:客户端和服务器之间的交互在请求之间是无状态的,即从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外此请求可以由任何可用服务器回答。
2. GithubAPI 很好地使用了RESTFUL架构,按表示层需要的数据组织 API 比按系统功能组织具有明显的优势。
3. 设计过程:
本次实现参考了 GoRestful、Go-json-rest、Gorilla Mux等包的设计架构和部分方法的实现
  1. http请求

    //封装一个http.Request,构造restful风格的Request
    type Request struct {
    	*http.Request
    	PathParams map[string]string
    	Env        map[string]interface{}
    }
    //读入Request并判断是本地地址还是其它地址
    func (r *Request) BaseUrl() *url.URL
    //解析URL中的路径信息
    func (r *Request) UrlFor(path string, queryParams map[string][]string) *url.URL
    //将Request中的信息解码为Json风格
    func (r *Request) DecodeJsonPayload(v interface{}) error
    //跨域资源请求CORS
    type CorsInfo struct {
    	IsCors      bool
    	IsPreflight bool
    	Origin      string
    	OriginUrl   *url.URL
    
    	AccessControlRequestMethod  string
    	AccessControlRequestHeaders []string
    }
    
  2. http响应:

    //ResponseWriter 将http.response响应封装为json风格
    type ResponseWriter interface {
    	//ResponseWriter头部信息
    	Header() http.Header
    	// The Content-Type header is set to "application/json"
    	WriteJson(v interface{}) error
    	// 将数据解码为JSON格式
    	EncodeJson(v interface{}) ([]byte, error)
    	//与WriteJson相似
    	WriteHeader(int)
    }
    
    // 对应于 http.Error(w, "error message", code)的错误
    func Error(w ResponseWriter, error string, code int)
    // 如果地址为不存在,返回404
    func NotFound(w ResponseWriter, r *Request)
    //封装 ResponseWriter
    type responseWriter struct {
    	http.ResponseWriter
    	wroteHeader bool
    }
    //设计ResponseWriter的方法
    //写头部信息
    func (w *responseWriter) WriteHeader(code int)
    //解码为Json格式
    func (w *responseWriter) EncodeJson(v interface{}) ([]byte, error)
    
  3. 路由 Route设计

    //定义一个路由
    type Route struct {
    	HttpMethod string
    	PathExp    string
    	Func       HandlerFunc
    }
    //路由路径匹配,通过路由表中的信息访问
    func (route *Route) Makepath(pathParams map[string]string) string
    //获取路径头部信息
    func getHead(path string, handlerFunc HandlerFunc) *Route 
    //http请求Get方法,获取信息
    func Get(path string, handlerFunc HandlerFunc) *Route 
    //http请求Post方法,插入信息
    func Post(path string, handlerFunc HandlerFunc) *Route
    //http请求Patch方法,不安全的更新信息
    func Patch(path string, handlerFunc HandlerFunc) *Route
    //http请求Delete方法,删除信息
    func Delete(path string, handlerFunc HandlerFunc) *Route
    //http请求Options方法,URL安全验证
    func Options(path string, handlerFunc HandlerFunc) *Route
    
  4. 路由器设计,用一个动态路由表记录路由信息

    //路由器结构
    type router struct {
    	Routes  []*Route//保存多个路由
    	disable bool //是否可达
    	index   map[*Route]int
    	trie    *trie.Trie //trie树保存路由信息
    }
    //将路由封装到app中
    func MakeRouter(routes ...*Route) (App, error)
    //在URL中进行信息匹配
    func (rt *router) AppFunc() HandlerFunc
    func escapedPath(urlObj *url.URL) string
    func escapedPathExp(pathExp string) (string, error)
    // 将路径保存到Trie路由结构中
    func (rt *router) start() error
    // 返回第一个定义的路由
    func (rt *router) ofFirstDefinedRoute(matches []*trie.Match) *trie.Match
    // 返回匹配到的第一个路由
    func (rt *router) findRouteFromURL(httpMethod string, urlObj *url.URL)
    // 解析URL中的路由地址
    func (rt *router) findRoute(httpMethod, urlStr string) (*Route, map[string]string, bool, error)
    
  5. middle中间架构通过接口设计,使得函数设计更加简单

    // 定义一个HandelerFunc,功能类似于http.handle
    type HandlerFunc func(ResponseWriter, *Request)
    
    //定义一个app接口,包含一个AppFunc方法用于封装
    type App interface {
    	AppFunc() HandlerFunc
    }
    //一个app适配器,使得函数编写变得简单
    type AppSimple HandlerFunc
    
    // AppFunc 使得 AppSimple 继承App接口
    func (as AppSimple) AppFunc() HandlerFunc {
    	return HandlerFunc(as)
    }
    //定义一个中间件返回解析后的HandlerFunc
    type Middleware interface {
    	MiddlewareFunc(handler HandlerFunc) HandlerFunc
    }
    // 对中间件进行解码
    func WrapMiddlewares(middlewares []Middleware, handler HandlerFunc) HandlerFunc
    
  6. API设计,通过封装好的App实现RESTFUL风格

    
    // 用栈保存app和middleware中间件
    type Api struct {
    	stack []Middleware
    	app   App
    }
    
    // 创建一个api并返回
    func NewApi() *Api
    //插入api的元素
    func (api *Api) Use(middlewares ...Middleware)
    // MakeHandler 对api进行解码,返回以一个http.Handler
    func (api *Api) MakeHandler() http.Handler
    //定义中间件,实现api界面友好功能
    var DefaultDevStack = []Middleware{
    	&AccessLogApacheMiddleware{},
    }
    
  7. 路由表结构设计

    Trie树,又称单词查找树键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串,一般有以下性质。

    1. 根节点不包含字符,其它每个节点都只包含一个字符
    2. 从根节点到某个节点,路径上的字符连接起来,对于当前节点对应的字符串
    3. 每个节点的所有子节点包含的字符都不相同

    从上面的描述可以看出,trie树的结构与URL中的路径信息的结构相一致,因此我们直接借用goRestful的设计。

    //树的节点信息
    type node struct {
    	HttpMethodToRoute map[string]interface{}
        //子节点
    	Children       map[string]*node
    	ChildrenKeyLen int
        // '/path'或'/.path'类型的节点
    	ParamChild *node
    	ParamName  string
        // ‘/#path'类型的节点
    	RelaxedChild *node
    	RelaxedName  string
        // 其它类型的节点
    	SplatChild *node
    	SplatName  string
    }
    //Trie结构
    type Trie struct {
    	root *node
    }
    //将路由保存到路由表中
    func (n *node) addRoute(httpMethod, pathExp string, route interface{}, usedParams []string) error
    //信息解码
    func splitParam(remaining string) (string, string)
    //松弛信息解码
    func splitRelaxed(remaining string) (string, string)
    // 路由信息压缩
    func (n *node) compress()
    //路由参数匹配
    type paramMatch struct {
    	name  string
    	value string
    }
    //参数上下文匹配
    type findContext struct {
    	paramStack []paramMatch
    	matchFunc  func(httpMethod, path string, node *node)
    }
    //参数映射
    func (fc *findContext) paramsAsMap() map[string]string
    
    
  8. 路由与URL是否匹配

    //http请求中的参数和路由表中保存的路径匹配的
    type Match struct {
    	// Same Route as in AddRoute
    	Route interface{}
    	// map of params matched for this result
    	Params map[string]string
    }
    //查找路由
    func (n *node) find(httpMethod, path string, context *findContext)
    //
    
4. 功能测试:
该过程只是对路由器的功能进行一小部分展示,在第六小节性能测试中我们会把所有的githubApi保存到一个struct结构中进行整体测试。
  1. //获取用户信息
    curl -i -X GET http://127.0.0.1:8080/users/1834
    

    在这里插入图片描述

  2. //获取所有用户
    curl -i -X GET http://127.0.0.1:8080/users
    

    在这里插入图片描述

  3. //修改用户信息
    curl -i -X PUT http://127.0.0.1:8080/users/1834 \-d '{"Code":"1834","Name":"bbb"}'
    

    在这里插入图片描述

  4. //增加一个用户
    curl -i -X POST http://127.0.0.1:8080/users \-d '{"Code":"1534","Name":"bbb"}'
    

    在这里插入图片描述

  5. //删除一个用户
    curl -i -X DELETE http://127.0.0.1:8080/users/1734
    

    在这里插入图片描述
    删除操作之和再次获取所有用户信息:可以看到code为1734的User被删除。
    在这里插入图片描述

  6. //访问外部网址github.com
    curl -i -X GET http://127.0.0.1:8080/link/github.com
    

    在这里插入图片描述

  7. //获取信息
    curl -i -X GET http://127.0.0.1:8080/message
    

    在这里插入图片描述

  8. //以流方式获取信息
    curl -i -X GET http://127.0.0.1:8080/stream
    

    在这里插入图片描述

  9. //读取txt文本信息
    curl -i -X GET http://127.0.0.1:8080/message.txt
    

    在这里插入图片描述

5. 集成测试

测试定义的所有方法:go test -v
在这里插入图片描述
在这里插入图片描述

6. 性能测试:借助go-http-routing-benchmark包的方法来实现
  1. 测试该路由器是否可以访问所有的GithubApi

    对所有的GithubApi进行封装,下面展示一部分。
    在这里插入图片描述

    利用以下方法进行所有api的测试

    func TestRouters(t *testing.T) {
    	loadTestHandler = true
        //routers为封装的所有路由
    	for _, router := range routers {
    		req, _ := http.NewRequest("GET", "/", nil)
    		u := req.URL
    		rq := u.RawQuery
    
    		for _, api := range apis {
    			r := router.load(api.routes)
    
    			for _, route := range api.routes {
    				w := httptest.NewRecorder()
    				req.Method = route.method
    				req.RequestURI = route.path
    				u.Path = route.path
    				u.RawQuery = rq
    				r.ServeHTTP(w, req)
    				if w.Code != 200 || w.Body.String() != route.path {
    					t.Errorf(
    						"%s in API %s: %d - %s; expected %s %s\n",
    						router.name, api.name, w.Code, w.Body.String(), route.method, route.path,
    					)
    				}
    			}
    		}
    	}
    
    	loadTestHandler = false
    }
    
    

    测试结果:

    在这里插入图片描述

  2. 对不同长度,不同类型的Api进行多次测试,测试路由器的性能

    const fiveBrace = "/{a}/{b}/{c}/{d}/{e}"
    const fiveRoute = "/test/test/test/test/test"
    func benchRoutes(b *testing.B, router http.Handler, routes []route)
    func BenchmarkGoJsonRest_Param(b *testing.B)
    
    const twentyColon = "/:a/:b/:c/:d/:e/:f/:g/:h/:i/:j/:k/:l/:m/:n/:o/:p/:q/:r/:s/:t"
    const twentyBrace = "/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}"
    const twentyRoute = "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t"
    func BenchmarkGoJsonRest_Param20(b *testing.B)
    func BenchmarkGoJsonRest_ParamWrite(b *testing.B)
    

    测试结果:
    在这里插入图片描述

    下面为用golang实现的其它路由器的测试结果:

RouterGithubApi
Beego407248 B
Goji95736 B
Gorilla Mux1557216 B
HttpRouter44344 B

对比可以看到,本次实现的路由器有着较好的性能。

7. 实验结论:

RESTFUL 风格的Api 按照表示层需要的数据组织api,相比其它类型的更加友好和直观,在我们设计路由器时也可以分块设计并且根据需求设计。这将会成为 web 应用间互操作的主流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Express是一个非常流行的Node.js框架,用于构建Web应用程序和RESTful API。在这个框架中,我们可以创建一个RESTful API版本1(v1)。 首先,我们需要安装Express并初始化一个新的项目。然后,我们可以创建一个新的路由文件夹来定义我们的API端点。在这个文件夹中,我们可以创建一个新的JavaScript文件,例如"api-v1.js",并在其中定义我们的API端点和相应的处理程序。 在我们的API文件中,我们可以使用Express的路由器来定义不同的HTTP请求方法(GET、POST、PUT、DELETE等)和相应的URL路径。我们可以为每个端点定义处理程序函数,以响应来自客户端的请求并执行相应的操作。 例如,我们可以创建一个GET端点来检索所有用户的数据,一个POST端点来创建新用户的数据,一个PUT端点来更新现有用户的数据,以及一个DELETE端点来删除用户的数据。 在每个处理程序函数中,我们可以编代码来处理客户端请求,并用合适的响应返回数据。我们可以使用Express的内置方法来发送JSON格式的响应,并在必要时设置相应的HTTP状态码。 最后,我们需要将我们的API文件导出并在主应用程序文件中使用它。我们可以使用Express的app.use()方法来将我们的API路由文件挂载到主程序中,并给它一个URL路径前缀,例如"/api/v1"。 通过这种方式,我们就可以在Express中创建一个RESTful API v1,并为客户端提供访问我们定义的不同端点的能力。这样,我们就可以与客户端建立起一个灵活和可扩展的接口,以访问和操作我们的数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值