Go 中的 Web 应用
本次作业的任务:
-
基于现有 Web 库,编写一个简单 Web 应用。
-
使用 curl 工具访问 Web 程序。
-
对 Web 执行压力测试。
Web 应用的编写
完整代码可见 GitHub。
项目中使用了两个 Web 库:negroni 和 mux。其中 negroni
库用于处理中间件,mux
库用于创建路由。
服务器模块提供 NewServer
函数,该函数首先通过 mux
库创建了一个路由,然后通过 HandleFunc 函数将 URL 路径映射到相应的处理函数上。随后,通过 negroni.Classic()
函数创建一个 negroni.Negroni
实例,提供一些默认的中间件,并在最后添加之前创建的路由(因为 Negroni
没有提供路由功能)。
func NewServer() *negroni.Negroni {
// create router
router := mux.NewRouter()
// register routes
router.HandleFunc("/", sayHello).Methods("GET")
router.HandleFunc("/login", login).Methods("GET", "POST")
// add some default middlewares
nc := negroni.Classic()
// use router
nc.UseHandler(router)
return nc
}
当客户端访问 “/” 路径时,向客户端发送 Hello 回复。
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!\n")
}
当客户端访问 “/login” 路径时,需要判断 HTTP 的方法:如果 HTTP 方法为 GET,则向客户端返回登陆页面;而如果 HTTP 方法为 POST,则在服务器端输出用户名及密码。
// template for login page
const loginTemplate = `
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
Username: <input type="text" name="username">
Password: <input type="password" name="password">
<input type="submit" value="Login">
</form>
</body>
</html>
`
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("Method:", r.Method)
if r.Method == "GET" {
t, _ := template.New("webpage").Parse(loginTemplate)
t.Execute(w, nil)
} else {
r.ParseForm()
fmt.Println("Username:", r.FormValue("username"))
fmt.Println("Password:", r.FormValue("password"))
}
}
在主函数中,用变量 port 表示监听端口。首先,需要检查是否存在环境变量 PORT,如果存在则将 port 变量设置为该环境变量的值,否则设置为默认值。然后,通过 pflag
库检查用户是否通过命令行参数设置了端口,如果是,则将 port 变量设置为用户提供的端口。在设置完端口后,调用 Negroni
库的 Run 函数(相当于调用 net/http
中的 ListenAndServe 函数)监听相应的端口。
const defaultPort string = "8888"
func main() {
// try to retrieve the value of the "PORT" environment variable
port := os.Getenv("PORT")
if len(port) == 0 {
port = defaultPort
}
// if the use set the port, then use what user set
pPort := flag.StringP("port", "p", "", "PORT for httpd listening")
flag.Parse()
if len(*pPort) != 0 {
port = *pPort
}
// run server
server := service.NewServer()
server.Run(":" + port)
}
访问 Web 程序
通过 go run main.go -p 1234
运行 Web 程序,然后使用 curl 工具访问 Web 程序。
首先访问 “/” 路径:
curl -v http://localhost:1234
结果如下:
接着通过 GET 方法访问 “/login” 路径:
curl -v http://localhost:1234/login
结果如下:
最后通过 POST 方法访问 “/login” 路径:
curl -v -X POST "http://localhost:1234/login?username=rhythm&password=123456"
结果如下:
服务器端的输出如下:
压力测试
最后,使用 macOS 自带的 ab(Apache Bench)对 Web 程序进行压力测试:
touch empty.txt \
&& \
ab \
-n 1000 \
-c 100 \
-T 'application/x-www-form-urlencoded; charset=UTF-8' \
-p empty.txt \
"http://localhost:1234/login?username=rhythm&password=123456" \
&& \
rm empty.txt
其中,-n
参数表示请求数量,-c
参数表示并发请求的数量,-T
参数用于设置 POST 请求时的 Content-Type 字段,-p
设置 POST 的数据文件(此处使用一个临时空文件)。
测试结果如下: