blog
blog copied to clipboard
Go语言动手写Web框架 - Gee第六天 模板(HTML Template) | 极客兔兔
https://geektutu.com/post/gee-day6.html
7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了如何为Web框架添加HTML模板(HTML Template)以及静态文件(Serve Static Files)的功能。
看着做下来了, 收益匪浅, 多谢啦
if err := c.engine.htmlTemplates.ExecuteTemplate 这段没弄明白,Context的字段中不是没有engine么
@lotuso 感谢指出。day6 Context 字段中添加了成员 engine *Engine
,并在 ServeHTTP 实例化时赋值,忘记写在正文中了。
type Context struct {
// ...
// middleware
handlers []HandlerFunc
index int
// engine pointer
engine *Engine
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// ...
c := newContext(w, req)
c.handlers = middlewares
c.engine = engine
engine.router.handle(c)
}
engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern))
会在这行panic,panic: html/template: pattern matches no files: templates/*
看了好久,实在找不着问题
@nidepapa engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern)) 会在这行panic,panic: html/template: pattern matches no files:
templates/*
看了好久,实在找不着问题
这个问题是因为你的templates文件夹路径写错了==
之前还奇怪为什么要写
handler := group.createStaticHandler(relativePath, http.Dir(root)) urlPattern := path.Join(relativePath, "/*filepath")
后来才发现原来fileHandler.ServeHTTP 会把req.url.path 作为文件路径
请问LoadHTMLGlob LoadHTMLGlob 为什么不写在RouterGroup里。 我没办法这样调用engine.Group("/html1").LoadHTMLGlob
@GiveTimeToLife 这一块设计比较简单,RouterGroup 只做路由的事情,Engine 做全局的事情。LoadHTMLGlob
设计成全局的了,你可以尝试下,实现 RouterGroup 级别的模板。
@geektutu thx , 教程很棒!
urlPattern := path.Join(relativePath, "/*filepath") 应该是 urlPattern := path.Join(relativePath, "/:filepath") 吧
@liron-li 不同的模式,*filepath
代表贪心匹配,例如 /css/geektutu.css
,可以匹配剩余的所有子路径。/:filepath
只匹配一层路径。
func (c *Context) HTML(code int, name string, data interface{}) {
c.SetHeader("Content-Type", "text/html")
c.Status(code)
if err := c.engine.htmlTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
c.Fail(500, err.Error())
}
}
这里前面c.Status(code)那里调用了response.WriteHeader,导致后面c.Fail再次调用response.WriteHeader无效
萌新求问,[2]*student{stu1, stu2}
应该怎么理解呢,[2]*student
是指有两个*student类型的变量吗?
urlPattern := path.Join(relativePath, "/*filepath") 是否应该修改为urlPattern := path.Join(group.prefix+relativePath, "/*filepath")
@Amber-JY
萌新求问,
[2]*student{stu1, stu2}
应该怎么理解呢,[2]*student
是指有两个*student类型的变量吗?
可以理解为: 装了2个*student类型的数组变量.
@xing-shadow urlPattern := path.Join(relativePath, "/*filepath") 是否应该修改为urlPattern := path.Join(group.prefix+relativePath, "/*filepath")
不需要,因为GET方法会自动拼接prefix
createStaticHandler 这个函数,没有返回值啊,为什么return一个func呢?
@dinghongfa createStaticHandler 这个函数,没有返回值啊,为什么return一个func呢?
返回值就是一个HandlerFunc,用来处理静态文件访问的。结合这里group.GET(urlPattern, handler)
来看。
请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?
@Alex-Jee 请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?
也有这个疑问
@dinghongfa createStaticHandler 这个函数,没有返回值啊,为什么return一个func呢?
HandlerFunc在前面就被定义为一个func(*Context)了,返回值就是func
@zmf173417897
@Alex-Jee 请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?
也有这个疑问 GET只是将处理函数注册到Context中的Engine的router的handlers中,在engine的severHTTP方法中调用router的handle方法将符合条件的处理函数复制到Context的handlers,然后调用Context的next方法执行相应的处理函数。 而在go的http包中,我们要想执行注册好的对应的路由函数,一般是通过Handle或者HandleFunc函数将其放入DefaultServeMux的m和es中,相当于Context的handlers。 ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器,会调用ServeMux的ServeHTTP方法,再调用注册的处理器函数的ServeHTTP。如果处理器参数通常是不为nil,则直接调用该处理器的ServeHTTP方法。 再gee项目中engine的severHTTP是我们自己实现的,所以我们需要自己在get中注册的处理器函数中手动调用本来应该由Handle或者HandleFunc函数注册的处理器的ServeHTTP。 可能说的不清楚,总结起来就是http的ListenAndServe当参数为nil时会调用DefaultServeMux中ServeHTTP,然后调用其muxEntry的实现Handler接口对象的ServeHTTP方法。我们实现engine的severHTTP并没有这么做,所以需要我们自己手动调用fileServer的severHTTP 有不对的地方请指教
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e //注册进来的处理函数放在这里
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)//拿到之前HandleFunc注册的处理函数
h.ServeHTTP(w, r)//调用HandleFunc注册的处理函数
}
参考链接:https://studygolang.com/articles/33920 https://www.cnblogs.com/gwyy/p/14100157.html
请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?
原文里已经明确说了,net/http 库已经实现了静态资源服务器,也就是fileServer.ServeHTTP。我们做的只是接收到请求,把请求的路径地址映射到静态资源所在的真是地址,剩下的就交给静态资源服务器去做就好了。
@fatFire
请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?
原文里已经明确说了,net/http 库已经实现了静态资源服务器,也就是fileServer.ServeHTTP。我们做的只是接收到请求,把请求的路径地址映射到静态资源所在的真是地址,剩下的就交给静态资源服务器去做就好了。
恩恩,是这样; 对比下之前的路由的handler, 都是执行了c.String() 或者其他写入 Writer 的操作, 这里静态资源服务器也是同样意思, 就是将文件内容写入 Writer 中, 如果不用 fileServer.ServeHTTP, 我理解也是可以的, 那就是需要自己手动读取文件内容, 写入返回中, 这样也ok
css.tmpl里的css文件为什么没有发送一次HTTP请求?
@ghost css.tmpl里的css文件为什么没有发送一次HTTP请求?
浏览器收到这个html文件,会自动执行加载css,发起http请求
有个问题,既然是服务端渲染,那代表用户是不需要请求/assets里面的文件的对吧,这些文件只是在服务端渲染的时候需要用到,比如css.tmpl文件请求了geektutu.css,那为什么需要给/assets加一个GET路由路径呢?
@LufeiCheng
@ghost css.tmpl里的css文件为什么没有发送一次HTTP请求?
浏览器收到这个html文件,会自动执行加载css,发起http请求
这应该是客户端渲染的过程吧,在这个例子里应该是css在第一次请求的时候就已经加载好了