geektutu / blog

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

Go语言动手写Web框架 - Gee第一天 http.Handler | 极客兔兔 #37

Open geektutu opened 5 years ago

geektutu commented 5 years ago

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

7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架。本文介绍了Go标准库 net/http 和 http.Handler 接口的使用,拦截所有的 HTTP 请求,交给Gee框架处理。

ccmzd commented 4 years ago

谢谢站主提供的项目经验,我有个问题不是很懂,网上查了资料也没有找到,http.ListenAndServe(":9999", engine)第二个参数是一个interface类型,里面有有一个ServeHTTP()方法,您代码中传入了一个结构体里面有一个ServeHTTP()方法,但是并没有调用啊,怎么就执行了。结构体不是应该先赋值给接口,接口才能够调用结构体中的方法吗。在下才疏学浅,想了2天,查了百度,还是觉得迷迷糊糊的,希望大神能够赐教,感激不尽

geektutu commented 4 years ago

@MasterCl 第二个参数类型是接口类型 http.HandlerHandler 的定义博文中已经贴了,是从 http 的源码中找到的。

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

func ListenAndServe(address string, h Handler) error

在 Go 语言中,实现了接口方法的 struct 都可以强制转换为接口类型。你可以这么写:

handler := (http.Handler)(engine) // 手动转换为借口类型
log.Fatal(http.ListenAndServe(":9999", handler))

然后,ListenAndServe 方法里面会去调用 handler.ServeHTTP() 方法,你感兴趣,可以在 http 的源码中找到调用的地方。但是这么写是多余的,传参时,会自动进行参数转换的。所以直接传入engine 即可。

ccmzd commented 4 years ago

@geektutu 非常感谢,我理解了

ppd0705 commented 4 years ago

第一天get,学框架的同时学习go。主要学习到:1. struct添加method 2. go mod 使用

JIACHENG135 commented 4 years ago

博主文章写得真好,默默学习膜拜一波

maogou commented 4 years ago

博主写的真的是太赞了👍

建议博主把

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    key := req.Method + "-" + req.URL.Path
    if handler, ok := engine.router[key]; ok {
        handler(w, req)
    } else {
        fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
    }
}

修改为

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    key := req.Method + "-" + req.URL.Path
    if handler, ok := engine.router[key]; ok {
        handler(w, req)
    } else {
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
    }
}
SilverShaded commented 3 years ago

跪谢大佬的教程,在google搜索出来的web实例教程大多都是用标准库写一个简易web应用,想进阶却难以突破,直到看见大佬这份框架教程。

geektutu commented 3 years ago

@maogou 非常感谢你的建议,下次更新的时候我改一下,这里确实是忘记设置返回码了,不标准。

@tumaowolf 感谢你的认可,web 是第一期,后面还有三期~

fangbaogang commented 3 years ago

太强了,谢谢,财大牛皮😃

geektutu commented 3 years ago

@fangbaogang 感谢认可~ 😁

wzy2687 commented 3 years ago

正想学学http 相关知识, 看了第一天就感觉很好. 感谢tutu. 赞赏支持了下.

geektutu commented 3 years ago

@wzy2687 感谢你的支持和赞赏 😸,希望能对你有用~

Fxskyzz commented 3 years ago

大佬牛批,看你的文章真的通俗易懂,能让小白看懂的文章真的赞,跟读go语言高级编程第一章一样的感觉,爽,谢谢tutu

geektutu commented 3 years ago

@chocolateszz 感谢你的认可,通俗易懂是这个系列的终极目的~

baoyixiang commented 3 years ago

@CaocaoWym 谢谢站主提供的项目经验,我有个问题不是很懂,网上查了资料也没有找到,http.ListenAndServe(":9999", engine)第二个参数是一个interface类型,里面有有一个ServeHTTP()方法,您代码中传入了一个结构体里面有一个ServeHTTP()方法,但是并没有调用啊,怎么就执行了。结构体不是应该先赋值给接口,接口才能够调用结构体中的方法吗。在下才疏学浅,想了2天,查了百度,还是觉得迷迷糊糊的,希望大神能够赐教,感激不尽

这个是标准库里的逻辑吧,第二个参数传nil的话,标准库里默认用DefaultServeMux这个接口,传了你自定义的,标准库就调用你自定义的。if xx == nil 判断下就知道了。 我看了下源码

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}
mesiyar commented 3 years ago

mark 跟进学习

geektutu commented 3 years ago

@mesiyar 希望有所收获,ღ( ´・ᴗ・` )比心

AcVoyager commented 3 years ago

谢谢站主。最近打算学go,看了tour of go之后就过来跟你的教程啦

geektutu commented 3 years ago

@AcVoyager 嗯,加油,希望对你有帮助~

ghost commented 3 years ago

@CaocaoWym 谢谢站主提供的项目经验,我有个问题不是很懂,网上查了资料也没有找到,http.ListenAndServe(":9999", engine)第二个参数是一个interface类型,里面有有一个ServeHTTP()方法,您代码中传入了一个结构体里面有一个ServeHTTP()方法,但是并没有调用啊,怎么就执行了。结构体不是应该先赋值给接口,接口才能够调用结构体中的方法吗。在下才疏学浅,想了2天,查了百度,还是觉得迷迷糊糊的,希望大神能够赐教,感激不尽

因为这个结构体有个方法已经实现了SeverHTTP,也就是说engine实现了Handler接口,故可以直接调用了

suibianmzl commented 3 years ago

谢谢大佬这一系列!

Shawn-fung commented 3 years ago

@geektutu @MasterCl 第二个参数类型是接口类型 http.HandlerHandler 的定义博文中已经贴了,是从 http 的源码中找到的。

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

func ListenAndServe(address string, h Handler) error

在 Go 语言中,实现了接口方法的 struct 都可以强制转换为接口类型。你可以这么写:

handler := (http.Handler)(engine) // 手动转换为借口类型
log.Fatal(http.ListenAndServe(":9999", handler))

然后,ListenAndServe 方法里面会去调用 handler.ServeHTTP() 方法,你感兴趣,可以在 http 的源码中找到调用的地方。但是这么写是多余的,传参时,会自动进行参数转换的。所以直接传入engine 即可。

是否可以理解为:自己重写了ServeHTTP方法,所以默认会自动调用重写后的ServeHTTP

hevi1991 commented 3 years ago

这一章跳下一章的链接对不上

ryan4yin commented 3 years ago

我测试的时候发现第一个 example 不会报 404,这和第二个 example 不一样。 查了下文档,原来默认使用的 ServerMux 有特殊的模式匹配规则。

ryetan commented 3 years ago

我是一只运维,对go初学很有帮助。感谢兔兔。

nm2006717 commented 3 years ago

也就是说,只要传入任何实现了 ServerHTTP 【接口】的实例。这里【接口】应该改成方法吧。

Lzu-Alisha commented 3 years ago

@nm2006717 也就是说,只要传入任何实现了 ServerHTTP 【接口】的实例。这里【接口】应该改成方法吧。 接口 = &类型 (该类型实现了接口中的所有方法),接口和具体方法的实现是这样传递的,方法由调用者去保证,ServeHttp定义的就是 type Handler interface { ServeHTTP(ResponseWriter, *Request) } 接口本来就是一个函数指针类型的集合

yudidi commented 3 years ago

如果有学过springmvc的同学,可以发现这里的engine就是就是dispatchservlet

Kimgtb commented 3 years ago

请问,还是需要引入gee框架吗?谢谢

schinapi commented 3 years ago

go.mod我这样写还是找不到依赖,麻烦问一下各位大佬知道这个怎么做吗

JasonDevops commented 3 years ago

正愁没啥参考学习的文章,刚好看到这篇文章,受益匪浅,感谢大佬!!

brady-wang commented 3 years ago

学习了

ghost commented 3 years ago

作为一个新手记录一下本次学习下来的收获: 从设计的角度来学习

学习到的实现方法

wangyouquanMas commented 3 years ago

@schinapi go.mod我这样写还是找不到依赖,麻烦问一下各位大佬知道这个怎么做吗

wangyouquanMas commented 3 years ago

import的时候,使用从项目名称开始的路径吧

wangyouquanMas commented 3 years ago

个人小结,希望大家给我补充一下

首先做了什么 : 本部分基于net/http基础库进行了二次封装。依据http.ListenAndServe监听了端口,将所有请求交给handler进行统一处理的特点。 对于不同的请求路径及handler,通过map与统一的handler type进行绑定,只需要http.ListenAndServe就可以处理所有请求, 不需要再像基础库那样,对于不同的请求路径写了多个http.HandlerFunc进行处理。

好处: 统一的请求入口,就可以进行统一的处理,如日志,拦截器等。

tahitimoon commented 2 years ago

楼主是真的厉害 感谢分享~ 学完Python再学Go感觉很亲切

jxy90 commented 2 years ago

从go夜读过来的,发现了宝藏博主.学习了

bestgopher commented 2 years ago

@geektutu @maogou 非常感谢你的建议,下次更新的时候我改一下,这里确实是忘记设置返回码了,不标准。

@tumaowolf 感谢你的认可,web 是第一期,后面还有三期~

但是没改呀。。。。

wjh791072385 commented 2 years ago

非常感谢楼主 很清晰

SharkLJ commented 2 years ago

请问为什么构造POST方法就会返回404呢,用GET是正常的。下面的r.GET换成r.POST就返回404


func main() {
    r := gee.New()
    r.GET("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
    })

    r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
        for k, v := range req.Header {
            fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
        }
    })

    r.Run(":9999")
}
steven-ok commented 2 years ago

请问下格式化字符串中的 %q 是什么意思,以前没有用过,了解下。

steven-ok commented 2 years ago

请问下格式化字符串中的 %q 是什么意思,以前没有用过,了解下。

了解了,是以 golang 中字面值的形式打印的

KnightAtGitHub commented 2 years ago

@SharkLJ 请问为什么构造POST方法就会返回404呢,用GET是正常的。下面的r.GET换成r.POST就返回404


func main() {
  r := gee.New()
  r.GET("/", func(w http.ResponseWriter, req *http.Request) {
      fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
  })

  r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
      for k, v := range req.Header {
          fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
      }
  })

  r.Run(":9999")
}

测试的时候有改成post吗?像下面这样

curl -X POST http://localhost:8088
LufeiCheng commented 2 years ago

多谢大佬分享教程,对新学习Golang的小伙伴太有帮助了,非常适合用来实战!!!

MachineGunLin commented 2 years ago

@schinapi go.mod我这样写还是找不到依赖,麻烦问一下各位大佬知道这个怎么做吗

同找不到,请问怎么解决,网上没找到很好的解决方案.

MachineGunLin commented 2 years ago

@maogou 博主写的真的是太赞了👍

建议博主把

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  key := req.Method + "-" + req.URL.Path
  if handler, ok := engine.router[key]; ok {
      handler(w, req)
  } else {
      fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
  }
}

修改为

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  key := req.Method + "-" + req.URL.Path
  if handler, ok := engine.router[key]; ok {
      handler(w, req)
  } else {
        w.WriteHeader(http.StatusNotFound)
      fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
  }
}

为啥要这样改

solitudealma commented 1 year ago

@MachineGunLin

@maogou 博主写的真的是太赞了👍

建议博主把

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    key := req.Method + "-" + req.URL.Path
    if handler, ok := engine.router[key]; ok {
        handler(w, req)
    } else {
        fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
    }
}

修改为

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    key := req.Method + "-" + req.URL.Path
    if handler, ok := engine.router[key]; ok {
        handler(w, req)
    } else {
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
    }
}

为啥要这样改

需要设置响应请求的状态码

pp-tt commented 1 year ago

卧槽,我第一次评论,简直太牛逼了博主

superfeimao commented 1 year ago

来了,肥猫我啊,狠狠的跟tutu学!