golang web 服务器 request 与 response 处理
介绍常见 web 服务的实现与输入、输出数据处理。包括:静态文件服务、js 请求支持、模板输出、表单处理、Filter 中间件设计。
一、静态文件服务支持
访问 html 静态网页是 web 基础服务。
github.com/pmlpml/golang-learning/web/cloudgo-static
1.1 使用文件服务
一般来说,生产环境静态文件的访问交给 WEB 服务器 Apache / Lighttpd / Nginx 处理。在开发、测试阶段也可以让 net/http 库处理。
server.go
package service
import (
"net/http"
"os"
"github.com/codegangsta/negroni"
"github.com/gorilla/mux"
"github.com/unrolled/render"
)
// NewServer configures and returns a Server.
func NewServer() *negroni.Negroni {
formatter := render.New(render.Options{
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
//fmt.Println(root)
}
}
//mx.HandleFunc("/api/test", apiTestHandler(formatter)).Methods("GET")
mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
}
ServeFileFileServer
首先我们需要在服务器上创建目录,以存放静态内容。例如:
assets(静态文件虚拟根目录)
|-- js
|-- images
+-- css
mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))webRoot + "/assets/"
http.Dirhttp.DirFileSystemhttp.FileServer()Handlerrootmx.PathPrefix
assetsgo run main.go
http;//localhost:8080/cssjs
index.html
1.2 支持 JavaScript 访问
随着 web 页面技术的进步,页面中大量使用 javascript。 添加一个服务:
apitest.go
package service
import (
"net/http"
"github.com/unrolled/render"
)
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: "8675309", Content: "Hello from Go!"})
}
}
这段代码非常简单,输出了一个 匿名结构 ,并 JSON (JavaScript Object Notation) 序列化输出。 打开前面程序的注释,运行网站并用 curl 测试输出!
$ curl http://localhost:8080/api/test
{
"id": "8675309",
"content": "Hello from Go!"
}
为了便于理解,课程给的案例非常简答。index.html 是
<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="48" width="48"/>
Sample Go Web Application!!
<div>
<p class="greeting-id">The ID is </p>
<p class="greeting-content">The content is </p>
</div>
</body>
</html>
使用的 hello.js 是:
$(document).ready(function() {
$.ajax({
url: "/api/test"
}).then(function(data) {
$('.greeting-id').append(data.id);
$('.greeting-content').append(data.content);
});
});
通过 web 应用控制台 Negroni 输出追踪,获知网页使用 javascript 获取了信息。
现在,你应该可以顺利的与 VUE,Bootstrap 这些做的应用前端与 golang 集成在一起了!
问题: 交互路由两个语句位置,会发生什么?
1.3 处理静态路径前缀
http://localhost:8080/static/js/hello.js
mx.PathPrefix("/static").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
404
正确的代码是:
mx.PathPrefix("/static").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(webRoot+"/assets/"))))
StripPrefixHandlerFunc 类型
小练习:
501 Not ImplementedNotImplementedNotImplementedHandler/api/unkown501 Not Implemented
二、使用模板输出
由于 Angluar,React 等 web 前端框架的流行, web 服务器对模板的需求已经不是非常强烈。然而,它依然是格式化数据输出的一种重要手段。 熟悉 jinja2 模板的人对 go 的模板设计应非常亲切。
github.com/pmlpml/golang-learning/web/cloudgo-template
2.1 输出 html 页面
github.com/unrolled/render
srver.go
package service
import (
"net/http"
"os"
"github.com/codegangsta/negroni"
"github.com/gorilla/mux"
"github.com/unrolled/render"
)
// 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
//fmt.Println(root)
}
}
mx.HandleFunc("/", homeHandler(formatter)).Methods("GET")
mx.PathPrefix("/").Handler(http.FileServer(http.Dir(webRoot + "/assets/")))
}
要点:
(1)formatter 构建,指定了模板的目录,模板文件的扩展名
(2)homeHandler 使用了模板
assetstemplates
<html>
<head>
<link rel="stylesheet" href="css/main.css"/>
</head>
<body>
<img src="images/cng.png" height="48" width="48"/>
Sample Go Web Application!!
<div>
<p class="greeting-id">The ID is {{.ID}}</p>
<p class="greeting-content">The content is {{.Content}}</p>
</div>
</body>
</html>
{{.}}{{.ID}}
home.go 处理了数据填充。
package service
import (
"net/http"
"github.com/unrolled/render"
)
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: "8675309", Content: "Hello from Go!"})
}
}
我们使用 formatter 的 HTML 直接将数据注入模板,并输出到浏览器。 更多内容见 render 在 git 上的 README。
$ go run main.go
在浏览器访问:http://localhost:8080
注意观察控制台 negroni 输出。
- 访问 “/” 第一次与后续访问时间的差异
- 访问 css 与 js 的返回状态, 为什么是 304 ?
2.2 使用 text/template 库
golang 提供了强大的 template 库,上述 Render 仅是它的简单包装。 官网的例子也非常简单:
type Inventory struct {
Material string
Count uint
}
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }
template.New("name").Parse("{{.Content}} string").Execute(writer,data)
New("name")
阅读 text/template API 文档,解释输出
t := template.Must(template.New("letter").Parse("A{{.}}\n"))
t1 := template.Must(t.New("letter1").Parse("B{{.}}\n"))
t.Execute(os.Stdout, "1")
t1.Execute(os.Stdout, "2")
fmt.Println(len(t1.Templates()))
for _, tt := range t1.Templates() {
fmt.Println(tt.Name())
}
这段代码的输出是?…
letter1lettert.Newtemplate.New
ParseFiles(...)
ParseFilesParseGlob
简明了解模板的语法:golang模板语法简明教程
代码与实例:Go语言核心之美 3.6-template模版
看完后,请完成:
- 输出一个 html 报表。 表格头是 “手机销量,Q1,Q2,Q3,Q3同比,Q3比上季”,数据是“华为,OPPO,小米 ,OV,Apple”
- 使用自定义格式化函数,格式化百分数
三、处理 Request
3.1 request 定义
要点:
- 与 http 协议的关系
3.2 如何处理表单
补充:
return &MyStruct{map["xx"]}
四、 negroni 与 Filter 模式
java web 有三大神器 Filter、Servlet、Listener。
- Servlet 称为服务小程序,处理 request,产生 response。 golang 的 net/http 的 Handler、HandlerFunc 以及 第三方 http mux 或 http router 包都完成这类功能
- Filter 称为过滤器或拦截器,处理 request 预处理 或 response 后处理,它常用的用途是 处理输入、输出的语言编码、日志、用户权限管理、session管理等等。它采用 Filter Chain 将一组 Filter 组件联系起来。 golang negroni 完成该工作。
- Listener 称为侦听器,用户感知 web 上下文(Context)变化,并通知用户。 golang ?
- application
- session
- request
对比 servlet: Java Servlet pk Golang Handler
对比 Filter: Java Filter pk negroni Handler
java 文档给出了常用的 Filter!
4.1 Filter 原理
go negroni “中间件”模板代码:
func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// do some stuff before
next(rw, r)
// do some stuff after
}
4.2 negroni组件
negroni classic 的实现:
// Classic returns a new Negroni instance with the default middleware already
// in the stack.
//
// Recovery - Panic Recovery Middleware
// Logger - Request/Response Logging
// Static - Static File Serving
func Classic() *Negroni {
return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public")))
}
- 建立标准的意义 - negroni 第三方扩展
例如,让 web 支持 gzip 协议:
注意,如果要支持静态文件压缩,请注意顺序!