内容与要求
1、概述
开发简单 web 服务程序 cloudgo,了解 web 服务器工作原理。
-
任务目标
- 熟悉 go 服务器工作原理
- 基于现有 web 库,编写一个简单 web 应用类似 cloudgo。
- 使用 curl 工具访问 web 程序
- 对 web 执行压力测试
2、任务要求
-
基本要求
- 编程 web 服务程序 类似 cloudgo 应用。
- 支持静态文件服务
- 支持简单 js 访问
- 提交表单,并输出一个表格(必须使用模板)
- 使用 curl 测试,将测试结果写入 README.md
- 使用 ab 测试,将测试结果写入 README.md。并解释重要参数。
-
扩展要求
选择以下一个或多个任务,以博客的形式提交。
- 1.通过源码分析、解释一些关键功能实现
- 2.选择简单的库,如 mux 等,通过源码分析、解释它是如何实现扩展的原理,包括一些 golang 程序设计技巧。
tip:源代码阅读
阅读源代码是学习 golang 绕不开的任务,否则你无法达到你期望的水平与能力。 如何阅读源代码?这是一个非常复杂的话题,知识、经验和技巧都有很大作用。
-
X.1 实现原理阅读
以 net/http 库 web 工作原理阅读为例:
- 有原理图,分四个步骤:创建 ServerSocket, 绑定并 listen, accept 连接, 创建 go 线程服务一个连接。
- 我们从入口函数 ListenAndServe 开始开始用 Ctrl 键开始追代码:
- 关注函数、方法参数中的 接口 和 函数 参数,是接口一定要了解接口的定义。
- 随时查阅 API 文档,了解相关类型的属性与方法
- 忽视任何错误处理、分支处理。尽管其中有许多有趣的东西,也要放弃
- 其中特别注意闭包、匿名函数、匿名类型这些编程技巧
- 特别注意接口断言语法 var.(type)
- 线程要注意上下文对象(context)的构建
-
X.2 DefaultServeMux 与 gorilla/mux 对比阅读
以 net/http 库 DefaultServeMux 实现为例
- 注意到类型 ServeMux 。 当然的知道它的任务是将 “用户请求中 path 映射到 Handler”
- map –> (path/name?, handler)
- muxEntry:Handler 是接口, pattern?
- 关键代码
- pathMatch 函数,你已经知道了,这就是 path == pattern 的简单匹配
- 再看看 ServeMux 方法的代码,基本就验证了你的想法
注意循序渐进,避免开始就搞复杂的东西。
分析
gorilla/mux
- 官网阅读它的功能与使用
- 从源代码角度对比 DefaultServeMux 与 gorilla/mux
- 有哪些收获?
- 注意到类型 ServeMux 。 当然的知道它的任务是将 “用户请求中 path 映射到 Handler”
-
X.3 Negroni中间件原理与实现
实现过程
静态文件服务
首先创建如下所示的文件结构
assets(静态文件虚拟根目录)
|-- js
|-- images
+-- css
Go 的 net/http 包中提供了静态文件的服务,ServeFile 和 FileServer 等函数,所以参考指导博客内容,直接使用语句:
mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
它的含义是将 path 以 “/” 前缀的 URL 都定位到 webRoot + “/assets/” 为虚拟根目录的文件系统。这样就可以访问到具体的静态文件了。如下:
-
逐步添加main.css,hello.js,img等文件,可以发现在添加index.html之前,访问
localhost:8080/
是不会显示网页的,但是可以访问到具体的静态文件:
原因是在访问网页时,首先匹配到语句mx.HandleFunc("/", homeHandler(formatter)).Methods("GET")
,然后调用homeHandler函数: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: "18342067", Content: "Hello from Go!"}) } }
显然,此时没有index.html,所以不能加载出页面来。
但是访问静态文件是直接通过语句mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
去访问asset目录下的文件,所以是可以访问的. -
添加index.html文件
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/main.css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="js/hello.js"></script>
<meta charset="utf-8">
<title>Hello world</title>
</head>
<body>
<div id="image">
<img src="images/cng.png" height="100%" width="100%" />
</div>
<div>
<p>Sample Go Web Application!! </p>
<p class="name">The ID is: {{.ID}}</p>
<p class="content">The content is: {{.Content}}</p>
</div>
<form action="/signin" method="post">
<p>Username: <input type="text" name="username"></p>
<p>Password: <input type="password" name="password"></p>
<input type="submit" value="signin">
</form>
</body>
</html>
然后再次访问localhost:8080/
:
此时已经加载出了页面。
处理静态路径前缀
在 web 应用中,部分应用会将所有静态文件访问路径用独立前缀,例如: http://localhost:8080/static/js/hello.js
,所以这时我们需要把这个独立前缀转换成所使用的静态路径,否则就会出现404(因为这个路径并不存在)
这时我们使用如下语句:
// for static
mx.PathPrefix("/static").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(webRoot+"/assets/"))))
它会把路径中的static去掉,然后再路径assets下继续访问,这就转换成了我们所使用的静态文件路径了。
简单js访问
使用指导博客中的案例,返回一个匿名结构,并用JSON序列化输出
mx.HandleFunc("/api/test", apiTestHandler(formatter)).Methods("GET")
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: "test user", Content: "Hello from Go!"})
}
}
$(document).ready(function() {
$.ajax({
url: "/api/test"
}).then(function(data) {
$('.greeting-id').append(data.id);
$('.greeting-content').append(data.content);
});
});
js访问结果如下所示:
提交表单,并输出一个表格
既然要做一个页面,那不可避免的就想到首先要做一个登录页面,这就是常见表单的一种。所以,根据项目要求,就实现一个用户登录功能,并且再登录之后输出这个表单。
首先,创建表单:
<form action="/signin" method="post">
<p>Username: <input type="text" name="username"></p>
<p>Password: <input type="password" name="password"></p>
<input type="submit" value="signin">
</form>
实现功能函数:
// index page To signin page
mx.HandleFunc("/signin", checkform).Methods("POST")
func checkform(w http.ResponseWriter, r *http.Request) {
//fmt.Println("1111111111111111111")
r.ParseForm()
username := template.HTMLEscapeString(r.Form.Get("username"))
password := template.HTMLEscapeString(r.Form.Get("password"))
t := template.Must(template.New("signin.html").ParseFiles("./templates/signin.html"))
//fmt.Println("1111111111111111111")
err := t.Execute(w, struct {
Username string
Password string
}{Username: username, Password: password})
if err != nil {
panic(err)
}
}
输出页面:
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/main.css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="js/hello.js"></script>
<meta charset="utf-8">
<title>user infomation</title>
</head>
<body>
<table>
<tr>
<td>Username</td>
<td>Password</td>
</tr>
<tr>
<td>{{.Username}}</td>
<td>{{.Password}}</td>
</tr>
</table>
</body>
</html>
这样,就完成了一个用户登录表单提交,并且再登录之后输出表单的功能。
演示如下:
测试
使用 curl 测试
使用ab 测试
一个非常详细的博客帮助我们理解这一内容。
常用参数解释:
-n:总共的请求执行数,缺省是1;
-c:并发数,缺省是1;
-t:测试所进行的总时间,秒为单位,缺省50000s
-s: 每个response等待最大时长,缺省为30s
-p:POST时的数据文件
-w: 以HTML表的格式输出结果
输出参数解释:
- Server Software: 平台,被测试的Web服务器软件名称
- Server Hostname: 连接的服务器域名
- Server Port: 端口
- Document Path: 测试页面的路径
- Document Length: HTTP响应数据的正文长度
- Concurrency Level: 并发数
- Time taken for tests: 测试时长
- Complete requests: 完成的请求数
- Failed requests: 失败请求数
- Total transferred: 所有请求的响应数据长度总和,包括每个HTTP响应数据的头信息和正文数据的长度
- HTML transferred: 所有请求的响应数据中正文数据的总和
- Requests per second: 吞吐率,Complete requests/Time taken for tests
- Time per request: 用户平均请求等待时间,Time token for tests/(Complete requests/Concurrency Level)
- Transfer rate: 网络传输速度,Total trnasferred/ Time taken for tests
- Connection Times (ms),一个表格,将一个请求的响应时间分成网络链接(Connect),系统处理(Processing)和等待(Waiting)三个部分。
测试结果展示:
源代码阅读
链接:https://blog.csdn.net/weixin_46092070/article/details/110008289