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模块处理流程简析的更多相关文章

  1. zxing二维码扫描的流程简析(Android版)

    目前市面上二维码的扫描似乎用开源google的zxing比较多,接下去以2.2版本做一个简析吧,勿喷... 下载下来后定位两个文件夹,core和android,core是一些核心的库,android是 ...

  2. OpenStack Cinder源代码流程简析

    版权声明:本博客欢迎转载,转载时请以超链接形式标明文章原始出处!谢谢! 博客地址:http://blog.csdn.net/i_chips 一.概况 OpenStack的各个模块都有对应的client ...

  3. React Native 启动流程简析

    导读:本文以 react-native-cli 创建的示例工程(安卓部分)为例,分析 React Native 的启动流程. 工程创建步骤可以参考官网.本文所分析 React Native 版本为 v ...

  4. LinkedHashMap结构get和put源码流程简析及LRU应用

    原理这篇讲得比较透彻Java集合之LinkedHashMap. 本文属于源码阅读笔记,因put,get调用逻辑及链表维护逻辑复杂(至少网上其它文章的逻辑描述及配图,我都没看明白LinkedHashMa ...

  5. jquery选择器的实现流程简析及提高性能建议!

    当我们洋洋得意的使用jquery强大的选择器功能时有没有在意过jquery的选择性能问题呢,其实要想高效的使用jquery选择器,了解其实现流程是很有必要的,那么这篇文章我就简单的讲讲其实现流程,相信 ...

  6. Tomcat启动流程简析

    Tomcat是一款我们平时开发过程中最常用到的Servlet容器.本系列博客会记录Tomcat的整体架构.主要组件.IO线程模型.请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识. ...

  7. 【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析

    前言 学习类加载必然离开不了sun.misc.Launcher这个类和Class.forName()这个方法. 分析ClassLoader.getSystemClassLoader()这个流程可以明白 ...

  8. HTTPS及流程简析

    [序] 在我们在浏览某些网站的时候,有时候浏览器提示需要安装根证书,可是为什么浏览器会提示呢?估计一部分人想也没想就直接安装了,不求甚解不好吗? 那么什么是根证书呢?在大概的囫囵吞枣式的百度之后知道了 ...

  9. Postfix 发送邮件流程简析

      PostFix接受和转发邮件的说明 来源ip符合inet_interfaces,收件人域符合mydestination, Postfix将接收到本地. 来源ip符合inet_interfaces, ...

随机推荐

  1. HDU 5340——Three Palindromes——————【manacher处理回文串】

    Three Palindromes Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others ...

  2. VUE中给template组件加背景

    <template> <div class="index_background" > </div> </template> < ...

  3. struts2的常量

    常量名 常量值 说明 struts.i18n.encoding UTF-8 应用中使用的编码 struts.objectFactory.spring.autoWire name 和spring框架整合 ...

  4. mybatis SqlSession事务

    mybatis版本:3.4.6. mybatis默认的SqlSessionFactory是DefaultSqlSessionFactory,它openSession()的源码是: public Sql ...

  5. javaSE练习2——流程控制_2.2

    一.假设某员工今年的年薪是30000元,年薪的年增长率6%.编写一个Java应用程序计算该员工10年后的年薪,并统计未来10年(从今年算起)总收入. package com.test; public ...

  6. Redis入门--(一)简介NoSQL

    1.什么是NoSql? 2.为什么需要NoSQL? 互联网经历了1.0和2.0的发展: web1.0 是早期新浪,雅虎等只能浏览,不能交互: 传统关系型数据库在应付web2.0这种动态网站的时候力不从 ...

  7. HTML的行内元素与块级元素的区别?

    块级元素:独占一行,其宽度自动填满父元素的宽度,可以容纳行内元素和其他块级元素,可以设置margin和padding值. 行内元素:不会独占一行,与其他行内元素排成一行,直到其父元素拍不下,才会从新一 ...

  8. css3实现iPhone滑动解锁

    该效果的主要实现思路是给文字添加渐变的背景,然后对背景进行裁剪,按文字裁剪(目前只有webkit内核浏览器支持该属性),最后给背景添加动画,即改变背景的位置,背景动画效果如下(GIF录制时有卡顿,代码 ...

  9. Help for enable SSL 3.0 and disable TLS 1.0..

    https://support.mozilla.org/en-US/questions/967266 i cant find tab Encryption for enable SSL 3.0 and ...

  10. ring0 进程隐藏实现

    最近在学习内核编程,记录一下最近的学习笔记. 原理:将当前进程从eprocess结构的链表中删除 无法被! process 0 0 看见 #include "HideProcess.h&qu ...