任务目标

  1. 熟悉 go 服务器工作原理
  2. 基于现有 web 库,编写一个简单 web 应用类似 cloudgo
  3. 使用 curl 工具访问 web 程序
  4. 对 web 执行压力测试

任务要求

基本要求

  1. 编程 web 服务程序 类似 cloudgo 应用
  2. 支持静态文件服务
  3. 支持简单 js 访问
  4. 提交表单,并输出一个表格(必须使用模板)
  5. 使用 curl 测试,将测试结果写入 README.md
  6. 使用 ab 测试,将测试结果写入 README.md。并解释重要参数

扩展要求
选择简单的库,如 mux 等,通过源码分析、解释它是如何实现扩展的原理,包括一些 golang 程序设计技巧。

cloudgo实现

项目结构如下:

一. 第三方库引用

  1. Negroni
    在 Go 语言里,Negroni 是一个很地道的 Web 中间件,它是一个具备微型、非嵌入式、鼓励使用原生 net/http 库特征的中间件。如果你喜欢用 Martini ,但又觉得它太魔幻,那么 Negroni 就是你很好的选择了。
    安装:go get github.com/codegangsta/negroni

  2. mux
    Mux(数据选择器 / multiplexer):在多路数据传送过程中,能够根据需要将其中任意一路选出来的电路,叫做数据选择器,也称多路选择器或多路开关。
    安装:go get github.com/gorilla/mux

  3. render
    Render is a package that provides functionality for easily rendering JSON, XML, text, binary data, and HTML templates. This package is based on the Martini render work.
    安装:go get github.com/unrolled/render

二. 静态文件访问

首先编写启动服务器的main函数。将默认端口设置为8080,如果有指定监听端口号的命令行参数,则使用pflag包函数进行解析并设置,然后开启服务器。

package mainimport ("os""github.com/lp/cloudgo/service"flag "github.com/spf13/pflag"
)const (PORT string = "8080"
)func main() {port := os.Getenv("PORT")if len(port) == 0 {port = PORT}pPort := flag.StringP("port", "p", PORT, "PORT for httpd listening")flag.Parse()if len(*pPort) != 0 {port = *pPort}server := service.NewServer()server.Run(":" + port)
}

然后在server.go中实现具体功能。Newserver函数新建一个render并初始化,使其能够在目录“templates”下寻找后缀为html的模板文件。initRoutes函数调用函数 func FileServer(root FileSystem) Handler 来完成文件服务,一条语句就实现了 mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))静态文件服务。它的含义是将 path 以 “/” 前缀的 URL 都定位到 webRoot + “/assets/” 为虚拟根目录的文件系统。

// NewServer configures and returns a Server.
func NewServer() *negroni.Negroni {formatter := render.New(render.Options{Directory:  "templates",Extensions: []string{".html"},IndentJSON: true,})n := negroni.Classic()mx := mux.NewRouter()initRoutes(mx, formatter)n.UseHandler(mx)return n
}func initRoutes(mx *mux.Router, formatter *render.Render) {webRoot := os.Getenv("WEBROOT")if len(webRoot) == 0 {if root, err := os.Getwd(); err != nil {panic("Could not retrive working directory")} else {webRoot = root}}mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
}

go run main.go -p 10000在10000端口运行服务器效果如下:

访问image下的5t5.jpg如下:

三. 主页访问

新增homeHandler函数,配合NewServer函数完成网页输出。要点如下:

  1. formatter 构建,指定了模板的目录为templates,模板文件的扩展名为.html
  2. homeHandler 使用了模板
  3. 将ID和Content字段信息作为结构体参数传递给index.html
func homeHandler(formatter *render.Render) http.HandlerFunc {return func(w http.ResponseWriter, req *http.Request) {formatter.HTML(w, http.StatusOK, "index", struct {ID      string `json:"id"`Content string `json:"content"`}{ID: "18342066", Content: "This is LP in SYSU!"})}
}

index.html设计如下:

<html>
<head><link rel="stylesheet" href="css/index.css"/>
</head>
<body><div class="container"><p>A simple web server program</p><p>The ID is {{.ID}}</p><p>The content is {{.Content}}</p>
</div>
</body>
</html>

index.css设计如下:

body {background-image: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606140725338&di=3b664098aa009b242b393b99c51e03b1&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F7%2F58e745a879945.jpg);
}.container {width: 40%; height: 20%; position: absolute; left:30%; top:20%; text-align: center;
}P {font-size: 20px;
}

在initRoutes函数中新增语句:mx.HandleFunc("/", homeHandler(formatter)).Methods("GET")
访问效果如下:

四. 支持简单js访问

修改index.html如下:

<html>
<head><link rel="stylesheet" href="css/index.css"/><script type="text/javascript" src="js/jquery-3.4.1.js"></script><script type="text/javascript" src="js/hello.js"></script>
</head>
<body><div class="container"><p>A simple web server program</p><p class="greeting-id">The ID is </p><p class="greeting-content">The content is </p>
</div>
</body>
</html>

hello.js通过ajax向服务器请求/api/test:

$(document).ready(function() {$.ajax({url: "/api/test"}).then(function(data) {console.log(data)$('.greeting-id').append(data.id);$('.greeting-content').append(data.content);});
});

在server.go中新增函数apiTestHandler,这段代码非常简单,输出了一个 匿名结构 ,并 JSON (JavaScript Object Notation) 序列化输出:

func apiTestHandler(formatter *render.Render) http.HandlerFunc {return func(w http.ResponseWriter, req *http.Request) {formatter.JSON(w, http.StatusOK, struct {ID      string `json:"id"`Content string `json:"content"`}{ID: "18342066", Content: "This is LP in SYSU!"})}
}

在initRoutes函数中新增语句:mx.HandleFunc("/api/test", apiTestHandler(formatter))

五. 提交表单,并输出一个表格(必须使用模板)

新建两个html文件,分别是登陆前和登陆后,点击登陆后由前者跳转至后者:
login.html:

<html>
<head>
<title></title>
</head>
<body>
<form action="/after_login" method="post">用户名:<input type="text" name="username">密码:<input type="password" name="password"><input type="submit" value="登录">
</form>
</body>
</html>

after_login.html:

<html>
<head>
<title></title>
</head>
<body>
<table><tr> <th>Username</th><th>Password</th></tr><tr><td>{{.Username}}</td><td>{{.Password}}</td></tr>
</body>
</html>

在serer.go中新增login函数,根据请求对应的方法来渲染不同的页面:

func login(formatter *render.Render) http.HandlerFunc {return func(w http.ResponseWriter, req *http.Request) {if req.Method == "GET" {formatter.HTML(w, http.StatusOK, "login", struct{}{})} else {req.ParseForm()formatter.HTML(w, http.StatusOK, "after_login", struct {Username stringPassword string}{Username: req.Form["username"][0],Password: req.Form["password"][0],})}}
}

在initRoutes函数中新增语句:

mx.HandleFunc("/login", login(formatter))
mx.HandleFunc("/after_login", login(formatter))

访问 http://localhost:10000/login 效果如下:

输入用户名和密码后,点击登录:

六. curl测试

访问主页:

访问静态文件:

访问登录页面:

七. ab测试

对主页进行10000次请求,100个并发请求:

freedom@freedom-virtual-machine:~$ ab -n 10000 -c 100 http://localhost:10000/
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requestsServer Software:
Server Hostname:        localhost
Server Port:            10000Document Path:          /
Document Length:        387 bytesConcurrency Level:      100
Time taken for tests:   1.645 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      5040000 bytes
HTML transferred:       3870000 bytes
Requests per second:    6079.50 [#/sec] (mean)
Time per request:       16.449 [ms] (mean)
Time per request:       0.164 [ms] (mean, across all concurrent requests)
Transfer rate:          2992.26 [Kbytes/sec] receivedConnection Times (ms)min  mean[+/-sd] median   max
Connect:        0    2   9.3      1      94
Processing:     0   14  13.4     10     114
Waiting:        0   12  12.7      9     113
Total:          0   16  17.2     11     137Percentage of the requests served within a certain time (ms)50%     1166%     1575%     1880%     2090%     3095%     5098%     7999%    109100%    137 (longest request)

对image/5t5.jpg进行10000次请求,10个并发请求:

freedom@freedom-virtual-machine:~$ ab -n 10000 -c 100 http://localhost:10000/image/5t5.jpg
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requestsServer Software:
Server Hostname:        localhost
Server Port:            10000Document Path:          /image/5t5.jpg
Document Length:        307565 bytesConcurrency Level:      100
Time taken for tests:   3.454 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      3077390000 bytes
HTML transferred:       3075650000 bytes
Requests per second:    2895.08 [#/sec] (mean)
Time per request:       34.541 [ms] (mean)
Time per request:       0.345 [ms] (mean, across all concurrent requests)
Transfer rate:          870047.87 [Kbytes/sec] receivedConnection Times (ms)min  mean[+/-sd] median   max
Connect:        0    0   0.6      0      28
Processing:     0   34  33.4     26     294
Waiting:        0   25  31.2     16     264
Total:          0   34  33.4     26     294Percentage of the requests served within a certain time (ms)50%     2666%     3675%     4280%     4890%     6895%     9898%    13699%    194100%    294 (longest request)

八. 源代码阅读—Negroni中间件原理与实现

首先看一下HTTP Server 的处理逻辑:

在这个逻辑处理的环节中,Negroni充当一个HTTP Handler的角色,并对于所有的HTTP Request的处理都会通过Negroni被转交到其内部的子中间件

negroni包是go的一个第三方库,是为了方便使用 net/http 而设计的一个库,由于该包设计优雅,易于扩展,被广泛使用。
中文文档
negroni包源码在github仓库中,可以从github上克隆下来或者通过go get命令获得。
negroni包的源码结构如下(使用tree命令获得):

.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc.go
├── go.mod
├── logger.go
├── logger_test.go
├── negroni.go
├── negroni_bench_test.go
├── negroni_test.go
├── recovery.go
├── recovery_test.go
├── response_writer.go
├── response_writer_pusher.go
├── response_writer_pusher_test
├── response_writer_test.go
├── static.go
├── static_test.go
└── translations├── README_de_de.md├── README_fr_FR.md├── README_ja_JP.md├── README_ko_KR.md├── README_pt_br.md├── README_zh_CN.md└── README_zh_tw.md1 directory, 25 files

源码中真正起作用的只有logger.go、negroni.go、recovery.go、response_writer.go、response_writer_pusher.go、static.go共六个文件,其余都是文档和测试代码。我们在此主要分析negroni.go文件。

首先是对Handler接口的定义,Handler接口要求实现ServeHTTP函数,参数类型是回复的写出流http.ResponseWriter、请求http.Request、回调函数http.HandlerFunc,
然后是对HandlerFunc的函数类型的定义,HandlerFunc的函数参数和Handler.ServeHTTP一致,都是回复、请求和回调函数三个参数。HandlerFunc是一个适配器,用于将外界的函数转化为能够被Negroni处理的类型。
然后定义HandlerFunc实现了ServeHTTP的虚函数,所以HandlerFunc可以转为Handler类型。

type Handler interface {ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {h(rw, r, next)
}

接下来是中间件middleware的类型定义,可见中间件类型包括了一个Handler和一个回调函数,middleware包含回调函数的目的是减少内存的分配。middleware实现了net/http里定义的ServeHTTP接口,通过调用middleware的handler的ServeHTTP来实现,这里形成了Negroni与原生的net/http包的无缝衔接。

type middleware struct {handler Handler// nextfn stores the next.ServeHTTP to reduce memory allocatenextfn func(rw http.ResponseWriter, r *http.Request)
}func newMiddleware(handler Handler, next *middleware) middleware {return middleware{handler: handler,nextfn:  next.ServeHTTP,}
}func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {m.handler.ServeHTTP(rw, r, m.nextfn)
}

接下来是定义装饰器,Wrap将http.Handler转换为一个negroni.Handler,WrapFunc将http.HandlerFunc转换为一个negroni.Handler,这里也是为了实现Negroni与原生的net/http包的无缝衔接。

func Wrap(handler http.Handler) Handler {return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {handler.ServeHTTP(rw, r)next(rw, r)})
}func WrapFunc(handlerFunc http.HandlerFunc) Handler {return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {handlerFunc(rw, r)next(rw, r)})
}

最后是Negroni结构的定义以及构造函数,Negroni定义为一个middleware和一个handlers数组组成的结构体,其构造方法有三个:

  1. New,通过给出一系列的handler来构造Negroni,不定参数handlers组成Negroni的handlers数组,middleware通过辅助函数build来通过这一系列handler构造
  2. With,With通过一个已有的Negroni,并给出不定参数handlers,将给出的handlers附加到已有的Negroni的handlers后即可得到新的Negroni。
  3. Classic, Classic通过在栈中的默认的中间件构造,使用了Recovery、Logger、Static来给出保存的信息。
type Negroni struct {middleware middlewarehandlers   []Handler
}func New(handlers ...Handler) *Negroni {return &Negroni{handlers:   handlers,middleware: build(handlers),}
}func (n *Negroni) With(handlers ...Handler) *Negroni {currentHandlers := make([]Handler, len(n.handlers))copy(currentHandlers, n.handlers)return New(append(currentHandlers, handlers...)...,)
}func Classic() *Negroni {return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public")))
}

开发简单 web 服务程序 cloudgo相关推荐

  1. 基于Golang的简单web服务程序开发——CloudGo

    基于Golang的简单web服务程序开发--CloudGo[阅读时间:约10分钟] 一.概述 二.系统环境&项目介绍 1.系统环境 2.项目的任务要求 (1)基本要求 (2)扩展要求 三.具体 ...

  2. SpringMVC+Spring4+Mybatis3集成,开发简单Web项目+源码下载

    转载自   SpringMVC+Spring4+Mybatis3集成,开发简单Web项目+源码下载 基本准备工作 1.安装JDK1.6以上版本,安装与配置 2.下载mybatis-3.2.0版:htt ...

  3. go语言服务器代码,Go语言开发简单web服务器

    欢迎,来自IP地址为:182.103.254.107 的朋友 Go语言由于其方便的并发通信机制以及强大的网络支持,常常被用于服务器软件的开发.本文将示例使用Go语言来开发简单的Web服务器. HTTP ...

  4. J2EE(一)——开发简单WEB服务器

    一.web开发介绍 网页连接过程(B/S):客户端使用浏览器,发送http请求到web服务器上,服务器进行回应.Browser/Server http请求内容: GET/HTTP/1.1 说明是get ...

  5. 【持久化框架】SpringMVC+Spring4+Mybatis3 集成,开发简单Web项目+源码下载

    通过spring与Mybatis集成,开发一个简单用户增删改查的Web项目. 基本准备工作 1.安装JDK1.6以上版本,安装与配置 2.下载mybatis-3.2.0版:https://repo1. ...

  6. 【持久化框架】SpringMVC+Spring4+Mybatis3集成,开发简单Web项目+源码下载【转】

    为什么80%的码农都做不了架构师?>>>    第一步:创建数据库表 在Navicat下执行如下sql命令创建数据库mybatis和表t_user CREATE DATABASE I ...

  7. 【持久化框架】SpringMVC+Spring4+Mybatis3集成,开发简单Web项目+源码下载

    2019独角兽企业重金招聘Python工程师标准>>> 第一步:创建数据库表 在Navicat下执行如下sql命令创建数据库mybatis和表t_user [sql] view pl ...

  8. vue和socket.io开发简单web聊天室

    2019独角兽企业重金招聘Python工程师标准>>> 效果预览 https://www.wangchunjian.top/chat.html 需要用到的库 https://sock ...

  9. ubuntu下使用vscode开发golang程序,从控制台到简单web程序

    最近项目要使用go语言开发一个web程序,由于是第一次使用go开发,就将开发过程中的点滴做个记录吧. 目录 1.安装go 1.1 安装 1.2 配置go语言环境变量 2.vscode配置go开发 2. ...

最新文章

  1. Linux crontab 命令格式
  2. 剑指Offer 替换空格
  3. hdu 4888 最大流慢板
  4. com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database 'test'
  5. html5基础知识点表单
  6. 随机数发生器怎么用_用随机数发生器射击自己的脚
  7. LeetCode 313. 超级丑数(动态规划)
  8. Kinect开发学习笔记之(八)彩色、深度、骨骼和用户抠图结合
  9. 【重识 HTML + CSS】定位
  10. python3-知识扩展扫盲易忘-generator的用法
  11. matlab2014调用vs2015进行混合编译生成mex文件
  12. 人脸识别数据集---CAS-PEAL-R1
  13. c语言指针实验报告总结,指针(C语言实验报告).doc
  14. 拉取 gcr.io 镜像,如 Kubernetes,istio 等
  15. ISBN码识别-DA数据结构二级项目
  16. jmeter http并发测试设置教程(设置线程组,设置http,csv参数化,查看结果集)
  17. 实现聊天软件消息刷屏
  18. K-近邻(K-Nearest Neighbor, KNN)分类原理及相关公式
  19. Zeroc-Ice(ice-Touch)详解与配置应用
  20. 华为云GaussDB(for Redis)揭秘:谁说Redis不能存大key

热门文章

  1. CAE软件安装包(百度网盘)
  2. wordpress:主题-一个完整的WP主题通常包含以下模板文件
  3. 【专栏】国内外物联网平台初探(篇三:QQ物联·智能硬件开放平台)
  4. springboot毕设项目游泳馆管理系统2069l(java+VUE+Mybatis+Maven+Mysql)
  5. 缺陷管理工具大PK:UniPro、Bugzilla和Teambition哪家强
  6. 蚁群算法解决tsp问题python_蚁群算法在解决TSP问题中的应用
  7. 创建Mac的shell命令文件(xxx.sh)
  8. 北京上地海淀IDC数据中心机房托管-永丰数据中心
  9. 使用EL表达式输出数据
  10. c# rar解压大小_C#解压RAR压缩文件