blog icon indicating copy to clipboard operation
blog copied to clipboard

Go语言动手写Web框架 - Gee第二天 上下文Context | 极客兔兔

Open geektutu opened this issue 5 years ago • 53 comments

https://geektutu.com/post/gee-day2.html

7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架。本文介绍了请求上下文(Context)的设计理念,封装了返回JSON/String/Data/HTML等类型响应的方法。

geektutu avatar Aug 18 '19 17:08 geektutu

赞一下,非常6

yixius avatar Dec 06 '19 07:12 yixius

正确的调用顺序应该是Header().Set 然后WriteHeader() 最后是Write()

walkmiao avatar Mar 03 '20 07:03 walkmiao

@walkmiao 正确的调用顺序应该是Header().Set 然后WriteHeader() 最后是Write()

感谢指出,在 WriteHeader() 后调用 Header().Set 是不会生效的,已经更正~

geektutu avatar Mar 03 '20 12:03 geektutu

@geektutu

@walkmiao 正确的调用顺序应该是Header().Set 然后WriteHeader() 最后是Write()

感谢指出,在 WriteHeader() 后调用 Header().Set 是不会生效的,已经更正~

感谢分享的教程

walkmiao avatar Mar 04 '20 00:03 walkmiao

大佬 JSON()这里有点问题

func (c *Context) JSON(code int, obj interface{}) {
	c.SetHeader("Content-Type", "application/json")
	c.Status(code)
	//  c.StatusCode = code
	//	c.Writer.WriteHeader(code)
	encoder := json.NewEncoder(c.Writer)
	if err := encoder.Encode(obj); err != nil {
		http.Error(c.Writer, err.Error(), 500)
		//  w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		//	w.Header().Set("X-Content-Type-Options", "nosniff")
		//	w.WriteHeader(code)
		//	fmt.Fprintln(w, error)
	}
}

如果err!=nil的话http.Error(c.Writer, err.Error(), 500)这里是不起作用的,因为前面已经执行了WriteHeader(code),那么返回码将不会再更改http.Error(c.Writer, err.Error(), 500)里面的w.WriteHeader(code)、w.Header().Set()不起作用,而且encoder.Encode(obj)相当于调用了Write(),http.Error(c.Writer, err.Error(), 500)里面的WriteHeader、Header().Set()操作都是无效的。我看了gin的代码,如果encoder.Encode(obj)这里报错的话是直接panic,感觉这里如果err!=nil的话确实不好处理

附上gin的代码 render/json.go 56行

// Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) (err error) {
	if err = WriteJSON(w, r.Data); err != nil {
		panic(err)
	}
	return
}

// WriteContentType (JSON) writes JSON ContentType.
func (r JSON) WriteContentType(w http.ResponseWriter) {
	writeContentType(w, jsonContentType)
}

// WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
	writeContentType(w, jsonContentType)
	encoder := json.NewEncoder(w)
	err := encoder.Encode(&obj)
	return err
}

xiaoxfan avatar Mar 17 '20 06:03 xiaoxfan

@xiaoxfan 非常感谢指出了这个问题,之前的实现没有注意到这个问题。看到这里的童鞋可以看看gin的实现,地址贴在这里render/json.go#L56

geektutu avatar Mar 18 '20 13:03 geektutu

day2-context/gee/router.go文件的第18行的正确写法是handler(c.Writer, c.Req),在handle函数中

lorenwe avatar Apr 14 '20 10:04 lorenwe

感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

Leoooo-tqp avatar Jul 13 '20 14:07 Leoooo-tqp

@lorenwe day2-context/gee/router.go文件的第18行的正确写法是handler(c.Writer, c.Req),在handle函数中

文章的没错, 你是不是 gee.go中 type HandlerFunc func(*Context) 没改, 还是写的type HandlerFunc func(w http.ResponseWriter, r *http.Request) ?

zhangzw001 avatar Aug 12 '20 06:08 zhangzw001

感谢手把手的教我框架的设计思想 让我对人生又重新燃起了希望

wilgx0 avatar Aug 28 '20 03:08 wilgx0

兔兔,兔兔我爱你,我要给你生🐒(虽然我是男的)

Pissssofshit avatar Nov 06 '20 13:11 Pissssofshit

@Pissssofshit 这。。。公司联谊你可以的。😉

geektutu avatar Nov 07 '20 09:11 geektutu

@Pissssofshit 这。。。公司联谊你可以的。😉

嘿嘿嘿,gay里gay气

Pissssofshit avatar Nov 08 '20 02:11 Pissssofshit

@Leoooo-tqp 感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

用双引号

baoyixiang avatar Dec 23 '20 08:12 baoyixiang

非常棒,手敲一遍,感触颇深

codevvvv9 avatar Dec 26 '20 15:12 codevvvv9

@walkmiao

@geektutu

@walkmiao 正确的调用顺序应该是Header().Set 然后WriteHeader() 最后是Write()

感谢指出,在 WriteHeader() 后调用 Header().Set 是不会生效的,已经更正~

感谢分享的教程

这个调用顺序是说哪里啊

codevvvv9 avatar Dec 26 '20 15:12 codevvvv9

@Leoooo-tqp 感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

正确调用姿势:curl --location --request POST '127.0.0.1:9999/login'
--form 'password="张三"'
--form 'username="李思"'

DiDiDaDiDiDa avatar Dec 30 '20 06:12 DiDiDaDiDiDa

感谢分享~

DiDiDaDiDiDa avatar Dec 30 '20 06:12 DiDiDaDiDiDa

感谢分享~

@DiDiDaDiDiDa 感谢认可,笔芯~ 😄

geektutu avatar Dec 31 '20 14:12 geektutu

@Leoooo-tqp 感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

我也有这个,难道是命令不一致吗

ghost avatar Jan 18 '21 09:01 ghost

@jianhuacc

@Leoooo-tqp 感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

我也有这个,难道是命令不一致吗

用双引号,curl "http://localhost:9999/login" -X POST -d "username=geektutu&password=1234"

HongTian avatar Apr 04 '21 14:04 HongTian

@HongTian

@jianhuacc

@Leoooo-tqp 感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

我也有这个,难道是命令不一致吗

用双引号,curl "http://localhost:9999/login" -X POST -d "username=geektutu&password=1234"

用双引号完美解决,赞!!!

hanhua111 avatar Apr 26 '21 02:04 hanhua111

很棒的教程~

songqii avatar May 11 '21 08:05 songqii

@Leoooo-tqp 感谢教程!我在使用curl命令 curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 时无法解析出传递的参数,并且显示'password' 不是内部或外部命令,也不是可运行的程序或批处理文件。这是什么原因?

改成双引号即可

SsnAgo avatar May 19 '21 09:05 SsnAgo

讲的太好了

valiner avatar Jul 05 '21 12:07 valiner

@codevvvv9

@walkmiao

@geektutu

@walkmiao 正确的调用顺序应该是Header().Set 然后WriteHeader() 最后是Write()

感谢指出,在 WriteHeader() 后调用 Header().Set 是不会生效的,已经更正~

感谢分享的教程

这个调用顺序是说哪里啊

If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) before writing the data.

Superfluous WriteHeader has no effect. That's mean if we want to set status code except http.StatusOK, we should call WriteHeader before Write.

Changing the header map after a call to WriteHeader (or Write) has no effect unless the modified headers are trailers. That's mean if we want to modify the response header, we should call Writer.Header().Set(key, value) before WriteHeader.

galaxyzen avatar Sep 12 '21 13:09 galaxyzen

func (c *Context) Render(code int, r render.Render) {
	c.Status(code)

	if !bodyAllowedForStatus(code) {
		r.WriteContentType(c.Writer)
		c.Writer.WriteHeaderNow()
		return
	}

	if err := r.Render(c.Writer); err != nil {
		panic(err)
	}
}

我发现gin中的*Context.JSON方法会调用*Context.Render方法,而此方法会最先调用WriteHeader方法,在整个调用链最后才会设置Content-Type

func writeContentType(w http.ResponseWriter, value []string) {
	header := w.Header()
	if val := header["Content-Type"]; len(val) == 0 {
		header["Content-Type"] = value
	}
}

这是为什么呢?不应该是 Header().Set 然后WriteHeader() 最后是Write()吗?

明白了 gin中的c.Writer类型是gin.ResponseWriter, 实际类型是gin.responseWriter,不是http.ResponseWriter

galaxyzen avatar Sep 12 '21 14:09 galaxyzen

@geektutu @xiaoxfan 非常感谢指出了这个问题,之前的实现没有注意到这个问题。看到这里的童鞋可以看看gin的实现,地址贴在这里render/json.go#L56

dashuaiduan avatar Sep 24 '21 03:09 dashuaiduan

Req.FormValue(key) 获取不到post参数吗

dashuaiduan avatar Sep 24 '21 03:09 dashuaiduan

66

fanerge avatar Oct 09 '21 09:10 fanerge