粗读web框架之go-gin和python-django

5次阅读

共计 20811 个字符,预计需要花费 53 分钟才能阅读完成。

为什么引入 web 框架

   web 应用的本质

  • 浏览器发送一个 HTTP 请求;
  • 服务器收到请求,生成一个 HTML 文档;
  • 服务器把 HTML 文档作为 HTTP 响应的 Body 发送给浏览器;
  • 浏览器收到 HTTP 响应,从 HTTP Body 取出 HTML 文档并显示;

   涉及的问题  

  • 解析 http 请求
  • 找到对应的处理函数
  • 生成并发送 http 响应
    • *

web 框架工作流程


中间件

  • 中间件是请求或者应用开始和结束时注入代码的机制
  • 常见的 web 中间件:鉴权、打印 log、session、统计信息、处理数据库连接  等等

===


golang http 服务端编程

  • golang 的 net/http 包提供了 http 编程的相关接口,封装了内部 TCP 连接和报文解析的复杂琐碎的细节 
    go c.serve(ctx) 最终会启动 goroutine 处理请求
  • 使用者只需要和 http.request 和 http.ResponseWriter 两个对象交互就行 (也就是一个 struct 实现 net/http 包中的 Handler interface 中的 ServeHttp 方法)
//E:\Go\src\net\http\server.go +82 
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}

// 纯 net.http 包的 server 方法
package main

import (
    "io"
    "net/http"
)

type helloHandler struct{}

func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello, world!"))
}

func main() {http.Handle("/", &helloHandler{})
    http.ListenAndServe(":12345", nil)
}

//////////////////////////////////////////////////////////////////

import ("net/http")

type Handle struct{}

func (h Handle) ServeHTTP(response http.ResponseWriter, request *http.Request) {
    switch request.URL.Path {
    case "/info":
        response.Write([]byte("info"))
    default:

    }
}

func main() {http.ListenAndServe(":8888", Handle{})
}

  • net/http 的另外一个重要的概念,ServeMux(多路传输):ServeMux 可以注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理
  • // 下面看一个带路由的 http server
    package main
    
    import (
        "io"
        "net/http"
    )
    
    func helloHandler(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "Hello, world!\n")
    }
    
    func echoHandler(w http.ResponseWriter, r *http.Request) {io.WriteString(w, r.URL.Path)
    }
    
    func main() {mux := http.NewServeMux()
        mux.HandleFunc("/hello", helloHandler)
        mux.HandleFunc("/", echoHandler)
    
        http.ListenAndServe(":12345", mux)
    }
    
    // 其实 ServeMux 也实现了 Handler 的 ServeHTTP 方法所以也是 Handler 接口
    //E:\Go\src\net\http\server.go 2382
    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)
    }
    
    
    // E:\Go\src\net\http\server.go +2219 可以看到 net/http 包中的 基于 map 路由查找
    // Find a handler on a handler map given a path string.
    // Most-specific (longest) pattern wins.
    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
    }

golang web 框架 GIN golang gin web 框架

//gin 框架初始化的流程
1. 初始化 engine 
2. 注册中间件
3. 注册路由

// 响应流程
1. 路由,找到 handle
2. 将请求和响应用 Context 包装起来供业务代码使用
3. 依次调用中间件和处理函数
4. 输出结果

//gin 里面最重要的两个数据结构
//1.Context 在中间件中传递本次请求的各种数据、管理流程,进行响应
//2.Engine gin 引擎,是框架的实例,它包含多路复用器,中间件和配置设置




// 下面看下 open-falcon-api 中的实际应用
//open-falcon-api 里面注册路由和中间件
//E:\go_path\src\github.com\open-falcon\falcon-plus\modules\api\app\controller\graph\graph_routes.go
// 首先注册 /api/v1/ 开头的 path 的路由组
// 然后 Use 一个 auth 的中间件,作用是检查 token
// 这个组后面的所有 path 都使用这个中间件 
// 也就是访问 /graph/endpoint 时会过 3 个中间件 (log recovery  auth)+ 一个 EndpointRegexpQuery 处理函数
// 
func Routes(r *gin.Engine) {db = config.Con()
    authapi := r.Group("/api/v1")
    authapi.Use(utils.AuthSessionMidd)
    authapi.GET("/graph/endpointobj", EndpointObjGet)
    authapi.GET("/graph/endpoint", EndpointRegexpQuery)
    authapi.GET("/graph/endpoint_counter", EndpointCounterRegexpQuery)
    authapi.POST("/graph/history", QueryGraphDrawData)
    authapi.POST("/graph/lastpoint", QueryGraphLastPoint)
    authapi.POST("/graph/info", QueryGraphItemPosition)
    authapi.DELETE("/graph/endpoint", DeleteGraphEndpoint)
    authapi.DELETE("/graph/counter", DeleteGraphCounter)

    grfanaapi := r.Group("/api")
    grfanaapi.GET("/v1/grafana", GrafanaMainQuery)
    grfanaapi.GET("/v1/grafana/metrics/find", GrafanaMainQuery)
    grfanaapi.POST("/v1/grafana/render", GrafanaRender)
    grfanaapi.GET("/v1/grafana/render", GrafanaRender)

}


func AuthSessionMidd(c *gin.Context) {auth, err := h.SessionChecking(c)
    if !viper.GetBool("skip_auth") {
        if err != nil || auth != true {log.Debugf("error: %v, auth: %v", err, auth)
            c.Set("auth", auth)
            h.JSONR(c, http.StatusUnauthorized, err)
            c.Abort()
            return
        }
    }
    c.Set("auth", auth)
}


// E:\go_path\src\github.com\open-falcon\falcon-plus\vendor\github.com\gin-gonic\gin\context.go +715 最后会调用这里的 Render 方法
// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) {c.Status(code)

    if !bodyAllowedForStatus(code) {r.WriteContentType(c.Writer)
        c.Writer.WriteHeaderNow()
        return
    }

    if err := r.Render(c.Writer); err != nil {panic(err)
    }
}

// 可以看到这里的 bind 是在 gin 在解析请求 payload 是否和函数中要求的 struct 一致
// E:\go_path\src\github.com\open-falcon\falcon-plus\vendor\github.com\gin-gonic\gin\context.go +504
// bind 会根据请求中的 Content-Type header 判断是 json 还是 xml
// 并且根据 struct 中的 tag 通过反射解析 payload

// Bind checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used:
//     "application/json" --> JSON binding
//     "application/xml"  --> XML binding
// otherwise --> returns an error.
// It parses the request's body as JSON if Content-Type =="application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj interface{}) error {b := binding.Default(c.Request.Method, c.ContentType())
    return c.MustBindWith(obj, b)
}


type APIEndpointObjGetInputs struct {Endpoints []string `json:"endpoints" form:"endpoints"`
    Deadline  int64    `json:"deadline" form:"deadline"`
}


func EndpointObjGet(c *gin.Context) {
    inputs := APIEndpointObjGetInputs{Deadline: 0,}
    if err := c.Bind(&inputs); err != nil {h.JSONR(c, badstatus, err)
        return
    }
    if len(inputs.Endpoints) == 0 {h.JSONR(c, http.StatusBadRequest, "endpoints missing")
        return
    }

    var result []m.Endpoint = []m.Endpoint{}
    dt := db.Graph.Table("endpoint").
        Where("endpoint in (?) and ts >= ?", inputs.Endpoints, inputs.Deadline).
        Scan(&result)
    if dt.Error != nil {h.JSONR(c, http.StatusBadRequest, dt.Error)
        return
    }

    endpoints := []map[string]interface{}{}
    for _, r := range result {endpoints = append(endpoints, map[string]interface{}{"id": r.ID, "endpoint": r.Endpoint, "ts": r.Ts})
    }

    h.JSONR(c, endpoints)
}

//E:\go_path\src\github.com\open-falcon\falcon-plus\modules\api\main.go  +78
// 初始化 gin
routes := gin.Default()

//E:\go_path\src\github.com\open-falcon\falcon-plus\vendor\github.com\gin-gonic\gin\gin.go +148
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

//E:\go_path\src\github.com\open-falcon\falcon-plus\vendor\github.com\gin-gonic\gin\gin.go +119 
// new 方法 返回一个不带中间件的 单例 engine,并且把 context 放在池中
func New() *Engine {debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {return engine.allocateContext()
    }
    return engine
}


//E:\go_path\src\github.com\open-falcon\falcon-plus\modules\api\app\controller\routes.go
//r.Run(port) 最后调用的是  net.http.ListenAndServe
func (engine *Engine) Run(addr ...string) (err error) {defer func() {debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}




//E:\Go\src\net\http\server.go +2686
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {handler = DefaultServeMux}
    if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

//E:\go_path\src\github.com\open-falcon\falcon-plus\vendor\github.com\gin-gonic\gin\gin.go +321
// 我们可以看到 在 gin 中实现了 ServeHTTP 方法  net.http.Handler

// ServeHTTP conforms to the http.Handler interface. 
// 这里使用 sync.pool cache context 数据结构,避免频繁 GC,每次使用都初始化
// 一个 struct 实现了 interface 中的方法 就说明这个 struct 是这个类型
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)
    
    engine.pool.Put(c)
}



// gin 里面处理请求的核心方法
// 根据一些配置去 压缩前缀树 radix.tree 中找到对应的 handleChain 然后执行
// 注意这句 handlers, params, tsr := root.getValue(path, c.Params, unescape)
// 路由查找的过程是 从基数树的根节点一直匹配到和请求一致的节点找到对应的 handlerchain
// 注册路由 E:\go_path\src\github.com\open-falcon\falcon-plus\vendor\github.com\gin-gonic\gin\gin.go +243 
// 从下面的 addRoute 方法中可以看到 gin 为每个 HttpMethod  GET POST PUT DELETE 都注册了一颗 tree
// 并且有 priority 即最长的路径优先匹配
/*
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {assert1(path[0] == '/', "path must begin with'/'")
    assert1(method != "","HTTP method can not be empty")
    assert1(len(handlers) > 0, "there must be at least one handler")

    debugPrintRoute(method, path, handlers)
    root := engine.trees.get(method)
    if root == nil {root = new(node)
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}
*/


func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    path := c.Request.URL.Path
    unescape := false
    if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
        path = c.Request.URL.RawPath
        unescape = engine.UnescapePathValues
    }

    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}
        root := t[i].root
        // Find route in tree
        handlers, params, tsr := root.getValue(path, c.Params, unescape)
        if handlers != nil {
            c.handlers = handlers
            c.Params = params
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
        if httpMethod != "CONNECT" && path != "/" {
            if tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)
                return
            }
            if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}
        }
        break
    }

    if engine.HandleMethodNotAllowed {
        for _, tree := range engine.trees {
            if tree.method == httpMethod {continue}
            if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
                c.handlers = engine.allNoMethod
                serveError(c, http.StatusMethodNotAllowed, default405Body)
                return
            }
        }
    }
    c.handlers = engine.allNoRoute
    serveError(c, http.StatusNotFound, default404Body)
}



python django(django 框架复杂,先简单看一下)

# 入口文件 
def execute_from_command_line(argv=None):
    """A simple method that runs a ManagementUtility."""
    utility = ManagementUtility(argv)
    utility.execute()


def execute(self):
        """
        Given the command-line arguments, figure out which subcommand is being
        run, create a parser appropriate to that command, and run it.
        """
        try:
            subcommand = self.argv[1]
        except IndexError:
            subcommand = 'help'  # Display help if no arguments were given.

        # Preprocess options to extract --settings and --pythonpath.
        # These options could affect the commands that are available, so they
        # must be processed early.
        parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
        parser.add_argument('--settings')
        parser.add_argument('--pythonpath')
        parser.add_argument('args', nargs='*')  # catch-all
        try:
            options, args = parser.parse_known_args(self.argv[2:])
            handle_default_options(options)
        except CommandError:
            pass  # Ignore any option errors at this point.

        try:
            settings.INSTALLED_APPS
        except ImproperlyConfigured as exc:
            self.settings_exception = exc
        except ImportError as exc:
            self.settings_exception = exc

        if settings.configured:
            # Start the auto-reloading dev server even if the code is broken.
            # The hardcoded condition is a code smell but we can't rely on a
            # flag on the command class because we haven't located it yet.
            if subcommand == 'runserver' and '--noreload' not in self.argv:
                try:
                    autoreload.check_errors(django.setup)()
                except Exception:
                    # The exception will be raised later in the child process
                    # started by the autoreloader. Pretend it didn't happen by
                    # loading an empty list of applications.
                    apps.all_models = defaultdict(OrderedDict)
                    apps.app_configs = OrderedDict()
                    apps.apps_ready = apps.models_ready = apps.ready = True

                    # Remove options not compatible with the built-in runserver
                    # (e.g. options for the contrib.staticfiles' runserver).
                    # Changes here require manually testing as described in
                    # #27522.
                    _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
                    _options, _args = _parser.parse_known_args(self.argv[2:])
                    for _arg in _args:
                        self.argv.remove(_arg)

            # In all other cases, django.setup() is required to succeed.
            else:
                django.setup()

        self.autocomplete()

        if subcommand == 'help':
            if '--commands' in args:
                sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
            elif not options.args:
                sys.stdout.write(self.main_help_text() + '\n')
            else:
                self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
        # Special-cases: We want 'django-admin --version' and
        # 'django-admin --help' to work, for backwards compatibility.
        elif subcommand == 'version' or self.argv[1:] == ['--version']:
            sys.stdout.write(django.get_version() + '\n')
        elif self.argv[1:] in (['--help'], ['-h']):
            sys.stdout.write(self.main_help_text() + '\n')
        else:
            self.fetch_command(subcommand).run_from_argv(self.argv)

#C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\django\core\management\__init__.py +301
'''
#1.fetch_command 最终会调用 C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\django\core\management\__init__.py 的 find_commands()
最终会找到 django\core\management\commands 下面的所有的 command
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    flush
    inspectdb
    loaddata
    makemessages
    makemigrations
    migrate
    runserver
    sendtestemail
    shell
    showmigrations
    sqlflush
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver

2. run_from_argv 调 execute() 再调用 handle()
'''

            
# 最常用的 runserver
#C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\django\core\management\commands\runserver.py + 
# execute()-->handle()-->run()-->inner_run()-->get_wsgi_application() #WSGIHandler 在这里加载中间件
# C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\django\core\handlers\wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = [*response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
        ]
        start_response(status, response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response


    def inner_run(self, *args, **options):
        # If an exception was silenced in ManagementUtility.execute in order
        # to be raised in the child process, raise it now.
        autoreload.raise_last_exception()

        threading = options['use_threading']
        # 'shutdown_message' is a stealth option.
        shutdown_message = options.get('shutdown_message', '')
        quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'

        self.stdout.write("Performing system checks...\n\n")
        self.check(display_num_errors=True)
        # Need to check migrations here, so can't use the
        # requires_migrations_check attribute.
        self.check_migrations()
        now = datetime.now().strftime('%B %d, %Y - %X')
        self.stdout.write(now)
        self.stdout.write(("Django version %(version)s, using settings %(settings)r\n"
            "Starting development server at %(protocol)s://%(addr)s:%(port)s/\n"
            "Quit the server with %(quit_command)s.\n"
        ) % {"version": self.get_version(),
            "settings": settings.SETTINGS_MODULE,
            "protocol": self.protocol,
            "addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
            "port": self.port,
            "quit_command": quit_command,
        })

        try:
            handler = self.get_handler(*args, **options)
            run(self.addr, int(self.port), handler,
                ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
        except socket.error as e:
            # Use helpful error messages instead of ugly tracebacks.
            ERRORS = {
                errno.EACCES: "You don't have permission to access that port.",
                errno.EADDRINUSE: "That port is already in use.",
                errno.EADDRNOTAVAIL: "That IP address can't be assigned to.",
            }
            try:
                error_text = ERRORS[e.errno]
            except KeyError:
                error_text = e
            self.stderr.write("Error: %s" % error_text)
            # Need to use an OS exit because sys.exit doesn't work in a thread
            os._exit(1)
        except KeyboardInterrupt:
            if shutdown_message:
                self.stdout.write(shutdown_message)
            sys.exit(0)
            

# C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\django\core\handlers\base.py
class BaseHandler:
    _view_middleware = None
    _template_response_middleware = None
    _exception_middleware = None
    _middleware_chain = None

    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE.

        Must be called after the environment is fixed (see __call__ in subclasses).
        """
        self._view_middleware = []
        self._template_response_middleware = []
        self._exception_middleware = []

        handler = convert_exception_to_response(self._get_response)
        for middleware_path in reversed(settings.MIDDLEWARE):
            middleware = import_string(middleware_path)
            try:
                mw_instance = middleware(handler)
            except MiddlewareNotUsed as exc:
                if settings.DEBUG:
                    if str(exc):
                        logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                    else:
                        logger.debug('MiddlewareNotUsed: %r', middleware_path)
                continue

            if mw_instance is None:
                raise ImproperlyConfigured('Middleware factory %s returned None.' % middleware_path)

            if hasattr(mw_instance, 'process_view'):
                self._view_middleware.insert(0, mw_instance.process_view)
            if hasattr(mw_instance, 'process_template_response'):
                self._template_response_middleware.append(mw_instance.process_template_response)
            if hasattr(mw_instance, 'process_exception'):
                self._exception_middleware.append(mw_instance.process_exception)

            handler = convert_exception_to_response(mw_instance)

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler
        
#get_handler 函数最终会返回一个 WSGIHandler 的实例。WSGIHandler 类只实现了 def __call__(self, environ, start_response) , 使它本身能够成为 WSGI 中的应用程序, 并且实现 __call__ 能让类的行为跟函数一样。class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = [*response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
        ]
        start_response(status, response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response
        
# C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\socketserver.py + 215
    def serve_forever(self, poll_interval=0.5):
        """处理一个 http 请求直到关闭"""
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    if ready:
                        #如果 fd 可用调用处理方法
                        self._handle_request_noblock()

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()



    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that selector.select() has returned that the socket is
        readable before this function was called, so there should be no risk of
        blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                #这里是真正处理请求的地方
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)
            
    
    def process_request(self, request, client_address):
        """Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        """
        self.finish_request(request, client_address)
        self.shutdown_request(request)
    

    #finish_request 最后调用这个 BaseRequestHandler
    class BaseRequestHandler:
    ''''''

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()
            
            
    # C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\django\core\servers\basehttp.py +156        
    def handle(self):
    '''
    这里对请求长度做限制
    parse_request 对 http 解包
    '''

    self.raw_requestline = self.rfile.readline(65537)
    if len(self.raw_requestline) > 65536:
        self.requestline = ''self.request_version =''
        self.command = ''
        self.send_error(414)
        return

    if not self.parse_request():  # An error code has been sent, just exit
        return

    handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
    handler.request_handler = self      # backpointer for logging
    handler.run(self.server.get_app())
    
    #get_app 返回之前装配的 WSGIAPP 最终
    class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = [*response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
        ]
        start_response(status, response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response

正文完
 0