开发 web 服务程序
1、概述
开发简单 web 服务程序 cloudgo,了解 web 服务器工作原理。
任务目标
- 熟悉 go 服务器工作原理
- 基于现有 web 库,编写一个简单 web 应用类似 cloudgo。
- 使用 curl 工具访问 web 程序
- 对 web 执行压力测试
2、任务要求
基本要求
- 编程 web 服务程序 类似 cloudgo 应用。
- 支持静态文件服务
- 支持简单 js 访问
- 提交表单,并输出一个表格(必须使用模板)
- 使用 curl 测试,将测试结果写入 README.md
- 使用 ab 测试,将测试结果写入 README.md。并解释重要参数。
go服务器工作原理:
在分布式计算中,计算机之间协作最基础、最简单的结构就是 C/S 架构 。一个 进程 扮演 Server 提供服务,一个或多个 进程 扮演 Client 发起服务请求。对于Go语言来说,只需要短短几行代码,就可以实现一个简单的http server,加上协程的加持,Go实现的http server拥有非常优秀的性能。如下图所示:
通过net/http标准库,我们可以启动一个http服务器,然后让这个服务器接收请求并返回响应。net/http标准库还提供了一个连接多路复用器(multiplexer)的接口以及一个默认的多路复用器。
Go语言创建一个服务器的步骤非常简单,只要调用ListenAndServe并传入网络地址以及负责处理请求的处理器(handler)作为参数就可以了。如果网络地址为空字符串,那么服务器默认使用80端口进行网络连接;如果处理器参数为nil,那么服务器将使用默认的多路复用器DefaultServeMux。代码实现:
package main
import "net/http"
func main() {
http.ListenAndServe("", nil)
}
除了可以通过ListenAndServe的参数对服务器的网络地址和处理器进行配置之外,还可以通过Server结构对服务器进行更详细的配置,其中包括为请求读取操作设置超时时间,为响应写入操作设置超时时间、为Server结构设置错误日志记录器等。
package main
import "net/http"
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil,
}
server.ListenAndServe()
}
在Go语言中,一个处理器就是一个拥有ServeHTTP方法的接口,这个ServeHTTP方法需要接收两个参数:第一个参数是一个ResponseWriter接口,而第二个参数则是一个指向Request结构的指针。换句话说,任何接口只要拥有一个ServeHTTP方法,并且该方法带有以下签名,那么它就是一个处理器:
ServeHTTP(http.ResponseWriter, *http.Request)
基于现有 web 库,编写一个简单 web 应用类似 cloudgo
使用的框架是:negroni,mux,render。
首先安装这三个框架的包:
go get github.com/codegangsta/negroni
go get github.com/gorilla/mux
go get github.com/unrolled/render
代码:service.go:
package service
import (
"net/http"
"os"
"github.com/codegangsta/negroni"
"github.com/gorilla/mux"
"github.com/unrolled/render"
"html/template"
)
// NewServer configures and returns a Server.
func NewServer() *negroni.Negroni {
// 返回一个Render实例的指针
formatter := render.New(render.Options{
Directory: "templates",
Extensions: []string{".html"},
IndentJSON: true, // 输出时的格式是方便阅读的JSON
})
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
//fmt.Println(root)
}
}
mx.HandleFunc("/", homeHandler(formatter)).Methods("GET")
mx.HandleFunc("/api/test", apiTestHandler(formatter)).Methods("GET")
mx.HandleFunc("/user", userHandler).Methods("POST")
mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
}
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: "17343040", Content: "Welcome from Go!"})
}
}
func apiTestHandler(formatter *render.Render) http.HandlerFunc {
//支持 JavaScript 访问
return func(w http.ResponseWriter, req *http.Request) {
formatter.JSON(w, http.StatusOK, struct {
ID string `json:"id"`
Content string `json:"content"`
}{ID: "17343040", Content: "Hello from Go!"})
}
}
//提交表单,并输出一个表格
func userHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := template.HTMLEscapeString(r.Form.Get("username"))
password := template.HTMLEscapeString(r.Form.Get("password"))
t := template.Must(template.New("user.html").ParseFiles("./templates/user.html"))
err := t.Execute(w, struct {
Username string
Password string
}{Username: username, Password: password})
if err != nil {
panic(err)
}
}
main.go:
package main
import (
"os"
"./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)
}
功能测试:
输入go run main.go 然后打开浏览器访问http://localhost/8080
可以看到index页面如下:(有bug:图片访问出现了问题)
在index页面输入Username和Password,点击Sign In后转到UserPage。
curl 测试:
$ curl -v http://localhost:8080
* About to connect() to localhost port 8080 (#0)
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 23 Nov 2020 22:36:54 GMT
< Content-Length: 504
< Content-Type: text/html; charset=utf-8
<
<html>
<head>
<link rel="stylesheet" href="css/main.css"/>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="js/hello.js"></script>
</head>
<body>
<img src="images/cng.png" height="400" width="400"/>
Welcome to Go Web Application!!
<div>
<p class="greeting-id">The ID is {{.ID}}</p>
<p class="greeting-content">The content is {{.Content}}</p>
</div>
<form class="box" action="./user" method="POST">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" value="Sign In">
</form>
</body>
</html>
* Connection #0 to host localhost left intact
ab测试:
ab -n 8080 -c 10 http://localhost:8080/