Go HTTP模块处理流程简析
Go语言提供完善的net/http包,用户使用起来非常方便简单,只需几行代码就可以搭建一个简易的Web服务,可以对Web路由、静态文件、cookie等数据进行操作。
一个使用http包建立的Web服务
package main
import (
"fmt"
"log"
"net/http"
)
func RequestHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", RequestHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe:", err)
}
}
核心代码代码如下,下面对它们进行解析
// 用户自定义HTTP路由句柄
func RequestHandler(w http.ResponseWriter, r *http.Request)
// Multiplexer路由注册
http.HandleFunc("/", RequestHandler)
// 服务器监听本地地址,并循环处理HTTP请求
http.ListenAndServe(":8080", nil)
创建ServeMux路由handler
func RequestHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
这个是用户自定义的函数,结合第二行代码看,作为http.HandleFunc函数调用的第二个参数,因为http.HandleFunc第二个参数类型是func(ResponseWriter, *Request),所以本函数定义带有(ResponseWriter, *Request)参数列表,无返回值。第一个参数http.ResponseWriter是一个接口,用于对一次HTTP请求做响应处理,第二个参数http.Request是一次HTTP请求实例,后面再做详细分析。本函数本质上是一个http请求处理的回调函数,是由http包框架开放出,用户可以自定义处理函数。
ServeMux路由表结构
Go默认路由表结构为ServerMux
// ServeMux结构是一个HTTP请求多路复用器(Multiplexer)
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // ServeMux路由表
hosts bool // whether any patterns contain hostnames
}
// 路由表项
type muxEntry struct {
h Handler // 请求处理handler
pattern string // 请求路径
}
ServeMux路由注册过程过程
// 往系统默认的ServerMux中添加一条路由
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// 系统默认的Multiplexer:ServeMux
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
// 将用户定义的handler方法,强转为HandlerFunc类型,即实现Handler接口,
// 后面直接通过f.ServeHTTP(w, r)实现对用户注册的handler的调用,f类型为HandlerFunc
mux.Handle(pattern, HandlerFunc(handler))
}
// HandlerFunc类型
type HandlerFunc func(ResponseWriter, *Request)
// HandlerFunc类型实现了Handler接口中ServeHTTP方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
// 往ServeMux路由表(mux.m,map结构)中添加路由
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)
}
// 向路由表中新增路由表项
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
// 如果不是以'/'开头,则从host开始匹配
if pattern[0] != '/' {
mux.hosts = true
}
}
ServeMux路由匹配过程
// 见net/http/server.go中func (c *conn) serve(ctx context.Context) 1847行(Go SDK 1.11.5)
// 这里就完成对用户注册的路由handler调用
serverHandler{c.server}.ServeHTTP(w, w.req)
// serverHandler类型
type serverHandler struct {
srv *Server
}
// serverHandler也实现了Handler接口中ServeHTTP方法
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// http.ListenAndServe(":8080", nil)中第二个参数为nil,表示使用系统默认Multiplexer:DefaultServeMux,
// 在这里就启用DefaultServeMux,可以查看Server结构对Handler成员注释说明(handler to invoke, http.DefaultServeMux if nil)
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// 调用ServeMux实现Handler接口中ServeHTTP方法
handler.ServeHTTP(rw, req)
}
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
}
// 通过Request找到路由
h, _ := mux.Handler(r)
// 到这里就调用用户注册的路由Handler
h.ServeHTTP(w, r)
}
// 进一步跟踪mux.Handler(r)
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// ...
return mux.handler(host, r.URL.Path)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// 通过路径匹配路由
if mux.hosts {
h, pattguizeern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match.
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
// 匹配时选择匹配度最高的路由(长匹配优先于短匹配)
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
// 如果pattern不是以'/'结尾,则需完全匹配
// 如果pattern是以'/'结尾,并且pattern(/tree/)是path(/tree/xxx)的前缀子串,
// 则path(/tree/xxx)属于pattern(/tree/*)路由子集下,后面通过match中for语句,基于长匹配优先于短匹配原则,完全匹配路由
func pathMatch(pattern, path string) bool {
if len(pattern) == 0 {
// should not happen
return false
}
n := len(pattern)
if pattern[n-1] != '/' {
return pattern == path
}
return len(path) >= n && path[0:n] == pattern
}
ServeMux路由匹配规则
对于每个HTTP请求,ServeHTTP会对URL进行模式(pattern)匹配,然后调用注册在此pattern下的handler来处理当前请求。
1、如果pattern以'/'开头,表示匹配URL的路径部分,如果不是以'/'开头,表示从host开始匹配;
2、模式匹配时,以匹配度最高为原则(长匹配优先于短匹配);
3、如果pattern(/tree/)以'/'结尾,将会对不带'/'的URL(/tree)进行301重定向到'/tree/'上,除非单独为'/tree'模式注册handler;
4、如果pattern(/tree)注册了handler,当请求的路径为'/tree/'时,则无法匹配该模式;
**实现Handler接口**
Handler结构定义如下
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
net/http/server.go中实现Handler接口有以下方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)}
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request)
func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request)
func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request)
func (h initNPNRequest) ServeHTTP(rw ResponseWriter, req *Request)
Go HTTP模块处理流程简析的更多相关文章
- zxing二维码扫描的流程简析(Android版)
目前市面上二维码的扫描似乎用开源google的zxing比较多,接下去以2.2版本做一个简析吧,勿喷... 下载下来后定位两个文件夹,core和android,core是一些核心的库,android是 ...
- OpenStack Cinder源代码流程简析
版权声明:本博客欢迎转载,转载时请以超链接形式标明文章原始出处!谢谢! 博客地址:http://blog.csdn.net/i_chips 一.概况 OpenStack的各个模块都有对应的client ...
- React Native 启动流程简析
导读:本文以 react-native-cli 创建的示例工程(安卓部分)为例,分析 React Native 的启动流程. 工程创建步骤可以参考官网.本文所分析 React Native 版本为 v ...
- LinkedHashMap结构get和put源码流程简析及LRU应用
原理这篇讲得比较透彻Java集合之LinkedHashMap. 本文属于源码阅读笔记,因put,get调用逻辑及链表维护逻辑复杂(至少网上其它文章的逻辑描述及配图,我都没看明白LinkedHashMa ...
- jquery选择器的实现流程简析及提高性能建议!
当我们洋洋得意的使用jquery强大的选择器功能时有没有在意过jquery的选择性能问题呢,其实要想高效的使用jquery选择器,了解其实现流程是很有必要的,那么这篇文章我就简单的讲讲其实现流程,相信 ...
- Tomcat启动流程简析
Tomcat是一款我们平时开发过程中最常用到的Servlet容器.本系列博客会记录Tomcat的整体架构.主要组件.IO线程模型.请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识. ...
- 【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析
前言 学习类加载必然离开不了sun.misc.Launcher这个类和Class.forName()这个方法. 分析ClassLoader.getSystemClassLoader()这个流程可以明白 ...
- HTTPS及流程简析
[序] 在我们在浏览某些网站的时候,有时候浏览器提示需要安装根证书,可是为什么浏览器会提示呢?估计一部分人想也没想就直接安装了,不求甚解不好吗? 那么什么是根证书呢?在大概的囫囵吞枣式的百度之后知道了 ...
- Postfix 发送邮件流程简析
PostFix接受和转发邮件的说明 来源ip符合inet_interfaces,收件人域符合mydestination, Postfix将接收到本地. 来源ip符合inet_interfaces, ...
随机推荐
- MachineLearningPreface
机器学习(包括监督学习, 无监督学习, 半监督学习与强化学习) 监督学习(包括分类与线性回归) 分类(标签的值为散列的"yes"或者"no", "go ...
- 二维码项目实例为二维码添加logo
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- 软件测试技术第三次作业——打印质数printPrimes()
作业一.Use the following method printPrimes() for questions a–d. printPrimes: /** ********************* ...
- maven课程 项目管理利器-maven 3-9 maven依赖冲突 4星
依赖相同的jar包,版本不一致,优先选择哪个版本呢? 1 路径短的优先 如A依赖B,B依赖C,C依赖commons.jar 1.0 A依赖D,D依赖commons.jar 2.0 那么优先解析2.0, ...
- Python异常处理及元类
一.异常处理 异常是错误发生的信号,一旦程序出错就会产生一个异常,如果该异常没有被应用程序处理,那么该异常就会跑出来,程序的执行也随之终止,也就是说异常就是一个事件,该事件会在程序执行过程中发生,影响 ...
- python if else while for
1 getpass模块 设置密码不显示明文 用户名和密码输入程序: import getpass username = input("username:") password = ...
- Azure进阶攻略 | VS2015和Azure,想要在一起其实很容易
下雨天,巧克力和音乐很配…… 大冬天,男神和捧在手里的奶茶很配…… 「驴牌」的包包,和女神的全部衣服都配…… 对于「王首富」,容易实现的小目标和一个亿是绝配…… …… 醒醒吧!!这些事情和每天只会写代 ...
- ASP.NET设置母版页
母版页允许开发人员创建具有指定的可编辑区域的站点级模板.随后,此模板可应用到网站中的 ASP.NET 页面上.这些 ASP.NET 页面只需为母版页中指定的可编辑区域提供相应内容 – 在使用母版页的所 ...
- 自定义 Firefox TLS支持版本s
如果目前正在运行火狐26,你可能已经注意到,浏览器仅支持SSL 3.0和TLS 1.0,默认不开启TLS 1.1或TLS 1.2.另外我们知道Firefox 27 已经实现了对TLS 1.2的支持. ...
- 如何C#操作SQLite数据库
或许有人之前在java开发中使用过SQLite,对它有些印象.在用Winform或Wpf开发小应用程序时,发现用SQLite数据库也是不错的.就像一个会员管理软件,开发完毕后,可以省去想sqlserv ...