15

粗读web框架之go gin和python django

 4 years ago
source link: https://segmentfault.com/a/1190000023117316
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

为什么引入web框架

web应用的本质

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

涉及的问题

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

web框架工作流程

aeE3Ibf.png!web

中间件

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

N3UBVrv.png!web

===

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

Recommend

  • 59
    • studygolang.com 6 years ago
    • Cache

    web框架Gin使用

    介绍 为简化使用流程,减少开发时间,降低项目开发成本,搭建了一套基于 Gin 的web骨架。组件包括但不局限于路由、控制器、Orm、session、cookie、mysql连接、认证中间件等。 项目地址:https://g...

  • 34
    • studygolang.com 6 years ago
    • Cache

    Gin框架介绍

    准备工作 确认本地环境的 $GOPATH Windows 下使用 echo %GOPATH% Linux 下使用 ...

  • 28
    • www.tuicool.com 5 years ago
    • Cache

    Go框架解析-gin

    前言 今天是我golang框架阅读系列第三篇文章,今天我们主要...

  • 34
    • tigerb.cn 5 years ago
    • Cache

    Go框架解析-gin - TIGERB

    Go框架解析:gin 2019-07-06

  • 4
    • studygolang.com 3 years ago
    • Cache

    golang框架-web框架之gin

    gin介绍gin是一个 Web应用框架,拥有良好的性能和简单明了的接口。同时支持中间件,类型绑定等实用功能。为什么要用gin在实际开发中,很少会直接实用http.Server。而自己搭建框架有一定成本,同时没有经过系统的校验,容易出现问题。...

  • 25
    • studygolang.com 3 years ago
    • Cache

    Go Web开发进阶实战(gin框架)

    Go Web开发进阶实战(gin框架) 喜欢运动m · 大约18小时之前 · 145 次点击 · 预计阅读时间不到 1 分钟 · 大约8小时之前 开始浏览     ...

  • 11
    • www.80shihua.com 3 years ago
    • Cache

    golang web框架Gin入门1-环境搭建

    golang web框架Gin入门1-环境搭建 作者: dreamfly 分类: go,个人博客 发布时间...

  • 7
    • rivenzoo.github.io 2 years ago
    • Cache

    Gin web框架

    gin是一个基于Golang标准库net/http封装的HTTPweb框架。它提供了方便的路由注册功能,支持捕获URL参数,提供了中间件机制来串连请求处理流程,提供了方便的数据获取和输出方法,所有这些功能提升了开发web服务的效率。本文将从以下六...

  • 2

    Go/Gin Gower 一款 Web 快速启动框架 falling-ts · 9天之前 · 1565 次...

  • 5

    Python Web 框架三巨头:Flask、Django 和 FastAPI 2023-10-09 18:17:52 Python 作为主要语言的受访者大多将其用于 Web 开发 (23%)。作为辅助语言,Python 最常用于数据分析 (16%) 和 DevOps (14%),Web 开发排在...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK