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, ...
随机推荐
- 划分用户故事(user-story)的原则
在敏捷开发过程中是通过用户故事来将需求具体化成可以进行迭代开发的一个个现实的可见的开发任务.因此在敏捷软件的开发过程中,用户故事的划分对于迭代和开发起着举足轻重的作用. 用户故事从其名字来看是站在用户 ...
- 浅析正则表达式模式匹配的 String 方法
在JavaScript代码中使用正则表达式进行模式匹配经常会用到String对象和RegExp对象的一些方法,例如replace.match.search等方法,以下是对一些方法使用的总结. Stri ...
- .NET通过PowerShell操作ExChange为用户开通邮箱教程
转:http://www.cnblogs.com/gongguo/archive/2012/03/12/2392049.html =================================== ...
- AutoResetEvent 2
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- Win7无法将图标(Chrome谷歌浏览器更新后无法锁定也适用)锁定到任务栏解决办法
“将程序锁定到任务栏”是Windows 7中的一个非常有用的功能,它比之前的快速启动栏要来得简洁.但是我用了一段时间之后,发现“锁定到任务栏”这一个选项消失了,对图标点右键找不到这个图标,直接把图标拖 ...
- Java中的阻塞队列-ConcurrentLinkedQueue
http://ifeve.com/concurrentlinkedqueue/ 1. 引言 在并发编程中我们有时候需要使用线程安全的队列.如果我们要实现一个线程安全的队列有两种实现方式一种是使用 ...
- Hibernate课程 初探一对多映射4-3 测试--信息查询
建立双向一对多关系,既可以由一方查询多方信息,同样可以由多方查询一方信息 demo: //查询学生所在班级 public static void showGidByStudent(){ Session ...
- if转switch
if($a=="a") { echo "a"; } elseif ($a == "b") { echo "b"; } e ...
- 4.JavaScript
1.简介 JavaScript 是一种轻量级的编程语言,是一种动态类型.弱类型.基于原型的脚本语言. JavaScript,通常缩写为JS,是一种高级的,解释执行的编程语言.JavaScript是一门 ...
- Struts2_默认Action
配置Struts2默认跳转的Action <package name="default" namespace="/" extends="stru ...