go语言之REST API路由库

REST API路由库

  • 1、REST API
    • 1.1 简介
    • 1.2 课程要求
  • 2、Route
    • 2.1 设计说明
    • 2.2 程序主要函数
    • 2.3 单元测试
      • 2.3.1 路由正常连接
      • 2.3.2 没有找到路由
      • 2.3.3 静态路由连接
      • 2.3.4 过滤器
      • 2.3.5 带参数的过滤器
    • 2.4 基准测试
    • 2.5 覆盖率测试
    • 2.6 功能测试
      • 2.6.1 Get
      • 2.6.2 Post
      • 2.6.3 Put
      • 2.6.4 Delete
  • 3、代码地址

1、REST API

1.1 简介

REST(Representational State Transfer),表述性状态传递,是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。
REST是一种软件架构风格,指的是一组架构约束条件和原则,而REST API则是满足这种软件架构风格的API架构。

  • 资源(Resources)。资源是REST风格的主体,通过表现层来展现。我们平时网上访问到图片、文字、文档、多媒体等就是资源,一般通过 URI 来定位。也就是说,一个 URI 就表示一个资源。
  • 表现层(Representation)。资源是作为一个具体的实体信息,它可以有多种的展现方式。而把实体展现出来就是表现层。例如一个 txt 文本信息,它可以输出成 html、json 等。
  • 状态转化(State Transfer)。访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,就涉及到数据和状态的变化。而 HTTP 协议是无状态的,那么这些状态肯定保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,就要通过某种方式来通知它。客户端能通知服务器端的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。

1.2 课程要求

设计一个专用于REST API高性能路由库

2、Route

2.1 设计说明

route是我参照母所设计的一个用于REST API的路由库,参照了mux路由库。
该路由库支持Http方法的四种基本操作:Get、Post、Put和Delete,支持自定义正则表达式;同时路由库也可以为静态文件创建路由,为文件提供/static;该库还实现了过滤器功能,可以过滤所有请求或仅在需要的REST URL参数存在时应用过滤器。

2.2 程序主要函数

此路由库仅有routes.go一个主要文件,设计的所有函数都在此文件中。主要内容如下:

  • route结构体:此结构体是路由的基本内容,包括方法名字,正则表达式内容,参数和handler对象;
  • Get函数:创建一个用于GET请求的路由;
  • Put函数:创建一个用于PUT请求的路由;
  • Post函数:创建一个用于POST请求的路由;
  • Del函数:创建一个用于DELETE请求的路由;
  • Static函数:为静态http请求创建一个新的路由,可以提供指定目录中的静态文件;
  • Filter函数:添加一个过滤器强制过滤所有请求;
  • FilterParam函数:先判断是否有URL参数,如果有URL参数,则添加一个过滤器过滤请求;
  • AddRoute函数:为handler对象添加路由,并解析url的内容,支持正则表达式。
  • ServeHTTP函数:提供http服务,对所有路由进行处理。
  • Write, Header, WriteHeader 函数:设置responseWriter结构体的具体参数,用于跟踪http响应是否被写入。

2.3 单元测试

本来想要对Get,Post,Put和Delete这四个函数都设置对应的测试的,但是写不来,mux库中也没有找到可以借鉴的,最后没有对这四个基本函数进行测试,而是测试了路由的连接情况以及过滤器的过滤功能。

2.3.1 路由正常连接

路由正常连接的代码为200.

var HandlerOk = func(w http.ResponseWriter, r *http.Request) {
     
	//fmt.Fprintf(w, "hello world")
	w.WriteHeader(http.StatusOK)
}

func TestRouteOk(t *testing.T) {
     
	r, _ := http.NewRequest("GET", "/student/three/LiMing?learn=Chinese", nil)
	w := httptest.NewRecorder()

	handler := new(RouteMux)
	handler.Get("/student/:grade/:name", HandlerOk)
	handler.ServeHTTP(w, r)

	params := r.URL.Query()
	gradeParam := params.Get(":grade")
	nameParam := params.Get(":name")
	learnParam := params.Get("learn")

	if gradeParam != "three" {
     
		t.Errorf("url param set to [%s]; want [%s]", gradeParam, "three")
	}
	if nameParam != "LiMing" {
     
		t.Errorf("url param set to [%s]; want [%s]", nameParam, "LiMing")
	}
	if learnParam != "Chinese" {
     
		t.Errorf("url param set to [%s]; want [%s]", learnParam, "Chinese")
	}
}

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

2.3.2 没有找到路由

如果没有与请求的url匹配的路由,则会返回代码404.

func TestNotFound(t *testing.T) {
     
	r, _ := http.NewRequest("GET", "/teacher/two/LiuYang?teach=English", nil)
	w := httptest.NewRecorder()

	handler := new(RouteMux)
	handler.Get("/student/:grade/:name", HandlerOk)
	handler.ServeHTTP(w, r)

	if w.Code != http.StatusNotFound {
     
		t.Errorf("Code set to [%v]; want [%v]", w.Code, http.StatusNotFound)
	}
}

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

2.3.3 静态路由连接

此测试能测试route路由库是否能提供静态路由连接,如果得到的路径结果错误的话,则说明静态路由连接失败,无法提供文件。

func TestStatic(t *testing.T) {
     

	r, _ := http.NewRequest("GET", "/routes_test.go", nil)
	w := httptest.NewRecorder()
	pwd, _ := os.Getwd()

	handler := new(RouteMux)
	handler.Static("/", pwd)
	handler.ServeHTTP(w, r)

	testFile, _ := ioutil.ReadFile(pwd + "/routes_test.go")
	if w.Body.String() != string(testFile) {
     
		t.Errorf("handler.Static failed to serve file")
	}
}

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

2.3.4 过滤器

此单元测试目的在于测试函数Filter()是否能够正常运行,若能够正常运行的话,会强制过滤掉所有路由。

var Filter = func(w http.ResponseWriter, r *http.Request) {
     
	if r.URL.User == nil || r.URL.User.Username() != "admin" {
     
		http.Error(w, "", http.StatusUnauthorized)
	}
}

func TestFilter(t *testing.T) {
     

	r, _ := http.NewRequest("GET", "/", nil)
	w := httptest.NewRecorder()

	handler := new(RouteMux)
	handler.Get("/", HandlerOk)
	handler.Filter(FilterUser)
	handler.ServeHTTP(w, r)

	if w.Code != http.StatusUnauthorized {
     
		t.Errorf("Did not apply Filter. Code set to [%v]; want [%v]", w.Code, http.StatusUnauthorized)
	}

	r, _ = http.NewRequest("GET", "/", nil)
	r.URL.User = url.User("admin")
	w = httptest.NewRecorder()
	handler.ServeHTTP(w, r)

	if w.Code != http.StatusOK {
     
		t.Errorf("Code set to [%v]; want [%v]", w.Code, http.StatusOK)
	}
}

测试结果:
go语言之REST API路由库_第1张图片

2.3.5 带参数的过滤器

此单元测试旨在测试函数FilterParam()是否能够过滤掉url中指定参数的所有路由。

var FilterUser = func(w http.ResponseWriter, r *http.Request) {
     
	id := r.URL.Query().Get(":user")
	if id == "admin" {
     
		http.Error(w, "", http.StatusUnauthorized)
	}
}

func TestFilterParam(t *testing.T){
     
	r, _  := http.NewRequest("GET", "/:user", nil)
	w := httptest.NewRecorder()

	handler := new(RouteMux)
	handler.Get("/:user", HandlerOk)
	handler.FilterParam(":user", FilterUser)
	handler.ServeHTTP(w, r)

	if w.Code != http.StatusOK {
     
		t.Errorf("Did not apply Filter. Code set to [%v]; want [%v]", w.Code, http.StatusOK)
	}

	r, _ = http.NewRequest("GET", "/admin", nil)
	w = httptest.NewRecorder()
	handler.ServeHTTP(w, r)

	if w.Code != http.StatusUnauthorized {
     
		t.Errorf("Did not apply Param Filter. Code set to [%v]; want [%v]", w.Code, http.StatusUnauthorized)
	}
}

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

2.4 基准测试

基准测试包括两个:BenchmarkRosteredHandlerBenchmarkServeMux,分别测试路由库中的RoutedMux以及http/serve.go文件中的ServeMux的性能并加以比较。

func BenchmarkRoutedHandler(b *testing.B) {
     
	handler := new(RouteMux)
	handler.Get("/", HandlerOk)

	for i := 0; i < b.N; i++ {
     
		r, _ := http.NewRequest("GET", "/", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
	}
}

func BenchmarkServeMux(b *testing.B) {
     
	r, _ := http.NewRequest("GET", "/", nil)
	w := httptest.NewRecorder()
	mux := http.NewServeMux()
	mux.HandleFunc("/", HandlerOk)

	for i := 0; i < b.N; i++ {
     
		mux.ServeHTTP(w, r)
	}
}

测试结果:
go语言之REST API路由库_第2张图片
可以发现,此route库对性能的影响还是很大的,相比http本身要慢了十多倍,每个操作平均需要一千多纳秒。 (不知道还算不算得上高性能路由库?)

2.5 覆盖率测试

覆盖率测试主要测试的是测试代码的代码覆盖率。

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

可以看到代码覆盖率有84.1%。

然后在命令行依次输入命令:

go test -coverprofile=cover.out
go tool cover -html=cover.out -o coverage.html

可以生成cover.out文件并根据cover.out文件生成coverage.html文件。查看html格式的代码覆盖率报告。

打开coverage.html文件,可以查看测试代码具体覆盖了哪些代码,主要就是Put、Post和Del这三个函数没有覆盖到。
go语言之REST API路由库_第3张图片

2.6 功能测试

由于在单元测试时没有涉及到Post, Put和Del这几个函数,所以我专门为这几个函数进行了功能测试,具体内容在main.go文件中。

package main

import (
	"fmt"
	"gitee.com/li-haowei/route"
	"net/http"
)

func getStudent(w http.ResponseWriter, r *http.Request) {
     
	params := r.URL.Query()
	grade := params.Get(":grade")
	name := params.Get(":name")
	fmt.Fprintf(w, "You get the Grade %s student's message, his name is %s.\n", grade, name)
}

func modifyStudent(w http.ResponseWriter, r *http.Request) {
     
	params := r.URL.Query()
	grade := params.Get(":grade")
	name := params.Get(":name")
	fmt.Fprintf(w, "You post the Grade %s student's message, his name is %s.\n", grade, name)
}

func addStudent(w http.ResponseWriter, r *http.Request) {
     
	params := r.URL.Query()
	grade := params.Get(":grade")
	name := params.Get(":name")
	fmt.Fprintf(w, "You put a new student message. The student named %s is in Grade %s\n", name, grade)
}

func deleteStudent(w http.ResponseWriter, r *http.Request) {
     
	params := r.URL.Query()
	grade := params.Get(":grade")
	name := params.Get(":name")
	fmt.Fprintf(w, "You delete the Grade %s student's message, his name is %s.\n", grade, name)
}

func main() {
     
	mux := route.New()
	mux.Get("/:grade/:name", getStudent)
	mux.Post("/:grade/:name", modifyStudent)
	mux.Put("/:grade/:name", addStudent)
	mux.Del("/:grade/:name", deleteStudent)

	http.Handle("/", mux)
	http.ListenAndServe(":8088", nil)
}

在main.go所在目录下运行go run main.go,可以开启http的监听和服务,然后另开一个终端进行测试。

2.6.1 Get

命令:

curl -X GET http://localhost:8088/three/LiMing

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

2.6.2 Post

命令:

curl -X POST http://localhost:8088/four/LiuYang

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

2.6.3 Put

命令:

curl -X PUT http://localhost:8088/five/WangWei

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

2.6.4 Delete

命令:

curl -X DELETE http://localhost:8088/six/ZhangFang

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

3、代码地址

【传送门】

你可能感兴趣的:(go)