blog icon indicating copy to clipboard operation
blog copied to clipboard

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

Open geektutu opened this issue 5 years ago • 28 comments

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

geektutu avatar Sep 08 '19 13:09 geektutu

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

Luyakus avatar Dec 05 '19 02:12 Luyakus

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

bambuo avatar Feb 20 '20 04:02 bambuo

@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)
}

geektutu avatar Feb 20 '20 04:02 geektutu

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

nidepapa avatar Jun 15 '20 11:06 nidepapa

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

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

leisun123 avatar Aug 05 '20 04:08 leisun123

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

givetimetolife avatar Oct 26 '20 02:10 givetimetolife

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

givetimetolife avatar Oct 26 '20 03:10 givetimetolife

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

geektutu avatar Oct 26 '20 07:10 geektutu

@geektutu thx , 教程很棒!

givetimetolife avatar Oct 27 '20 02:10 givetimetolife

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

liron-li avatar Dec 07 '20 08:12 liron-li

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

geektutu avatar Dec 07 '20 11:12 geektutu

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无效

liushuangls avatar Jan 29 '21 11:01 liushuangls

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

Chips-Zhang avatar Mar 08 '21 02:03 Chips-Zhang

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

xing-shadow avatar Mar 10 '21 09:03 xing-shadow

@Amber-JY

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

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

ChrisLeejing avatar May 05 '21 03:05 ChrisLeejing

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

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

puck1006 avatar Jun 29 '21 15:06 puck1006

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

dinghongfa avatar Jul 09 '21 06:07 dinghongfa

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

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

LCAR979 avatar Jul 29 '21 16:07 LCAR979

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

Alex-Jee avatar Oct 27 '21 04:10 Alex-Jee

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

也有这个疑问

zmf173417897 avatar Nov 23 '21 01:11 zmf173417897

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

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

dbhuo avatar Dec 03 '21 07:12 dbhuo

@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

liujing-siyang avatar Dec 25 '21 10:12 liujing-siyang

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

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

fatFire avatar Feb 13 '22 08:02 fatFire

@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

llqhz avatar Feb 13 '22 15:02 llqhz

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

ghost avatar Mar 20 '22 13:03 ghost

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

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

LufeiCheng avatar Jul 25 '22 07:07 LufeiCheng

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

ZirongCai avatar Jul 28 '23 13:07 ZirongCai

@LufeiCheng

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

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

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

ZirongCai avatar Jul 28 '23 13:07 ZirongCai