GO WEB 开发

搭建一个简单的web服务

package main

import (
	"fmt"
	"net/http"
	"log"
)

func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析参数,默认是不会解析的
	r.ParseForm()

	//打印参数
	fmt.Println(r.Form)

	//打印出url
	fmt.Println("path:", r.URL.Path)

	//打印出scheme
	fmt.Println("scheme:", r.URL.Scheme)

	//打印出所有的参数
	for k, v := range r.Form {
		fmt.Println("key:", k, ",value:", v)
	}

	//输出到客户端
	fmt.Fprintf(w, "hello,go!")
}


func main() {
	//设置访问的路由
	http.HandleFunc("/hello", sayHello)

	//设置监听的端口
	err := http.ListenAndServe(":9090", nil)

	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

运行命令

go run serve.go

在页面上访问

http://localhost:9090/hello

显示结果

hello,go!

后台控制台结果

map[a:[1 3] b:[2]]
path: /hello
scheme:
key: a ,value: [1 3]
key: b ,value: [2]

Go http包详解

go的http有两个核心功能:Conn、ServeMux

Conn的goroutine

与我们一般编写的http服务器不同,Go为了实现高并发和高性能,使用了goroutines来处理Conn的读写事件,这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证

Go在等待客户端请求里面是这样写的:

c, err := srv.newConn(rw)
if err != nil {
    continue
}
go c.serve()

这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handle,该handle中便可以读取到响应的header信息,这样保证了每个请求的独立性。

ServeMux的自定义

前面介绍conn.serve的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。serveMux的结构如下

type ServeMux struct {
    // 锁,由于请求设计到并发处理,因此这里需要一个锁机制
	mu    sync.RWMutex  
	// 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
	m     map[string]muxEntry
	// 是否存在任意的规则中带有host信息
	hosts bool // whether any patterns contain hostnames
}

下面看看muxEntry定义

type muxEntry struct {
   // 这个路由表达式对应的那个handle
	h       Handler
	// 匹配字符串
	pattern string
}

接着看一下Handle的定义

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}

Handle是一个接口,但是上文中的例子中sayHell函数并没有实现ServeHTTP这个方法,为什么能添加呢?原来在http包里面还定义了一个类型HandleFunc,我们定义sayHell函数,通过调用HandleFunc(f)强制把sayHello转换成了HandleFunc类型,这样f就拥有了ServeHTTP方法

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

路由器里面存储好了响应的路由规则之后,具体的请求又是怎么分发的呢?看下面的默认路由器实现的ServeHTTP方法

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)
	h.ServeHTTP(w, r)

如果是*则关闭连接;否则调用mux.Hander®返回对应路由器的handle,并执行hander.ServeHTTP(w,r)方法

那么mux.Handler®是怎么处理的呢?

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

	// CONNECT requests are not canonicalized.
	if r.Method == "CONNECT" {
		// If r.URL.Path is /tree and its handler is not registered,
		// the /tree -> /tree/ redirect applies to CONNECT requests
		// but the path canonicalization does not.
		if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)
	}

	// All other requests have any port stripped and path cleaned
	// before passing to mux.handler.
	host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)

	// If the given path is /tree and its handler is not registered,
	// redirect for /tree/.
	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}

	if path != r.URL.Path {
		_, pattern = mux.handler(host, path)
		url := *r.URL
		url.Path = path
		return RedirectHandler(url.String(), StatusMovedPermanently), pattern
	}

	return mux.handler(host, r.URL.Path)
}

原来它是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到后返回存储的handler,调用这个handler的ServerHTTP接口就可以执行到响应的函数了

通过上面介绍,我们了解到整个路由过程,Go其实支持外部实现的路由器,ListenAndServe 的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们在自己实现的路由器的ServeHTTP里面实现自定义路由功能

package main

import (
	"fmt"
	"net/http"
	"log"
)

type MyMux struct{

}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/hello" {
		sayHello(w,r)
		return
	}
	log.Fatal("not found")
	http.NotFound(w, r)
	return
}
func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析参数,默认是不会解析的
	r.ParseForm()

	//打印参数
	fmt.Println(r.Form)

	//打印出url
	fmt.Println("path:", r.URL.Path)

	//打印出scheme
	fmt.Println("scheme:", r.URL.Scheme)

	//打印出所有的参数
	for k, v := range r.Form {
		fmt.Println("key:", k, ",value:", v)
	}

	//输出到客户端
	fmt.Fprintf(w, "hello,go!")
}


func main() {
	//设置访问的路由
	// http.HandleFunc("/hello", sayHello)

	//设置监听的端口
	err := http.ListenAndServe(":9090", &MyMux{})

	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

Go代码的执行流程

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程

首先调用http.HandleFunc

调用DefaultServeMux的HandleFunc
调用DefaultServeMux的Handle
往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则

其次调用http.ListenAndServe(":9090", nil)

实例化Server
调用Server的ListenAndServe()
调用net.Listen(“tcp”, addr)监听端口
启动一个for循环,在循环体中Accept请求
对每个请求实例化一个Conn,并开启一个goroutine为这个请求进行服务 go c.serve()
读取每个请求的内容w, err := c.readRequest()
判断handler是否为空,如果没有设置handler,handler就设置为DefaultServeMux
调用handler的ServeHttp
根据request选择handler,并且进入到这个handler的ServeHTTP中 mux.handler®.ServeHTTP(w,r)

选择handler:

判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
如果有路由满足,则调用这个路由的ServeHttp方法
如果没有路由满足,则调用NotFoundHandleer的ServeHttp方法

参考

[1] https://www.w3cschool.cn/yqbmht/bpmh8ozt.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值