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, ...
随机推荐
- HDU 5340——Three Palindromes——————【manacher处理回文串】
Three Palindromes Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others ...
- VUE中给template组件加背景
<template> <div class="index_background" > </div> </template> < ...
- struts2的常量
常量名 常量值 说明 struts.i18n.encoding UTF-8 应用中使用的编码 struts.objectFactory.spring.autoWire name 和spring框架整合 ...
- mybatis SqlSession事务
mybatis版本:3.4.6. mybatis默认的SqlSessionFactory是DefaultSqlSessionFactory,它openSession()的源码是: public Sql ...
- javaSE练习2——流程控制_2.2
一.假设某员工今年的年薪是30000元,年薪的年增长率6%.编写一个Java应用程序计算该员工10年后的年薪,并统计未来10年(从今年算起)总收入. package com.test; public ...
- Redis入门--(一)简介NoSQL
1.什么是NoSql? 2.为什么需要NoSQL? 互联网经历了1.0和2.0的发展: web1.0 是早期新浪,雅虎等只能浏览,不能交互: 传统关系型数据库在应付web2.0这种动态网站的时候力不从 ...
- HTML的行内元素与块级元素的区别?
块级元素:独占一行,其宽度自动填满父元素的宽度,可以容纳行内元素和其他块级元素,可以设置margin和padding值. 行内元素:不会独占一行,与其他行内元素排成一行,直到其父元素拍不下,才会从新一 ...
- css3实现iPhone滑动解锁
该效果的主要实现思路是给文字添加渐变的背景,然后对背景进行裁剪,按文字裁剪(目前只有webkit内核浏览器支持该属性),最后给背景添加动画,即改变背景的位置,背景动画效果如下(GIF录制时有卡顿,代码 ...
- 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 ...
- ring0 进程隐藏实现
最近在学习内核编程,记录一下最近的学习笔记. 原理:将当前进程从eprocess结构的链表中删除 无法被! process 0 0 看见 #include "HideProcess.h&qu ...