geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

Go语言动手写Web框架 - Gee第六天 模板(HTML Template) | 极客兔兔 #49

Open geektutu opened 4 years ago

geektutu commented 4 years ago

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)的功能。

Luyakus commented 4 years ago

看着做下来了, 收益匪浅, 多谢啦

bambuo commented 4 years ago

if err := c.engine.htmlTemplates.ExecuteTemplate 这段没弄明白,Context的字段中不是没有engine么

geektutu commented 4 years ago

@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)
}
nidepapa commented 4 years ago

engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern)) 会在这行panic,panic: html/template: pattern matches no files: templates/* 看了好久,实在找不着问题

leisun123 commented 3 years ago

@nidepapa engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern)) 会在这行panic,panic: html/template: pattern matches no files: templates/* 看了好久,实在找不着问题

这个问题是因为你的templates文件夹路径写错了==

givetimetolife commented 3 years ago

之前还奇怪为什么要写 handler := group.createStaticHandler(relativePath, http.Dir(root)) urlPattern := path.Join(relativePath, "/*filepath") 后来才发现原来fileHandler.ServeHTTP 会把req.url.path 作为文件路径

givetimetolife commented 3 years ago

请问LoadHTMLGlob LoadHTMLGlob 为什么不写在RouterGroup里。 我没办法这样调用engine.Group("/html1").LoadHTMLGlob

geektutu commented 3 years ago

@GiveTimeToLife 这一块设计比较简单,RouterGroup 只做路由的事情,Engine 做全局的事情。LoadHTMLGlob 设计成全局的了,你可以尝试下,实现 RouterGroup 级别的模板。

givetimetolife commented 3 years ago

@geektutu thx , 教程很棒!

liron-li commented 3 years ago

urlPattern := path.Join(relativePath, "/*filepath") 应该是 urlPattern := path.Join(relativePath, "/:filepath") 吧

geektutu commented 3 years ago

@liron-li 不同的模式,*filepath 代表贪心匹配,例如 /css/geektutu.css,可以匹配剩余的所有子路径。/:filepath 只匹配一层路径。

liushuangls commented 3 years ago
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无效

Chips-Zhang commented 3 years ago

萌新求问,[2]*student{stu1, stu2}应该怎么理解呢,[2]*student是指有两个*student类型的变量吗?

xing-shadow commented 3 years ago

urlPattern := path.Join(relativePath, "/filepath") 是否应该修改为urlPattern := path.Join(group.prefix+relativePath, "/filepath")

ChrisLeejing commented 3 years ago

@Amber-JY

萌新求问,[2]*student{stu1, stu2}应该怎么理解呢,[2]*student是指有两个*student类型的变量吗?

可以理解为: 装了2个*student类型的数组变量.

puck1006 commented 3 years ago

@xing-shadow urlPattern := path.Join(relativePath, "/filepath") 是否应该修改为urlPattern := path.Join(group.prefix+relativePath, "/filepath")

不需要,因为GET方法会自动拼接prefix

dinghongfa commented 3 years ago

createStaticHandler 这个函数,没有返回值啊,为什么return一个func呢?

LCAR979 commented 2 years ago

@dinghongfa createStaticHandler 这个函数,没有返回值啊,为什么return一个func呢?

返回值就是一个HandlerFunc,用来处理静态文件访问的。结合这里group.GET(urlPattern, handler)来看。

Alex-Jee commented 2 years ago

请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?

zmf173417897 commented 2 years ago

@Alex-Jee 请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?

也有这个疑问

dbhuo commented 2 years ago

@dinghongfa createStaticHandler 这个函数,没有返回值啊,为什么return一个func呢?

HandlerFunc在前面就被定义为一个func(*Context)了,返回值就是func

liujing-siyang commented 2 years ago

@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
fatFire commented 2 years ago

请问,为什么func (group *RouterGroup) createStaticHandler里,需要自己启动fileServer.ServeHTTP(c.Writer, c.Req)呢?不是已经把这个handler绑定到了GET上吗?之前的GET请求也不需要自己来启动ServeHTTP呀?

原文里已经明确说了,net/http 库已经实现了静态资源服务器,也就是fileServer.ServeHTTP。我们做的只是接收到请求,把请求的路径地址映射到静态资源所在的真是地址,剩下的就交给静态资源服务器去做就好了。

llqhz commented 2 years ago

@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

ghost commented 2 years ago

css.tmpl里的css文件为什么没有发送一次HTTP请求?

LufeiCheng commented 1 year ago

@ghost css.tmpl里的css文件为什么没有发送一次HTTP请求?

浏览器收到这个html文件,会自动执行加载css,发起http请求

ZirongCai commented 12 months ago

有个问题,既然是服务端渲染,那代表用户是不需要请求/assets里面的文件的对吧,这些文件只是在服务端渲染的时候需要用到,比如css.tmpl文件请求了geektutu.css,那为什么需要给/assets加一个GET路由路径呢?

ZirongCai commented 12 months ago

@LufeiCheng

@ghost css.tmpl里的css文件为什么没有发送一次HTTP请求?

浏览器收到这个html文件,会自动执行加载css,发起http请求

这应该是客户端渲染的过程吧,在这个例子里应该是css在第一次请求的时候就已经加载好了