debugging相关知识
-
生成dump文件的方法我们知道调试程序主要有两种方法一种是:live debugging (附加进程 使进程hang住) 生产环境最好不要live debugging 一种是:post-mortem debugging or reading dump files (生成dump文件然后进行分析) 现在介绍一下如何生成dump文件,以及各种方法的差异第一步:确定SQLSERVER的进程ID由于我的机器安装了四个SQLSERVER实例,所以看到会有四个进程方法1:在cmd窗口输入下面命令tasklist | find /i "sqlservr" 方法2:打开任务管理进行查看 方法3:在SSMS里执行下面sql语句SELECT SERVERPROPERTY('PROCESSID') AS sqlpid 方法4:从SQL errorlog里获取进程IDEXEC [sys].
-
[C#]Hosting Process (vshost.exe)写在前面最近在群里,有朋友问起这个vshost.exe进程到底是什么?当时确实不知道是个什么东东,给人的感觉是,经常看到它,就是在启动一个项目的时候,经常看到它,就是没细研究它是啥玩意儿。既然遇到了,就不能放过,就要研究个一二。vshost.exe通过名字Hosting Process我们可以翻译为:宿主进程。The hosting process is a feature in Visual Studio 2005 that improves debugging performance, enables partial trust debugging, and enables design time expression evaluation. The hosting process files contain vshost in the file name and are placed in the output folder of your project. For more information,
-
20个Chrome DevTools调试技巧小编推荐: Fundebug提供JS、微信小程序、微信小游戏,Node.js和Java错误监控。真的是一个很好用的错误监控服务,众多大佬公司都在使用。 译者按: Chrome DevTools很强大,甚至可以替代IDE了! 原文: Art of debugging with Chrome DevTools 译者: Fundebug 为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。 谷歌开发者工具提供了一系列的功能来帮助开发者高效Debug网页应用,让他们可以更快地查找和修复bug。在谷歌的开发者工具中,有非常
-
FusionCharts常见问题及解决方法 在上文中,我们介绍了FusionCharts常见问题(FAQ)的解决方法,本文将一同讨论FusionCharts使用者面临的一些复杂问题的解决方法。>>>下载最新版FusionCharts 如何启用JavaScript调试模式?要启用JavaScript调试模式,你将需要写下面的几行代码:FusionCharts.debugMode.enabled(true);此外,你还需要指定"debugMode" 输出的位置。如果你还想在浏览器的JavaScript控制台中查看错误,你需要写入一下几行代码:FusionCharts.debugMode.outputTo( function() { console.log(arguments); } );注意:根据浏览器的不同,你可能还需要启用“JavaScript控制台”。>>>详情请参阅Debugging your charts > Using JavaScript page。Fusi
debugging相关课程
debugging相关教程
- 1. 调试程序 在编辑器中打开所需的 Python 脚本,或在"Project"工具窗口中选择文件。从上下文菜单中选择 “debug 脚本名”。Python 脚本的调试会话将从默认的临时运行/调试配置开始,如果你已经配置永久的运行/调试配置为当前脚本,此时菜单显示 “debug 配置名”。Tips: 在前面章节已经讲了如何添加运行/调试配置,在调试脚本前,可以增加新的配置。除此以外,在前面章节运行程序的方法入口也同样适用于调试程序,在菜单中都会找到对应的调试选项。调试会话启动后,程序会在击中的第一个断点处挂起,PyCharm会自动打开Debug 工具窗口。会包括Debugger 与 Console 选项卡。在调试过程中, 可以通过工具栏按钮暂停与恢复调试。对应的菜单 暂停: Run -> Debugging Actions -> Pause Program 恢复 Run -![>](//img1.sycdn.imooc.com//wiki/5f1d2d4009ffeec115260808.jpg) Debugging Actions -> Resume ProgramTips: 如果对断点执行的静音操作(mute breakpoints), 所有断点图标将变为灰色,如下图所示。在此种状态调试程序,相当于没有断点直接运行了程序。如果要恢复断点,再点击一下静音断点的按钮即可。当执行到达断点或手动挂起程序时,可以通过分析栈帧来检查应用程序。所有当前活动的帧都显示在Debugger 工具窗口的"Frames"窗格中。这种场景多用于多线程的情况。(栈帧就是一个函数执行的环境。实际上可以简单理解为:栈帧就是存储在用户栈上的每一次函数调用涉及的相关信息的记录单元。)
- 1. Scrapy 的 <code>settings.py</code> 配置 从前面的学习中我们知道,settings.py 是 Scrapy 使用 startproject 命令生成的,这里的配置会默认覆盖 Scrapy 内置的配置项,这些默认的配置项都位于 Scrapy 的 scrapy/settings/default_settings.py 中:Scrapy的默认配置文件我们来看看 default_settings.py 中的一些默认配置项。AJAXCRAWL_ENABLED:通用爬取经常会抓取大量的 index 页面;AjaxCrawlMiddleware 能帮助我们正确地爬取,AJAXCRAWL_ENABLED 配置正是开启该中间件的开关。由于有些性能问题,且对于特定爬虫没有什么意义,该中间默认关闭;自动限速扩展 (AutoThrottle):这类配置主要是以 Scrapy 爬虫以及正在抓取网站的负载来自动优化爬取速度。它能自动调整 Scrapy 达到最佳的爬取速度,使用者无需自己设置下载延迟,只要设置好最大并发请求数即可。来看看有关该扩展的配置项:AUTOTHROTTLE_ENABLED = False # 默认关闭 AUTOTHROTTLE_DEBUG = False # 关闭调试 AUTOTHROTTLE_MAX_DELAY = 60.0 # 最高下载延迟 AUTOTHROTTLE_START_DELAY = 5.0 # 初始化下载延迟 AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Scrapy 同时请求目标网站的平均请求数下面四个配置用于设置爬虫自动关闭条件:CLOSESPIDER_TIMEOUT:一个整数值,单位为秒。如果一个 spider 在指定的秒数后仍在运行, 它将以 closespider_timeout 的原因被自动关闭。 如果值设置为0 (或者没有设置),spiders 不会因为超时而关闭;CLOSESPIDER_ITEMCOUNT:一个整数值,指定条目的个数。如果 spider 爬取条目数超过了设置的值, 并且这些条目通过 item pipelines 传递,spider 将会以 closespider_itemcount 的原因被自动关闭;CLOSESPIDER_PAGECOUNT:一个整数值,指定最大的抓取响应 (reponses) 数。 如果 spider 抓取数超过指定的值,则会以 closespider_pagecount 的原因自动关闭。 如果设置为0(或者未设置),spiders不会因为抓取的响应数而关闭;CLOSESPIDER_ERRORCOUNT:一个整数值,指定spider可以接受的最大错误数。 如果spider生成多于该数目的错误,它将以 closespider_errorcount 的原因关闭。 如果设置为0(或者未设置),spiders不会因为发生错误过多而关闭;以上四个参数在 default_settings.py 中设置的默认值都是0并发相关,的设置会较大影响 Scrapy 爬虫的性能。下面是默认的配置值,其中都已经进行了详细的注释说明:# pipelines中并发处理items数CONCURRENT_ITEMS = 100# scrapy中并发下载请求数CONCURRENT_REQUESTS = 16# 对任何单个域执行的并发请求的最大数量CONCURRENT_REQUESTS_PER_DOMAIN = 8# 将对任何单个IP执行的并发请求的最大数量。如果非零CONCURRENT_REQUESTS_PER_IP = 0Cookie相关配置:# 是否启用cookiesmiddleware。如果关闭,cookies将不会发送给web serverCOOKIES_ENABLED = True# 如果启用,Scrapy将记录所有在request(cookie 请求头)发送的cookies及response接收到的cookies(set-cookie接收头),这也会间接影响性能,因此默认关闭。COOKIES_DEBUG = False请求深度相关配置,比如 DEPTH_LIMIT 设置请求允许的最大深度。如果为 0 ,则表示不受限;DEPTH_STATS_VERBOSE 参数控制是否收集详细的深度统计信息;如果启用此选项,则在统计信息中收集每个深度的请求数。DEPTH_PRIORITY 参数用于根据深度调整请求优先级。来看看他们的默认设置:DEPTH_LIMIT = 0DEPTH_STATS_VERBOSE = FalseDEPTH_PRIORITY = 0DNS 相关配置。DNSCACHE_ENABLED 用于控制是否启用 DNS 缓存,DNSCACHE_SIZE参数设置缓存大小,DNS_TIMEOUT 处理 DNS 查询超时时间;我们来具体看看 default_settings.py 中的默认配置:DNSCACHE_ENABLED = TrueDNSCACHE_SIZE = 10000# 缓存解析器DNS_RESOLVER = 'scrapy.resolver.CachingThreadedResolver'DNS_TIMEOUT = 60下载器相关。这部分的配置比较多,也是主要影响性能的地方。我们对一些关键的配置进行说明,具体如下:DOWNLOAD_DELAY:下载器在从同一网站下载连续页面之前应等待的时间,通过该配置可以限制爬虫的爬取速度。此外,该设置也受RANDOMIZE_DOWNLOAD_DELAY 设置(默认情况下启用)的影响。DOWNLOAD_TIMEOUT:下载超时时间;DOWNLOAD_MAXSIZE:下载器将下载的最大响应大小;DOWNLOAD_HANDLERS_BASE:处理不同类型下载的下载器;DOWNLOAD_FAIL_ON_DATALOSS:数据丢失后是否继续下载;DOWNLOADER_MIDDLEWARES 和DOWNLOADER_MIDDLEWARES_BASE:分别表示自定义的下载中间件类和默认的下载中间件类;DOWNLOADER_STATS:是否启用下载器统计信息收集。来看看 default_settings.py 中的默认配置,具体如下:DOWNLOAD_DELAY = 0DOWNLOAD_HANDLERS = {}DOWNLOAD_HANDLERS_BASE = { 'data': 'scrapy.core.downloader.handlers.datauri.DataURIDownloadHandler', 'file': 'scrapy.core.downloader.handlers.file.FileDownloadHandler', 'http': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler', 'https': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler', 's3': 'scrapy.core.downloader.handlers.s3.S3DownloadHandler', 'ftp': 'scrapy.core.downloader.handlers.ftp.FTPDownloadHandler',}DOWNLOAD_TIMEOUT = 180 # 3minsDOWNLOAD_MAXSIZE = 1024 * 1024 * 1024 # 1024mDOWNLOAD_WARNSIZE = 32 * 1024 * 1024 # 32mDOWNLOAD_FAIL_ON_DATALOSS = TrueDOWNLOADER = 'scrapy.core.downloader.Downloader'DOWNLOADER_HTTPCLIENTFACTORY = 'scrapy.core.downloader.webclient.ScrapyHTTPClientFactory'DOWNLOADER_CLIENTCONTEXTFACTORY = 'scrapy.core.downloader.contextfactory.ScrapyClientContextFactory'DOWNLOADER_CLIENT_TLS_CIPHERS = 'DEFAULT'# Use highest TLS/SSL protocol version supported by the platform, also allowing negotiation:DOWNLOADER_CLIENT_TLS_METHOD = 'TLS'DOWNLOADER_CLIENT_TLS_VERBOSE_LOGGING = FalseDOWNLOADER_MIDDLEWARES = {}DOWNLOADER_MIDDLEWARES_BASE = { # Engine side 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, # Downloader side}DOWNLOADER_STATS = TrueDUPEFILTER_CLASS:指定去重类;DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'自定义扩展和内置扩展配置:EXTENSIONS = {}EXTENSIONS_BASE = { 'scrapy.extensions.corestats.CoreStats': 0, 'scrapy.extensions.telnet.TelnetConsole': 0, 'scrapy.extensions.memusage.MemoryUsage': 0, 'scrapy.extensions.memdebug.MemoryDebugger': 0, 'scrapy.extensions.closespider.CloseSpider': 0, 'scrapy.extensions.feedexport.FeedExporter': 0, 'scrapy.extensions.logstats.LogStats': 0, 'scrapy.extensions.spiderstate.SpiderState': 0, 'scrapy.extensions.throttle.AutoThrottle': 0,}文件存储相关:FILES_STORE_S3_ACL = 'private'FILES_STORE_GCS_ACL = ''FTP 服务配置, Scrapy 框架内置 FTP 下载程序。我们可以指定 FTP 的相关参数:FTP_USER = 'anonymous'FTP_PASSWORD = 'guest'FTP_PASSIVE_MODE = TrueHTTP 缓存相关配置。Scrapy 的 HttpCacheMiddleware 组件(默认情况下没有启用)提供了一个底层的对HTTP请求和响应的缓存。如果启用的话(把HTTPCACHE_ENABLED设置为True),它会缓存每个请求和对应的响应。来看看和其相关的配置和含义:# 是否启用http缓存HTTPCACHE_ENABLED = False# 缓存数据目录HTTPCACHE_DIR = 'httpcache'HTTPCACHE_IGNORE_MISSING = False# 缓存存储的插件HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'# 缓存过期时间HTTPCACHE_EXPIRATION_SECS = 0HTTPCACHE_ALWAYS_STORE = False# 缓存忽略的Http状态码HTTPCACHE_IGNORE_HTTP_CODES = []HTTPCACHE_IGNORE_SCHEMES = ['file']HTTPCACHE_IGNORE_RESPONSE_CACHE_CONTROLS = []HTTPCACHE_DBM_MODULE = 'dbm'# 设置缓存策略,DummyPolicy是所有请求均缓存,下次在请求直接访问原来的缓存即可HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'# 是否启用缓存数据压缩HTTPCACHE_GZIP = FalseItem 和 Item pipelines相关配置:# ITEM处理器ITEM_PROCESSOR = 'scrapy.pipelines.ItemPipelineManager'# 自定义的 item pipelinesITEM_PIPELINES = {}ITEM_PIPELINES_BASE = {}日志相关的配置:# 启动日志功能LOG_ENABLED = True# 日志编码LOG_ENCODING = 'utf-8'# 日志格式器LOG_FORMATTER = 'scrapy.logformatter.LogFormatter'# 日志格式LOG_FORMAT = '%(asctime)s [%(name)s] %(levelname)s: %(message)s'# 日志时间格式LOG_DATEFORMAT = '%Y-%m-%d %H:%M:%S'LOG_STDOUT = False# 日志级别LOG_LEVEL = 'DEBUG'# 指定日志输出文件LOG_FILE = NoneLOG_SHORT_NAMES = False邮件配置:在 Scrapy 中提供了邮件功能,该功能使用十分简便且采用了 Twisted 非阻塞模式,避免了对爬虫的影响。我们只需要在 Scrapy 中进行简单的设置,就能通过 API 发送邮件。邮件的默认配置项如下:MAIL_HOST = 'localhost'MAIL_PORT = 25MAIL_FROM = 'scrapy@localhost'MAIL_PASS = NoneMAIL_USER = None我们现在可以简单的使用下 Scrapy 给我们提供的邮件类,来利用它给我们自己发送一封邮件。首先需要找下自己的 qq 邮箱或者其他邮箱,开启 POP3/SMTP服务,然后我们可以得到一个授权码。这个就是我们登陆这个邮箱服务的密码。然后我们配置 settings.py 中的相应项:MAIL_HOST = 'smtp.qq.com'MAIL_PORT = 25MAIL_FROM = '2894577759@qq.com'MAIL_PASS = '你的授权码'MAIL_USER = '2894577759@qq.com'接下来我们在 scrapy shell 中来调用相应的邮件接口,发送邮件:(scrapy-test) [root@server china_pub]# scrapy shell --nolog[s] Available Scrapy objects:[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)[s] crawler <scrapy.crawler.Crawler object at 0x7f1c3d4e9100>[s] item {}[s] settings <scrapy.settings.Settings object at 0x7f1c3d4e6dc0>[s] Useful shortcuts:[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)[s] fetch(req) Fetch a scrapy.Request and update local objects [s] shelp() Shell help (print this help)[s] view(response) View response in a browser>>> from scrapy.mail import MailSender>>> mailer = MailSender().from_settings(settings)>>> mailer.send(to=["2894577759@qq.com"], subject="这是一个测试", body="来自百度云主机发送的一封邮件", cc=["2894577759@qq.com"])<Deferred at 0x7f1c3c4d1c40>调用 Scrapy 的邮件接口发送邮件内存相关参数:MEMDEBUG_ENABLED = False # enable memory debuggingMEMDEBUG_NOTIFY = [] # send memory debugging report by mail at engine shutdownMEMUSAGE_CHECK_INTERVAL_SECONDS = 60.0# 是否启用内存使用扩展MEMUSAGE_ENABLED = True# 在关闭Scrapy之前允许的最大内存量,为0则不检查MEMUSAGE_LIMIT_MB = 0# 要达到内存限制时通知的电子邮件列表MEMUSAGE_NOTIFY_MAIL = []# 在发送警告电子邮件通知之前,要允许的最大内存量(以兆字节为单位)。如果为零,则不会产生警告MEMUSAGE_WARNING_MB = 0调度器相关配置:# 调度器类SCHEDULER = 'scrapy.core.scheduler.Scheduler'# 指定调度器的三种队列类SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue'SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue'SCHEDULER_PRIORITY_QUEUE = 'scrapy.pqueues.ScrapyPriorityQueue'# 正在处理响应数据的软限制(以字节为单位),如果所有正在处理的响应的大小总和高于此值,Scrapy不会处理新的请求SCRAPER_SLOT_MAX_ACTIVE_SIZE = 5000000spider 中间件相关配置,有我们熟悉的 SPIDER_MIDDLEWARES 和 SPIDER_MIDDLEWARES_BASE,表示自定义的 Spider 中间件和 Scrapy 内置的 Spider 中间件;SPIDER_MIDDLEWARES = {}SPIDER_MIDDLEWARES_BASE = { # Engine side 'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500, 'scrapy.spidermiddlewares.referer.RefererMiddleware': 700, 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800, 'scrapy.spidermiddlewares.depth.DepthMiddleware': 900, # Spider side}指定模板文件目录,这个在使用 scrapy startproject 项目名 命令创建项目时,对应的模板文件所在的目录:TEMPLATES_DIR = abspath(join(dirname(__file__), '..', 'templates'))USER_AGENT:设置请求头的 User-Agent 参数,用来模拟浏览器。我们通常都会添加一个浏览器的 User-Agent 值,防止爬虫直接被屏蔽;Scrapy 的大体配置就是这些,还有一些没有介绍到的参数,课后可以仔细查看官方文档进行了解。
- 1. 深入 Django 中 CSRF 校验过程 上一节中提到了,针对 CSRF 攻击有效的解决方案是在网页上添加一个随机的校验 token 值,我们前面的登录的模板页面中添加的 {% csrf_token %},这里正好对应着一个随机值。我们拿之前的登录表单来进行观察,会发现这样几个现象:网页上隐藏的 csrf_token 值会在每次刷新时变化;对应在请求和响应头部的 cookie 中的 csrftoken值却一直不变;这样子我们对应会产生几个思考问题:为什么网页上的 token 值会变,而 cookie 中的 token 则一直不变?整个 token 的校验过程是怎样的,有密码?如果有密码,密码存在哪里?今天我们会带着这两个问题,查看下 Django 内部源码,找到这些问题的代码位置。我可能不会很完整的描述整个代码运行的逻辑,因为篇幅不够,而且细节太多,容易迷失在代码的海洋里。首先毋庸置疑的第一步是找我们在 settings.py 中设置的 CSRF 中间件:MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',]我们在上一讲中提到过中间件类的两个函数:process_request() 和 process_response()。而在 CSRF 中间件文件中还有一个方法:process_view()。中间类比较完整的处理流程示意图如下所示,可以看到中间件的 process_view() 方法如果返回 None,则会执行下一个 中间件的 process_view() 方法。一旦它返回 HttpResponse 实例,则直接跳过视图函数到达最后一个中间件的 process_response() 方法中。我们来关注下 django.middleware 目录下的 csrf.py 文件,所有的答案都在这里可以找到。首先看最核心的中间件类:# 源码位置:django/middleware/csrf.py# ...class CsrfViewMiddleware(MiddlewareMixin): def _accept(self, request): # Avoid checking the request twice by adding a custom attribute to # request. This will be relevant when both decorator and middleware # are used. request.csrf_processing_done = True return None def _reject(self, request, reason): response = _get_failure_view()(request, reason=reason) log_response( 'Forbidden (%s): %s', reason, request.path, response=response, request=request, logger=logger, ) return response def _get_token(self, request): # ... def _set_token(self, request, response): # ... def process_request(self, request): csrf_token = self._get_token(request) if csrf_token is not None: # Use same token next time. request.META['CSRF_COOKIE'] = csrf_token def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, 'csrf_processing_done', False): return None # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works if getattr(callback, 'csrf_exempt', False): return None # Assume that anything not defined as 'safe' by RFC7231 needs protection if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if getattr(request, '_dont_enforce_csrf_checks', False): # Mechanism to turn off CSRF checks for test suite. # It comes after the creation of CSRF cookies, so that # everything else continues to work exactly the same # (e.g. cookies are sent, etc.), but before any # branches that call reject(). return self._accept(request) # 判断是不是 https 协议,不然不用执行这里 if request.is_secure(): # ... csrf_token = request.META.get('CSRF_COOKIE') if csrf_token is None: # No CSRF cookie. For POST requests, we insist on a CSRF cookie, # and in this way we can avoid all CSRF attacks, including login # CSRF. return self._reject(request, REASON_NO_CSRF_COOKIE) # Check non-cookie token for match. request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') except IOError: # Handle a broken connection before we've completed reading # the POST data. process_view shouldn't raise any # exceptions, so we'll ignore and serve the user a 403 # (assuming they're still listening, which they probably # aren't because of the error). pass if request_csrf_token == "": # Fall back to X-CSRFToken, to make things easier for AJAX, # and possible for PUT/DELETE. request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') request_csrf_token = _sanitize_token(request_csrf_token) if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN) return self._accept(request) def process_response(self, request, response): if not getattr(request, 'csrf_cookie_needs_reset', False): if getattr(response, 'csrf_cookie_set', False): return response if not request.META.get("CSRF_COOKIE_USED", False): return response # Set the CSRF cookie even if it's already set, so we renew # the expiry timer. self._set_token(request, response) response.csrf_cookie_set = True return response这里比较复杂的部分就是 process_view() 方法。process_request() 方法只是从请求头中取出 csrftoken 值或者生成一个 csrftoken 值放到 request.META 属性中去;process_response() 会设置对应的 csrftoken 值到 cookie 或者 session 中去。这里获取 csrftoken 和 设置 csrftoken 调用的正是 _get_token() 和 set_token()方法:class CsrfViewMiddleware(MiddlewareMixin): # ... def _get_token(self, request): if settings.CSRF_USE_SESSIONS: try: return request.session.get(CSRF_SESSION_KEY) except AttributeError: raise ImproperlyConfigured( 'CSRF_USE_SESSIONS is enabled, but request.session is not ' 'set. SessionMiddleware must appear before CsrfViewMiddleware ' 'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is None else '') ) else: try: cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None csrf_token = _sanitize_token(cookie_token) if csrf_token != cookie_token: # Cookie token needed to be replaced; # the cookie needs to be reset. request.csrf_cookie_needs_reset = True return csrf_token def _set_token(self, request, response): if settings.CSRF_USE_SESSIONS: if request.session.get(CSRF_SESSION_KEY) != request.META['CSRF_COOKIE']: request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE'] else: response.set_cookie( settings.CSRF_COOKIE_NAME, request.META['CSRF_COOKIE'], max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, secure=settings.CSRF_COOKIE_SECURE, httponly=settings.CSRF_COOKIE_HTTPONLY, samesite=settings.CSRF_COOKIE_SAMESITE, ) # Set the Vary header since content varies with the CSRF cookie. patch_vary _headers(response, ('Cookie',)) # ...如果我们没在 settings.py 中设置 CSRF_USE_SESSIONS 值时,在 django/conf/global_settings.py 默认设置为 False,那么我们就是调用前面熟悉的 response.set_cookie() 方法取设置 cookie 中的 key-value 值,也是我们在上面第二张图片所看到的 Set-Cookie 里面的值。我们来看最核心的处理方法:process_view()。它的执行流程如下所列,略有删减,请仔细研读和对照代码:判断视图方法是否有 csrf_exempt 属性。相当于该视图方法添加了 @csrf_exempt 装饰器,这样不用检验 csrf_token 值,直接返回 None,进入下面的中间件执行,直到视图函数去处理 HTTP 请求;对于 GET、HEAD、 OPTIONS、 TRACE 这四种请求不用检查 csrf_token,会直接跳到最后执行 self._accept(request) 方法。但是我们常用的如 POST、PUT 以及 DELETE 等请求会进行特别的处理;来看针对 POST、PUT 以及 DELETE 的特殊处理,要注意两处代码:request_csrf_token 值的获取:对于 POST 请求,我们要从请求参数中获取,这个值正是表单中隐藏的随机 csrf_token,也是我们在第一张图中看到的值,每次请求都会刷新该值;而且对于其它的请求,该值则是从 request.META 中获取;校验 csrf_token 值是否正确。如果是不正确的 csrf_token 值,则会直接返回 403 错误;if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN)可以看到,这里校验的是两个值:一个是我们从 cookie 中获取的,另一个是前端表单中隐藏的那个随机数。现在我们大致心里有个数了,Django 的校验方法竟然是用 cookie 中的值和页面上的随机值进行校验,这两个值都是64位的,你必须同时拿到这两个正确 token 值才能通过 Django 的 csrf 中间件校验。比较原理,2个 token,一个放到 cookie 中,另一个放到表单中,会一直变得那种。接下来就是对这两个 token 进行对比。我们继续追踪 _compare_salted_tokens() 方法,可以在 csrf.py 中找到如下两个方法,它们分别对应着 csrf_token 值的生成和解码:# 源码位置:django/middleware/csrf.py# ...def _salt_cipher_secret(secret): """ Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS), generate a token by adding a salt and using it to encrypt the secret. """ salt = _get_new_csrf_string() chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in salt)) cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs) return salt + cipherdef _unsalt_cipher_token(token): """ Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt the second half to produce the original secret. """ salt = token[:CSRF_SECRET_LENGTH] token = token[CSRF_SECRET_LENGTH:] chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) secret = ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok return secret# ...来看这两个函数,首先是 _salt_cipher_secret() 方法,需要传入一个长度为 32 的 secret,就可以得到一个64位的随机字符。这个 secret 值在使用时也是随机生成的32个字符:# 源码位置:django/middleware/csrf.pydef _get_new_csrf_string(): return get_random_string(CSRF_SECRET_LENGTH, allowed_chars=CSRF_ALLOWED_CHARS)# 源码位置:django/utils/crypto.pydef get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): """ Return a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns a 71-bit value. log_2((26+26+10)^12) =~ 71 bits """ if not using_sysrandom: # This is ugly, and a hack, but it makes things better than # the alternative of predictability. This re-seeds the PRNG # using a value that is hard for an attacker to predict, every # time a random string is required. This may change the # properties of the chosen random sequence slightly, but this # is better than absolute predictability. random.seed( hashlib.sha256( ('%s%s%s' % (random.getstate(), time.time(), settings.SECRET_KEY)).encode() ).digest() ) return ''.join(random.choice(allowed_chars) for i in range(length))在 _salt_cipher_secret() 方法中我们可以看到,传入32位的密钥 secret,最后的 csrf_token 的生成是 salt + cipher,前32位是 salt,后32位是加密字符串。解密的过程差不多就是 _salt_cipher_secret() 的逆过程了,最后得到 secret。我们可以在 Django 的 shell 模式下使用下这两个函数:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django.middleware.csrf import _get_new_csrf_token, _unsalt_cipher_token>>> x1 = _get_new_csrf_token()>>> x2 = _get_new_csrf_token()>>> x3 = _get_new_csrf_token()>>> print('x1={}\nx2={}\nx3={}'.format(x1, x2, x3))x1=dvK3CRLiyHJ6Xgt0B6eZ7kUjxXgZ5CKkhl8HbHq8CKR0ZXMOxYnigzDTIZIdk3xZx2=TMazqRDst3BSiyxIAI1XDiFKdbmxu8nKRVvMogERiZi6IG6KNhDSxcgEOPTqU0qFx3=gy998wPOCZJiXHo7HYQtY3dfwaevPHKAs2YXPAeJmWUaA5vV2xdXqvlidLR4XM1T>>> _unsalt_cipher_token(x1)'e0yOJ0P0edi4cRtY62jtjpTKlcCopBXP'>>> _unsalt_cipher_token(x2)'8jvn8zbzZ6RoAiJcnJM544L4LOH3A2d5'>>> _unsalt_cipher_token(x3)'mEZYRez5U7l2NyhYvJxECCidRLNJifrt'>>> 了解了上述这些方法后,现在来思考前面提出的问题:为什么每次刷新表单中的 csrf_token 值会一直变化,而 cookie 中的 csrf_token 值却一直不变呢?首先我们看在页面上生成随机 token 值的代码,也就是将标签 {{ csrf_token }} 转成 64位随机码的地方:# 源码位置: django/template/defaulttags.py@register.tagdef csrf_token(parser, token): return CsrfTokenNode()class CsrfTokenNode(Node): def render(self, context): csrf_token = context.get('csrf_token') if csrf_token: if csrf_token == 'NOTPROVIDED': return format_html("") else: return format_html('<input type="hidden" name="csrfmiddlewaretoken" value="{}">', csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning if settings.DEBUG: warnings.warn( "A {% csrf_token %} was used in a template, but the context " "did not provide the value. This is usually caused by not " "using RequestContext." ) return ''可以看到 csrf_token 值是从 context 中取出来的,而在 context 中的 csrf_token 值又是由如下代码生成的:# 源码位置:django/template/context_processors.pyfrom django.middleware.csrf import get_token# ...def csrf(request): """ Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if it has not been provided by either a view decorator or the middleware """ def _get_val(): token = get_token(request) if token is None: # In order to be able to provide debugging info in the # case of misconfiguration, we use a sentinel value # instead of returning an empty dict. return 'NOTPROVIDED' else: return token return {'csrf_token': SimpleLazyObject(_get_val)}可以看到,最后 csrf_token 值还是由 csrf.py 文件中的 get_token() 方法生成的。来继续看这个 get_token() 方法的代码:# 源码位置:django/middleware/csrf.pydef get_token(request): """ Return the CSRF token required for a POST form. The token is an alphanumeric value. A new token is created if one is not already set. A side effect of calling this function is to make the csrf_protect decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' header to the outgoing response. For this reason, you may need to use this function lazily, as is done by the csrf context processor. """ if "CSRF_COOKIE" not in request.META: csrf_secret = _get_new_csrf_string() request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret) else: csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"]) request.META["CSRF_COOKIE_USED"] = True return _salt_cipher_secret(csrf_secret)注意!注意!最关键的地方来了,这个加密的 secret 的值是从哪里来的?正是从请求头中的 cookie 信息中来的,如果没有将生成一个新的密钥,接着把该密钥生成的 token 放到 cookie 中。最后使用 _salt_cipher_secret() 方法生成的 csrf_token 和 cookie 中的 csrf_token 具有相同的密钥。同时拿到了这两个值,就可以进行校验和判断,下面我们在 ``_salt_cipher_secret()方法中加上一个print()` 语句,然后执行下看看是否如我们所说。22可以看到每次生成 token 时加密的秘钥都是一样的。我们从上面生成的 csrf_token 中选一个进行解密,得到的结果和 cookie 中的正是一样的密钥:>>> from django.middleware.csrf import _unsalt_cipher_token# 两个 token 解密是相同的,这才是正确的>>> _unsalt_cipher_token('2Tt8StiU4rZcvCrTb2KqJwTTOTCP0WvJhp7GyTj58RGv97IvJInxyrAN4DKCdt1M')'pGOIQAbleARtOFrMIQNhZ5R4qUiXnHGd'>>> _unsalt_cipher_token('VI68m6xT1JczSsnuJvxqtcr0L0EvCN1DaeKG2wy459TSwXE6hbaxi78U1KMiPkxG')'pGOIQAbleARtOFrMIQNhZ5R4qUiXnHGd'现在大部分代码我们也算清楚了,csrf_token 的校验原理我们也知道了。那么如果想自己生成 csrf_token 并通过 Django 的校验也非常简单,只需要通过那个密钥生成一个 csrf_token 或者直接输入使用密钥都可以通过校验。我们首先使用密钥在 shell 模式下随机生成一个 csrf_token 值:>>> from django.middleware.csrf import _salt_cipher_secret>>> _salt_cipher_secret('pGOIQAbleARtOFrMIQNhZ5R4qUiXnHGd')'ObaC9DEZfn4seXbOhgBCph2Y5PMjm0Eo3HOaP3FajNLLSssqPWeJecJSlzU6zxar接下来在看我的演示,第一此我随机改动 csrf_token 的字符,这样校验肯定通不过;接下来我是用自己生成的 token 值以及直接填写密钥再去提交都能通过校验。为了方便演示结果,我们在 csrf.py 的 process_view() 函数中添加几个 print() 方法,方便我们理解执行的过程。23
- ES6+ includes() 零基础深入浅出讲解 ES6+ 的语法及使用
- MyBatis insert 一款优秀的持久层框架
- 12 Ruby 的哈希 专为面向对象编程所设计的 Ruby 语言
debugging相关搜索
-
daima
damain
dart
dataset
datasource
datediff
datediff函数
datepicker
datetime
db4o
dbi
dcloud
deallocate
debian安装
debugger
debugging
declaration
declarations
declare
decode函数