共计 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 |
正文完