geektutu / blog

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

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

Open geektutu opened 5 years ago

geektutu commented 5 years ago

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等类型响应的方法。

yixius commented 4 years ago

赞一下,非常6

walkmiao commented 4 years ago

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

geektutu commented 4 years ago

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

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

walkmiao commented 4 years ago

@geektutu

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

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

感谢分享的教程

xiaoxfan commented 4 years ago

大佬 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
}
geektutu commented 4 years ago

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

lorenwe commented 4 years ago

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

Leoooo-tqp commented 4 years ago

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

zhangzw001 commented 4 years ago

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

wilgx0 commented 4 years ago

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

Pissssofshit commented 3 years ago

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

geektutu commented 3 years ago

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

Pissssofshit commented 3 years ago

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

嘿嘿嘿,gay里gay气

baoyixiang commented 3 years ago

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

用双引号

codevvvv9 commented 3 years ago

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

codevvvv9 commented 3 years ago

@walkmiao

@geektutu

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

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

感谢分享的教程

这个调用顺序是说哪里啊

DiDiDaDiDiDa commented 3 years ago

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

感谢分享~

geektutu commented 3 years ago

感谢分享~

@DiDiDaDiDiDa 感谢认可,笔芯~ 😄

ghost commented 3 years ago

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

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

HongTian commented 3 years ago

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

@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"

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

songqii commented 3 years ago

很棒的教程~

SsnAgo commented 3 years ago

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

改成双引号即可

valiner commented 3 years ago

讲的太好了

galaxyzen commented 3 years ago

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

dashuaiduan commented 3 years ago

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

dashuaiduan commented 3 years ago

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

fanerge commented 2 years ago

66

Juminiy commented 2 years ago

没有json输出

dengyunsheng250 commented 2 years ago

@Juminiy 没有json输出

没有问题的

DurantVivado commented 2 years ago

如果能进行错误处理就更好了

qingxiao1999 commented 2 years ago

感觉json函数可以写成这样,就不用担心状态码写不进去了 c.SetHeader("Content-Type", "application/json") buffer := new(bytes.Buffer) encode := json.NewEncoder(buffer) if err := encode.Encode(obj); err != nil { http.Error(c.Writer, err.Error(), 500) } c.Status(code) c.Writer.Write(buffer.Bytes())

vvvviolet commented 2 years ago

打卡第二天~

ZhuoxueQAQ commented 2 years ago

感谢大佬的分享!小白有个小疑问,最后一段那里,engine类型应该是通过实现了ServerHTTP方法实现了http.Handler接口,而不是实现了ServeHTTP 接口。感觉这里应该是笔误?

EndlessCheng commented 2 years ago

大佬,你好,请问一下,http.ResponseWriter.wirte()这个write方法的实现在哪里查看呢,我在goland中只能跳转到ResponseWriter的接口定义,怎么可以找到实现的方法吗?

GoLand 可以看接口实现有哪些。

如果太多的话可以打断点跑跑看。

xiaogeamadeus commented 2 years ago
day2-context git:(master) curl -i http://localhost:9999/
HTTP/1.1 200 OK
Date: Thu, 10 Mar 2022 12:19:00 GMT
Content-Length: 15
Content-Type: text/plain; charset=utf-8

URL.Path = "/"

想问一下为什么我将代码下载到本地,终端输入这个之后他一直回复URL.Path = "/",萌新不知道是什么原因....

ts-cao commented 2 years ago

@ZhuoxueQAQ 感谢大佬的分享!小白有个小疑问,最后一段那里,engine类型应该是通过实现了ServerHTTP方法实现了http.Handler接口,而不是实现了ServeHTTP 接口。感觉这里应该是笔误?

ts-cao commented 2 years ago

没错吧 type Handler interface { ServeHTTP(ResponseWriter, *Request) }

ayuayue commented 2 years ago

这种只能获取到 Content-Type: application/x-www-form-urlencoded 的数据吧

hellolutar commented 1 year ago

太棒了呀,太棒了呀,谢谢大佬带我感受技术的魅力

xuyanshi commented 1 year ago

@EndlessCheng

大佬,你好,请问一下,http.ResponseWriter.wirte()这个write方法的实现在哪里查看呢,我在goland中只能跳转到ResponseWriter的接口定义,怎么可以找到实现的方法吗?

GoLand 可以看接口实现有哪些。

如果太多的话可以打断点跑跑看。

零神居然也在看这个😂

orangenekorou commented 1 year ago

每个单独小块基本弄明白了,但整个流程在脑海中就是没法顺利跑通。看来说到底还是没搞懂,学海无涯,小白膜拜大佬。

feymanpaper commented 1 year ago

太妙了太妙了,写轮子真的妙哇。精彩绝伦!

harmonic-lqw commented 12 months ago

2023年命令修改:curl "http://localhost:9999/login" -Method POST -Body 'username=geektutu&password=1234' 而且解码json的方式最新的gin也变了: jsonBytes, err := json.Marshal(obj) _, err = c.Writer.Write(jsonBytes)

lv997 commented 10 months ago

@harmonic-lqw 2023年命令修改:curl "http://localhost:9999/login" -Method POST -Body 'username=geektutu&password=1234' 而且解码json的方式最新的gin也变了: jsonBytes, err := json.Marshal(obj) _, err = c.Writer.Write(jsonBytes)

http的post方法发送body参数的时候有好几种格式呢,你这个是键值对。 例如可以参考的Content-Type就有application/json,application/x-www-form-urlencoded

lv997 commented 10 months ago

@feymanpaper 太妙了太妙了,写轮子真的妙哇。精彩绝伦!

感谢geektutu哈哈哈,跟着写轮子的同时也把golang的代码思想捋清了一遍。截止到目前为止,golang赛高!!

jiechen257 commented 10 months ago

最后的 main 函数执行结果,第一个应该只有 html 返回,没有其余的 response header

$ curl -i http://localhost:9999/
<h1>Hello Gee</h1>
liekkas99 commented 10 months ago

来学习了