├── .gitignore ├── Makefile ├── README.rst ├── exts ├── basic.py ├── block.py ├── chinese_search.py ├── code_question.py ├── html_figref.py ├── image.py ├── latex_fix.py ├── latexstyle │ ├── anim.pdf │ ├── code.pdf │ ├── link.pdf │ ├── think.pdf │ ├── tip.pdf │ ├── warning.pdf │ └── xeCJK.sty.bak ├── literal_include.py ├── main.dic ├── nohighlight.py ├── number_label.py ├── number_ref.py ├── smallseg.py ├── suffix.dic ├── theme │ └── book │ │ ├── layout.html │ │ ├── static │ │ ├── anim.png │ │ ├── book.css │ │ ├── booktools.js │ │ ├── code.png │ │ ├── comment.css │ │ ├── comment.js │ │ ├── comment_white_yellow.gif │ │ ├── contents.png │ │ ├── link.png │ │ ├── logo.png │ │ ├── logo_sphinxbook.png │ │ ├── navigation.png │ │ ├── picture.png │ │ ├── pygments.css │ │ ├── think.png │ │ ├── tip.png │ │ └── warning.png │ │ └── theme.conf └── zh.py └── source ├── appendix_a.rst ├── appendix_b.rst ├── appendix_c.rst ├── chapter_01.rst ├── chapter_02.rst ├── chapter_03.rst ├── chapter_04.rst ├── chapter_05.rst ├── chapter_06.rst ├── chapter_07.rst ├── chapter_08.rst ├── chapter_09.rst ├── chapter_10.rst ├── chapter_11.rst ├── chapter_12.rst ├── chapter_13.rst ├── chapter_14.rst ├── conf.py ├── images ├── chapter-10-1.PNG ├── chapter-12-1.png ├── chapter-2-1.PNG ├── chapter-2-2.PNG ├── chapter-4-1.png ├── chapter-4-2.png ├── chapter-5-1.PNG ├── chapter-5-2.PNG ├── chapter-9-1.jpg ├── code-style-1.JPG ├── code-style-10.JPG ├── code-style-11.JPG ├── code-style-12.JPG ├── code-style-13.JPG ├── code-style-2.JPG ├── code-style-3.JPG ├── code-style-4.JPG ├── code-style-5.JPG ├── code-style-6.JPG ├── code-style-7.JPG ├── code-style-8.JPG └── code-style-9.JPG ├── index.rst ├── module_development.rst └── source_analysis.rst /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | exts/*.pyc 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sphinxdoc.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sphinxdoc.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/sphinxdoc" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sphinxdoc" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. nginx_book documentation master file, created by 2 | sphinx-quickstart on Wed Feb 29 17:58:19 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Nginx开发从入门到精通 7 | ============================= 8 | 9 | 10 | 缘起 11 | ++++++ 12 | 13 | nginx由于出色的性能,在世界范围内受到了越来越多人的关注,在淘宝内部它更是被广泛的使用,众多的开发以及运维同学都迫切的想要了解nginx模块的开发和它的内部原理,但是国内却没有一本关于这方面的书,源于此我们决定自己来写一本。本书的作者为淘宝核心系统服务器平台组的成员,本书写作的思路是从模块开发逐渐过渡到nginx原理剖析。书籍的内容会定期在这里更新,欢迎大家提出宝贵意见,不管是本书的内容问题,还是字词错误,都欢迎大家提交issue(章节标题的左侧有评注按钮),我们会及时的跟进。 14 | 15 | .. topic:: 更新历史 16 | 17 | .. csv-table:: 18 | :header: 日期, 描述 19 | :widths: 20, 160 20 | :quote: $ 21 | :delim: | 22 | 23 | 2012/03/01|创建目录大纲 24 | 2012/03/28|增加了样章 25 | 2012/05/25|更新样章 26 | 2012/06/08|增加第5章 27 | 2012/06/11|增加第4章 28 | 2012/06/26|增加第6章(event module) 29 | 2012/06/27|更新第5章部分内容 30 | 2012/07/04|更新第6章event module部分内容 31 | 2012/07/12|增加第12章(请求头读取,subrequest解析) 32 | 2012/08/14|增加第2章(nginx基础架构及基础概念) 33 | 2012/08/14|增加第2章(ngx_str_t数据结构介绍) 34 | 2012/08/17|增加第7章(模块开发高级篇之变量) 35 | 2012/08/25|增加第11章(nginx的启动阶段部分内容) 36 | 2012/09/26|增加第2章(ngx_array_t,ngx_hash_t及ngx_pool_t介绍) 37 | 2012/10/08|增加第11章(配置解析综述) 38 | 2012/10/12|增加第2章(ngx_hash_wildcard_t,ngx_hash_combined_t及ngx_hash_keys_arrays_t介绍) 39 | 2012/10/21|增加第2章(ngx_chain_t,ngx_list_t及ngx_buf_t介绍) 40 | 2012/11/09|增加第12章(请求体的读取和丢弃解析) 41 | 2012/11/24|更新第2章(ngx_buf_t的部分字段以及其他一些书写错误和表达) 42 | 2012/12/18|更新第11章(解析http块) 43 | 2012/12/10|增加第3章的内容 44 | 2012/12/28|补充和完善了第3章的内容 45 | 2013/01/25|增加了第2章(nginx的配置系统) 46 | 2013/02/18|增加了第2章(nginx的模块化体系结构, nginx的请求处理) 47 | 2013/03/05|增加了第12章部分内容(多阶段请求处理) 48 | 2013/03/08|完成第11章第1节(配置解析综述、ngx_http_block) 49 | 2013/04/16|完成第9章第1节(源码目录结构、configure原理) 50 | 2013/09/30|完成第12章部分内容(多阶段执行链各个阶段解析) 51 | 2013/10/11|完成第12章部分内容(filter解析) 52 | 2013/10/11|完成第12章部分内容(ssl解析) 53 | 54 | 版权申明 55 | ++++++++++++ 56 | 57 | 本书的著作权归作者淘宝核心系统服务器平台组成员所有。你可以: 58 | 59 | - 下载、保存以及打印本书 60 | - 网络链接、转载本书的部分或者全部内容,但是必须在明显处提供读者访问本书发布网站的链接 61 | - 在你的程序中任意使用本书所附的程序代码,但是由本书的程序所引起的任何问题,作者不承担任何责任 62 | 63 | 你不可以: 64 | 65 | - 以任何形式出售本书的电子版或者打印版 66 | - 擅自印刷、出版本书 67 | - 以纸媒出版为目的,改写、改编以及摘抄本书的内容 68 | 69 | 目录 70 | ++++++ 71 | 72 | 书籍浏览 (http://tengine.taobao.org/book/index.html) 73 | 74 | 团队成员 75 | ++++++++++++ 76 | 77 | 叔度 (http://blog.zhuzhaoyuan.com) 78 | 79 | 雕梁 (http://www.pagefault.info) 80 | 81 | 文景 (http://yaoweibin.cn) 82 | 83 | 李子 (http://blog.lifeibo.com) 84 | 85 | 卫越 (http://blog.sina.com.cn/u/1929617884) 86 | 87 | 袁茁 (http://yzprofile.me) 88 | 89 | 小熊 (http://dinic.iteye.com) 90 | 91 | 吉兆 (http://jizhao.blog.chinaunix.net) 92 | 93 | 静龙 (http://blog.csdn.net/fengmo_q) 94 | 95 | 竹权 (http://weibo.com/u/2199139545) 96 | 97 | 公远 (http://100continue.iteye.com/) 98 | 99 | 布可 (http://weibo.com/sifeierss) 100 | 101 | -------------------------------------------------------------------------------- /exts/basic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sphinx 3 | 4 | def setup(app): 5 | app.add_javascript('booktools.js') -------------------------------------------------------------------------------- /exts/block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from sphinx.util.compat import Directive 3 | import shutil 4 | import pdb 5 | import os.path as path 6 | from docutils.parsers.rst import directives 7 | from docutils import nodes 8 | 9 | def build_finished(app, ex): 10 | if app.builder.name == "latex": 11 | import glob 12 | curpath = path.split(__file__)[0] 13 | for fn in glob.glob(path.join(curpath, "latexstyle", "*.*")): 14 | print "copy %s" % fn 15 | shutil.copy(fn, path.join(app.builder.outdir, path.split(fn)[-1])) 16 | 17 | class timgblock(nodes.Part, nodes.Element): 18 | pass 19 | 20 | def latex_visit_timgblock(self, node): 21 | text = r""" 22 | \framebox[1.0 \textwidth]{ 23 | \includegraphics[width=2.5em]{%(image)s.pdf} 24 | \raisebox{1.0em}{\parbox{0.9 \textwidth}{\small 25 | """ 26 | self.body.append( text % node) 27 | self.context.append("}}}") 28 | 29 | def latex_depart_timgblock(self, node): 30 | self.body.append(self.context.pop()) 31 | 32 | def html_visit_timgblock(self, node): 33 | text = r"""
""" 34 | self.body.append(text % node) 35 | self.context.append("
") 36 | 37 | def html_depart_timgblock(self, node): 38 | self.body.append(self.context.pop()) 39 | 40 | def empty_visit(self, node): 41 | raise nodes.SkipNode 42 | 43 | class ImageBlockDirective(Directive): 44 | has_content = True 45 | required_arguments = 0 46 | optional_arguments = 2 47 | final_argument_whitespace = True 48 | option_spec = { 49 | 'text': directives.unchanged 50 | } 51 | image = "" 52 | 53 | def run(self): 54 | node = timgblock() 55 | node["image"] = self.image 56 | if self.arguments and self.arguments[0]: 57 | node['argument'] = u" ".join(self.arguments) 58 | self.state.nested_parse(self.content, self.content_offset, node) 59 | ret = [node] 60 | return ret 61 | 62 | def MakeFileDirective(imgname): 63 | #curpath = path.split(__file__)[0] 64 | #shutil.copy(path.join(curpath, imgname + ".pdf"), path.join(curpath, "..\\..\\build\\latex")) 65 | return type(imgname+"Directive",(ImageBlockDirective,),{"image":imgname}) 66 | 67 | def setup(app): 68 | #pdb.set_trace() 69 | app.add_node(timgblock, 70 | latex=(latex_visit_timgblock, latex_depart_timgblock), 71 | text=(empty_visit, None), 72 | html=(html_visit_timgblock, html_depart_timgblock)) 73 | app.add_directive('tcode', MakeFileDirective("code")) 74 | app.add_directive('tanim', MakeFileDirective("anim")) 75 | app.add_directive('twarning', MakeFileDirective("warning")) 76 | app.add_directive('tlink', MakeFileDirective("link")) 77 | app.add_directive('ttip', MakeFileDirective("tip")) 78 | app.add_directive('tthink', MakeFileDirective("think")) 79 | app.connect("build-finished", build_finished) -------------------------------------------------------------------------------- /exts/chinese_search.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def setup(app): 4 | import sphinx.search as search 5 | import zh 6 | search.languages["zh_CN"] = zh.SearchChinese -------------------------------------------------------------------------------- /exts/code_question.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils import nodes 3 | import sphinx.writers.latex as latex 4 | import sphinx.writers.html as html 5 | 6 | def replace_latex_question_mark(t): 7 | return t.replace(r"\PYGZsh{}\textless{}?\textgreater{}", u"\\large{【你的程序】}") 8 | 9 | def replace_html_question_mark(t): 10 | return t.replace("#<?>", u'【你的程序】') 11 | 12 | def setup(app): 13 | print "code question loaded" 14 | old_depart_literal_block = latex.LaTeXTranslator.depart_literal_block 15 | def depart_literal_block(self, node): 16 | old_depart_literal_block(self, node) 17 | self.body[-1] = replace_latex_question_mark(self.body[-1]) 18 | latex.LaTeXTranslator.depart_literal_block = depart_literal_block 19 | latex.LaTeXTranslator.depart_doctest_block = depart_literal_block 20 | 21 | old_visit_literal_block = html.HTMLTranslator.visit_literal_block 22 | def visit_literal_block(self, node): 23 | try: 24 | old_visit_literal_block(self, node) 25 | finally: 26 | self.body[-1] = replace_html_question_mark(self.body[-1]) 27 | 28 | html.HTMLTranslator.visit_literal_block = visit_literal_block 29 | html.HTMLTranslator.visit_doctest_block = visit_literal_block -------------------------------------------------------------------------------- /exts/html_figref.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils import nodes 3 | import sphinx.writers.html as html 4 | import pdb 5 | 6 | def setup(app): 7 | old_visit_reference = html.HTMLTranslator.visit_reference 8 | def visit_reference(self, node): 9 | if node.get('refid','').startswith("fig-"): 10 | text = node.children[0].children[0].astext() 11 | node.children[0].children[0] = nodes.Text(u"【图:%s】" % text) 12 | old_visit_reference(self, node) 13 | html.HTMLTranslator.visit_reference = visit_reference -------------------------------------------------------------------------------- /exts/image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os.path as path 3 | import os 4 | from docutils import nodes 5 | from glob import glob 6 | import imghdr 7 | from sphinx.environment import BuildEnvironment 8 | from docutils.utils import relative_path 9 | from sphinx.builders.latex import LaTeXBuilder 10 | from sphinx.builders.html import StandaloneHTMLBuilder 11 | def process_images(self, docname, doctree): 12 | """ 13 | Process and rewrite image URIs. 14 | """ 15 | docdir = path.dirname(self.doc2path(docname, base=None)) 16 | for node in doctree.traverse(nodes.image): 17 | # Map the mimetype to the corresponding image. The writer may 18 | # choose the best image from these candidates. The special key * is 19 | # set if there is only single candidate to be used by a writer. 20 | # The special key ? is set for nonlocal URIs. 21 | node['candidates'] = candidates = {} 22 | imguri = node['uri'] 23 | if imguri.find('://') != -1: 24 | self.warn(docname, 'nonlocal image URI found: %s' % imguri, 25 | node.line) 26 | candidates['?'] = imguri 27 | continue 28 | # imgpath is the image path *from srcdir* 29 | if imguri.startswith('/') or imguri.startswith(os.sep): 30 | # absolute path (= relative to srcdir) 31 | imgpath = path.normpath(imguri[1:]) 32 | else: 33 | imgpath = path.normpath(path.join(docdir, imguri)) 34 | # set imgpath as default URI 35 | node['uri'] = imgpath 36 | if imgpath.endswith(os.extsep + '*'): 37 | for filename in glob(path.join(self.srcdir, imgpath)): 38 | new_imgpath = relative_path(self.srcdir, filename) 39 | if filename.lower().endswith('.pdf'): 40 | candidates['application/pdf'] = new_imgpath 41 | elif filename.lower().endswith('.svg'): 42 | candidates['image/svg+xml'] = new_imgpath 43 | elif ".latex." in filename.lower(): 44 | candidates['latex'] = new_imgpath 45 | elif ".html." in filename.lower(): 46 | candidates['html'] = new_imgpath 47 | else: 48 | try: 49 | f = open(filename, 'rb') 50 | try: 51 | imgtype = imghdr.what(f) 52 | finally: 53 | f.close() 54 | except (OSError, IOError), err: 55 | self.warn(docname, 'image file %s not ' 56 | 'readable: %s' % (filename, err), 57 | node.line) 58 | if imgtype: 59 | candidates['image/' + imgtype] = new_imgpath 60 | else: 61 | candidates['*'] = imgpath 62 | # map image paths to unique image names (so that they can be put 63 | # into a single directory) 64 | for imgpath in candidates.itervalues(): 65 | self.dependencies.setdefault(docname, set()).add(imgpath) 66 | if not os.access(path.join(self.srcdir, imgpath), os.R_OK): 67 | self.warn(docname, 'image file not readable: %s' % imgpath, 68 | node.line) 69 | continue 70 | self.images.add_file(docname, imgpath) 71 | 72 | from sphinx.util import url_re, get_matching_docs, docname_join, \ 73 | FilenameUniqDict 74 | 75 | def add_file(self, docname, newfile): 76 | if newfile in self: 77 | self[newfile][0].add(docname) 78 | return self[newfile][1] 79 | uniquename = path.basename(newfile) 80 | self[newfile] = (set([docname]), uniquename) 81 | self._existing.add(uniquename) 82 | return uniquename 83 | 84 | def setup(app): 85 | FilenameUniqDict.add_file = add_file 86 | BuildEnvironment.process_images = process_images 87 | LaTeXBuilder.supported_image_types.insert(0, "latex") 88 | StandaloneHTMLBuilder.supported_image_types.insert(0, "html") 89 | -------------------------------------------------------------------------------- /exts/latex_fix.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils import nodes 3 | import sphinx.writers.latex as latex 4 | 5 | def setup(app): 6 | latex.LaTeXTranslator.default_elements["babel"] = '\\usepackage[english]{babel}' 7 | #latex.LaTeXTranslator.default_elements["inputenc"] = '' 8 | -------------------------------------------------------------------------------- /exts/latexstyle/anim.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/latexstyle/anim.pdf -------------------------------------------------------------------------------- /exts/latexstyle/code.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/latexstyle/code.pdf -------------------------------------------------------------------------------- /exts/latexstyle/link.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/latexstyle/link.pdf -------------------------------------------------------------------------------- /exts/latexstyle/think.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/latexstyle/think.pdf -------------------------------------------------------------------------------- /exts/latexstyle/tip.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/latexstyle/tip.pdf -------------------------------------------------------------------------------- /exts/latexstyle/warning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/latexstyle/warning.pdf -------------------------------------------------------------------------------- /exts/literal_include.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import os.path as path 4 | import codecs 5 | from docutils.parsers.rst import directives 6 | from docutils import nodes 7 | import sphinx.directives.code as code 8 | import re 9 | from sphinx.util import parselinenos 10 | 11 | from number_label import CircleNumbers 12 | 13 | def replace_number_label(text): 14 | def f(mo): 15 | return u"#"+CircleNumbers[int(mo.group(1))-1] 16 | return re.sub(r"#{(\d+)}", f, text) 17 | 18 | def run(self): 19 | document = self.state.document 20 | filename = self.arguments[0] 21 | #print filename 22 | if not document.settings.file_insertion_enabled: 23 | return [document.reporter.warning('File insertion disabled', 24 | line=self.lineno)] 25 | env = document.settings.env 26 | if filename.startswith('/') or filename.startswith(os.sep): 27 | rel_fn = filename[1:] 28 | else: 29 | docdir = path.dirname(env.doc2path(env.docname, base=None)) 30 | rel_fn = path.normpath(path.join(docdir, filename)) 31 | fn = path.join(env.srcdir, rel_fn) 32 | 33 | if 'pyobject' in self.options and 'lines' in self.options: 34 | return [document.reporter.warning( 35 | 'Cannot use both "pyobject" and "lines" options', 36 | line=self.lineno)] 37 | 38 | encoding = self.options.get('encoding', env.config.source_encoding) 39 | try: 40 | f = codecs.open(fn, 'rU', encoding) 41 | lines = f.readlines() 42 | f.close() 43 | # 去掉编码指示 44 | if fn.endswith(".py") and lines[0].startswith("#") and "coding" in lines[0]: 45 | lines = lines[1:] 46 | # 去掉文档说明 47 | if fn.endswith(".py"): 48 | if lines[0].startswith('"""'): 49 | for lineno, line in enumerate(lines[1:]): 50 | if line.strip().endswith('"""'): 51 | lines = lines[lineno+2:] 52 | break 53 | # 去掉每行末尾空格 54 | for i in xrange(len(lines)): 55 | lines[i] = lines[i].rstrip() + "\n" 56 | 57 | except (IOError, OSError): 58 | return [document.reporter.warning( 59 | 'Include file %r not found or reading it failed' % filename, 60 | line=self.lineno)] 61 | except UnicodeError: 62 | return [document.reporter.warning( 63 | 'Encoding %r used for reading included file %r seems to ' 64 | 'be wrong, try giving an :encoding: option' % 65 | (encoding, filename))] 66 | 67 | objectname = self.options.get('pyobject') 68 | if objectname is not None: 69 | from sphinx.pycode import ModuleAnalyzer 70 | analyzer = ModuleAnalyzer.for_file(fn, '') 71 | tags = analyzer.find_tags() 72 | if objectname not in tags: 73 | return [document.reporter.warning( 74 | 'Object named %r not found in include file %r' % 75 | (objectname, filename), line=self.lineno)] 76 | else: 77 | lines = lines[tags[objectname][1]-1 : tags[objectname][2]-1] 78 | 79 | linespec = self.options.get('lines') 80 | if linespec is not None: 81 | try: 82 | linelist = parselinenos(linespec, len(lines)) 83 | except ValueError, err: 84 | return [document.reporter.warning(str(err), line=self.lineno)] 85 | lines = [lines[i] for i in linelist] 86 | 87 | startafter = self.options.get('start-after') 88 | endbefore = self.options.get('end-before') 89 | if startafter is not None or endbefore is not None: 90 | use = not startafter 91 | res = [] 92 | for line in lines: 93 | if not use and startafter in line: 94 | use = True 95 | elif use and endbefore in line: 96 | use = False 97 | break 98 | elif use: 99 | res.append(line) 100 | lines = res 101 | 102 | section = self.options.get("section") 103 | if section is not None: 104 | section = "###%s###" % section 105 | print section 106 | use = False 107 | res = [] 108 | for line in lines: 109 | if not use and section in line: 110 | use = True 111 | continue 112 | elif use and section in line: 113 | use = False 114 | break 115 | if use: 116 | res.append(line) 117 | lines = res 118 | indent = len(lines[0]) - len(lines[0].lstrip()) 119 | for i,line in enumerate(lines): 120 | lines[i] = line[indent:] 121 | 122 | text = replace_number_label(''.join(lines)) 123 | text = re.sub(r"(?s)#<\?(.*?)>.+?#<\?/>", lambda mo:u"#%s" % mo.group(1), text) 124 | #text = (u"#程序文件:%s\n" % filename) + text 125 | retnode = nodes.literal_block(text, text, source=fn) 126 | retnode.line = 1 127 | if self.options.get('language', ''): 128 | retnode['language'] = self.options['language'] 129 | if 'linenos' in self.options: 130 | retnode['linenos'] = True 131 | document.settings.env.note_dependency(rel_fn) 132 | #print "LiteralInclude hacked" 133 | return [retnode] 134 | 135 | def setup(app): 136 | code.LiteralInclude.option_spec["section"] = directives.unchanged_required 137 | code.LiteralInclude.run = run -------------------------------------------------------------------------------- /exts/nohighlight.py: -------------------------------------------------------------------------------- 1 | from sphinx import highlighting 2 | def highlight_block(self, source, lang, linenos=False, warn=None): 3 | return self.unhighlighted(source) 4 | highlighting.PygmentsBridge.highlight_block = highlight_block -------------------------------------------------------------------------------- /exts/number_label.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils import nodes 3 | import sphinx.writers.latex as latex 4 | import sphinx.writers.html as html 5 | 6 | CircleNumbers = u"❶❷❸❹❺❻❼❽❾❿" 7 | 8 | def replace_latex_code_labels(t): 9 | for i, n in enumerate(CircleNumbers): 10 | target = r"{\normalsize\ding{%s}}" % (202+i) 11 | target2 = r"[@normalsize@ding[%s]]" % (202+i) 12 | t = t.replace(r"\PYG{c}{\PYGZsh{}%s}" % n, target) 13 | t = t.replace(r"\PYG{c}{\#%s}" % n, target) 14 | t = t.replace(r"@#%s" % n, target2) 15 | return t 16 | 17 | def replace_latex_text_labels(t): 18 | for i, n in enumerate(CircleNumbers): 19 | t = t.replace(n, r"{\Large\ding{%s}}\hspace{1mm}" % (202+i)) 20 | return t 21 | 22 | def replace_html_code_labels(t): 23 | for i, n in enumerate(CircleNumbers): 24 | target = '#%s' % n 25 | t = t.replace("#%s" % n, target).replace("#{%d}" % (i+1), target).replace("#{{%d}}" % (i+1), "#{%d}" % (i+1)) 26 | return t 27 | 28 | def setup(app): 29 | print "number_label loaded" 30 | old_depart_literal_block = latex.LaTeXTranslator.depart_literal_block 31 | def depart_literal_block(self, node): 32 | old_depart_literal_block(self, node) 33 | self.body[-1] = replace_latex_code_labels(self.body[-1]) 34 | latex.LaTeXTranslator.depart_literal_block = depart_literal_block 35 | latex.LaTeXTranslator.depart_doctest_block = depart_literal_block 36 | 37 | old_visit_Text = latex.LaTeXTranslator.visit_Text 38 | def visit_Text(self, node): 39 | old_visit_Text(self, node) 40 | self.body[-1] = replace_latex_text_labels(self.body[-1]) 41 | latex.LaTeXTranslator.visit_Text = visit_Text 42 | 43 | old_visit_literal_block = html.HTMLTranslator.visit_literal_block 44 | def visit_literal_block(self, node): 45 | try: 46 | old_visit_literal_block(self, node) 47 | finally: 48 | self.body[-1] = replace_html_code_labels(self.body[-1]) 49 | 50 | html.HTMLTranslator.visit_literal_block = visit_literal_block 51 | html.HTMLTranslator.visit_doctest_block = visit_literal_block -------------------------------------------------------------------------------- /exts/number_ref.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils import nodes 3 | import sphinx.writers.latex as latex 4 | from sphinx.util.nodes import clean_astext 5 | import pdb 6 | def doctree_resolved(app, doctree, docname): 7 | """将带sec-开头的target标签名添加到标签的父节点之上 8 | 这样就可以在section节点之下定义章节的标签。便于用 9 | leo的auto-rst功能编辑rst文档。 10 | 11 | 例如: 12 | 13 | 章节名称 14 | -------- 15 | 16 | .. _sec-test: 17 | 18 | 章节内容 19 | """ 20 | for node in doctree.traverse(nodes.target): 21 | if node.get("refid", "").startswith("sec-"): 22 | section = node.parent 23 | section["ids"].append(node["refid"]) 24 | node["refid"] = "-" + node["refid"] 25 | 26 | def doctree_read(app, doctree): 27 | """ 28 | 为了sec-开头标签能正常工作需要将其添加进: 29 | env.domains["std"].data["labels"] 30 | sec-test: 文章名, 标签名, 章节名, 31 | """ 32 | labels = app.env.domains["std"].data["labels"] 33 | for name, _ in doctree.nametypes.iteritems(): 34 | if not name.startswith("sec-"): continue 35 | labelid = doctree.nameids[name] 36 | node = doctree.ids[labelid].parent 37 | if node.tagname == 'section': 38 | sectname = clean_astext(node[0]) 39 | labels[name] = app.env.docname, labelid, sectname 40 | 41 | def setup(app): 42 | print "number_ref loaded" 43 | old_visit_reference = latex.LaTeXTranslator.visit_reference 44 | def visit_reference(self, node): 45 | uri = node.get('refuri', '') 46 | hashindex = uri.find('#') 47 | if hashindex == -1: 48 | id = uri[1:] + '::doc' 49 | else: 50 | id = uri[1:].replace('#', ':') 51 | if uri.startswith("%") and "#fig-" in uri: 52 | self.body.append(self.hyperlink(id)) 53 | self.body.append(u"图\\ref*{%s}" % id) 54 | self.context.append("}}") 55 | raise nodes.SkipChildren 56 | elif uri.startswith("%") and "#sec-" in uri: 57 | self.body.append(self.hyperlink(id)) 58 | self.body.append(u"第\\ref*{%s}节" % id) 59 | self.context.append("}}") 60 | raise nodes.SkipChildren 61 | else: 62 | return old_visit_reference(self, node) 63 | latex.LaTeXTranslator.visit_reference = visit_reference 64 | 65 | app.connect("doctree-read", doctree_read) 66 | app.connect("doctree-resolved", doctree_resolved) -------------------------------------------------------------------------------- /exts/smallseg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | import os 4 | import sys 5 | class SEG(object): 6 | def __init__(self): 7 | _localDir=os.path.dirname(__file__) 8 | _curpath=os.path.normpath(os.path.join(os.getcwd(),_localDir)) 9 | curpath=_curpath 10 | self.d = {} 11 | print >> sys.stderr,"loading dict..." 12 | self.set([x.rstrip() for x in file(os.path.join(curpath,"main.dic")) ]) 13 | self.specialwords= set([x.rstrip().decode('utf-8') for x in file(os.path.join(curpath,"suffix.dic"))]) 14 | print >> sys.stderr,'dict ok.' 15 | #set dictionary(a list) 16 | def set(self,keywords): 17 | p = self.d 18 | q = {} 19 | k = '' 20 | for word in keywords: 21 | word = (chr(11)+word).decode('utf-8') 22 | if len(word)>5: 23 | continue 24 | p = self.d 25 | ln = len(word) 26 | for i in xrange(ln-1,-1,-1): 27 | char = word[i].lower() 28 | if p=='': 29 | q[k] = {} 30 | p = q[k] 31 | if not (char in p): 32 | p[char] = '' 33 | q = p 34 | k = char 35 | p = p[char] 36 | 37 | pass 38 | 39 | def _binary_seg(self,s): 40 | ln = len(s) 41 | if ln==1: 42 | return [s] 43 | R = [] 44 | for i in xrange(ln,1,-1): 45 | tmp = s[i-2:i] 46 | R.append(tmp) 47 | return R 48 | 49 | def _pro_unreg(self,piece): 50 | #print piece 51 | R = [] 52 | tmp = re.sub(u"。|,|,|!|…|!|《|》|<|>|\"|'|:|:|?|\?|、|\||“|”|‘|’|;|—|(|)|·|\(|\)| "," ",piece).split() 53 | ln1 = len(tmp) 54 | for i in xrange(len(tmp)-1,-1,-1): 55 | mc = re.split(r"([0-9A-Za-z\-\+#@_\.]+)",tmp[i]) 56 | for j in xrange(len(mc)-1,-1,-1): 57 | r = mc[j] 58 | if re.search(r"([0-9A-Za-z\-\+#@_\.]+)",r)!=None: 59 | R.append(r) 60 | else: 61 | R.extend(self._binary_seg(r)) 62 | return R 63 | 64 | 65 | def cut(self,text): 66 | """ 67 | """ 68 | text = text.decode('utf-8','ignore') 69 | p = self.d 70 | ln = len(text) 71 | i = ln 72 | j = 0 73 | z = ln 74 | q = 0 75 | recognised = [] 76 | mem = None 77 | mem2 = None 78 | while i-j>0: 79 | t = text[i-j-1].lower() 80 | #print i,j,t,mem 81 | if not (t in p): 82 | if (mem!=None) or (mem2!=None): 83 | if mem!=None: 84 | i,j,z = mem 85 | mem = None 86 | elif mem2!=None: 87 | delta = mem2[0]-i 88 | if delta>=1: 89 | if (delta<5) and (re.search(ur"[\w\u2E80-\u9FFF]",t)!=None): 90 | pre = text[i-j] 91 | #print pre 92 | if not (pre in self.specialwords): 93 | i,j,z,q = mem2 94 | del recognised[q:] 95 | mem2 = None 96 | 97 | p = self.d 98 | if((i1))): 118 | #print text[i-1] 119 | mem = None 120 | mem2 = i,j,z,len(recognised) 121 | p = self.d 122 | i -= 1 123 | j = 0 124 | continue 125 | #print mem 126 | p = self.d 127 | #print i,j,z,text[i:z] 128 | if((i 17 | {%- endblock %} 18 | {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} 19 | {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} 20 | {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and 21 | (sidebars != []) %} 22 | {%- set url_root = pathto('', 1) %} 23 | {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} 24 | 25 | {%- macro relbar() %} 26 | 44 | {%- endmacro %} 45 | 46 | {%- macro sidebar() %} 47 | {%- if render_sidebar %} 48 |
49 |
50 | {%- block sidebarlogo %} 51 | 54 | {%- endblock %} 55 | {%- if sidebars != None %} 56 | {#- new style sidebar: explicitly include/exclude templates #} 57 | {%- for sidebartemplate in sidebars %} 58 | {%- include sidebartemplate %} 59 | {%- endfor %} 60 | {%- else %} 61 | {#- old style sidebars: using blocks -- should be deprecated #} 62 | {%- block sidebartoc %} 63 | {%- include "localtoc.html" %} 64 | {%- endblock %} 65 | {%- block sidebarrel %} 66 | {%- include "relations.html" %} 67 | {%- endblock %} 68 | {%- block sidebarsourcelink %} 69 | {%- include "sourcelink.html" %} 70 | {%- endblock %} 71 | {%- if customsidebar %} 72 | {%- include customsidebar %} 73 | {%- endif %} 74 | {%- block sidebarsearch %} 75 | {%- include "searchbox.html" %} 76 | {%- endblock %} 77 | {%- endif %} 78 |
79 |
80 | {%- endif %} 81 | {%- endmacro %} 82 | 83 | 84 | 85 | 86 | {{ metatags }} 87 | {%- if not embedded and docstitle %} 88 | {%- set titlesuffix = " — "|safe + docstitle|e %} 89 | {%- else %} 90 | {%- set titlesuffix = "" %} 91 | {%- endif %} 92 | {%- block htmltitle %} 93 | {{ title|striptags|e }}{{ titlesuffix }} 94 | {%- endblock %} 95 | 96 | 97 | {%- for cssfile in css_files %} 98 | 99 | {%- endfor %} 100 | {%- if not embedded %} 101 | 110 | 111 | {%- for scriptfile in script_files %} 112 | 113 | {%- endfor %} 114 | {%- if use_opensearch %} 115 | 118 | {%- endif %} 119 | {%- if favicon %} 120 | 121 | {%- endif %} 122 | {%- endif %} 123 | {%- block linktags %} 124 | {%- if hasdoc('about') %} 125 | 126 | {%- endif %} 127 | {%- if hasdoc('genindex') %} 128 | 129 | {%- endif %} 130 | {%- if hasdoc('search') %} 131 | 132 | {%- endif %} 133 | {%- if hasdoc('copyright') %} 134 | 135 | {%- endif %} 136 | 137 | {%- if parents %} 138 | 139 | {%- endif %} 140 | {%- if next %} 141 | 142 | {%- endif %} 143 | {%- if prev %} 144 | 145 | {%- endif %} 146 | {%- endblock %} 147 | {%- block extrahead %} {% endblock %} 148 | 149 | 150 | {%- block header %}{% endblock %} 151 | 152 | {%- block relbar1 %}{{ relbar() }}{% endblock %} 153 | 154 | {%- block content %} 155 | {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} 156 | 157 |
158 | {%- block document %} 159 |
160 | {%- if render_sidebar %} 161 |
162 | {%- endif %} 163 |
164 | {% block body %} {% endblock %} 165 |
166 | {%- if render_sidebar %} 167 |
168 | {%- endif %} 169 |
170 | {%- endblock %} 171 | 172 | {%- block sidebar2 %}{{ sidebar() }}{% endblock %} 173 |
174 |
175 | {%- endblock %} 176 | 177 | {%- block relbar2 %}{{ relbar() }}{% endblock %} 178 | 179 | {%- block footer %} 180 | 195 | {%- endblock %} 196 | 197 | 198 | -------------------------------------------------------------------------------- /exts/theme/book/static/anim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/anim.png -------------------------------------------------------------------------------- /exts/theme/book/static/book.css: -------------------------------------------------------------------------------- 1 | /* 2 | * sphinxdoc.css_t 3 | * ~~~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- sphinxdoc theme. Originally created by 6 | * Armin Ronacher for Werkzeug. 7 | * 8 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 9 | * :license: BSD, see LICENSE for details. 10 | * 11 | */ 12 | 13 | @import url("basic.css"); 14 | 15 | /* -- page layout ----------------------------------------------------------- */ 16 | 17 | body { 18 | font-family: segoe UI,sans-serif; 19 | font-size: 16px; 20 | letter-spacing: -0.01em; 21 | line-height: 150%; 22 | text-align: center; 23 | background-color: #E7E7E2; 24 | color: black; 25 | padding: 0; 26 | border: 1px solid #aaa; 27 | margin: 0px 140px 0px 140px; 28 | min-width: 900px; 29 | } 30 | 31 | div.document { 32 | background-color: white; 33 | text-align: left; 34 | background-image: url(contents.png); 35 | background-repeat: repeat-x; 36 | } 37 | 38 | div.bodywrapper { 39 | margin: 0 0 0 240px; 40 | border-left: 1px solid #ccc; 41 | } 42 | 43 | div.documentwrapper { 44 | float: left; 45 | width: 100%; 46 | } 47 | 48 | div.body { 49 | margin: 0; 50 | padding: 0.5em 20px 20px 20px; 51 | } 52 | 53 | div.related { 54 | font-size: 1em; 55 | } 56 | 57 | div.related ul { 58 | background-image: url(navigation.png); 59 | height: 2em; 60 | border-top: 1px solid #ddd; 61 | border-bottom: 1px solid #ddd; 62 | } 63 | 64 | div.related ul li { 65 | margin: 0; 66 | padding: 0; 67 | height: 2em; 68 | float: left; 69 | } 70 | 71 | div.related ul li.right { 72 | float: right; 73 | margin-right: 5px; 74 | } 75 | 76 | div.related ul li a { 77 | margin: 0; 78 | padding: 0 5px 0 5px; 79 | line-height: 1.75em; 80 | color: #224466; 81 | } 82 | 83 | div.related ul li a:hover { 84 | color: #3CA8E7; 85 | } 86 | 87 | div.sphinxsidebarwrapper { 88 | padding: 0; 89 | } 90 | 91 | div.sphinxsidebar { 92 | margin: 0; 93 | padding: 0.5em 15px 15px 15px; 94 | margin-left: -100%; 95 | width: 210px; 96 | float: left; 97 | font-size: 1em; 98 | text-align: left; 99 | } 100 | 101 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 102 | margin: 1em 0 0.5em 0; 103 | font-size: 1em; 104 | padding: 0.1em 0 0.1em 0.5em; 105 | color: white; 106 | border: 1px solid #86989B; 107 | background-color: #666; 108 | } 109 | 110 | div.sphinxsidebar h3 a { 111 | color: white; 112 | } 113 | 114 | div.sphinxsidebar ul { 115 | padding-left: 1.5em; 116 | margin-top: 7px; 117 | padding: 0; 118 | line-height: 130%; 119 | } 120 | 121 | div.sphinxsidebar ul ul { 122 | margin-left: 20px; 123 | } 124 | 125 | div.footer { 126 | background-color: #444444; 127 | color: #ffffff; 128 | padding: 3px 8px 3px 0; 129 | clear: both; 130 | font-size: 0.8em; 131 | text-align: right; 132 | } 133 | 134 | div.footer a { 135 | color: #ffffff; 136 | text-decoration: underline; 137 | } 138 | 139 | /* -- body styles ----------------------------------------------------------- */ 140 | 141 | p { 142 | margin: 0.8em 0 0.5em 0; 143 | } 144 | 145 | a { 146 | color: #224466; 147 | text-decoration: none; 148 | } 149 | 150 | a:hover { 151 | color: #2491CF; 152 | } 153 | 154 | div.body a { 155 | text-decoration: underline; 156 | } 157 | 158 | h1 { 159 | margin: 0; 160 | padding: 0.7em 0 0.3em 0; 161 | font-size: 1.8em; 162 | } 163 | 164 | h2 { 165 | margin: 1.3em 0 0.2em 0; 166 | font-size: 1.35em; 167 | padding: 0; 168 | } 169 | 170 | h3 { 171 | margin: 1em 0 -0.3em 0; 172 | font-size: 1.2em; 173 | } 174 | 175 | div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { 176 | color: black!important; 177 | } 178 | 179 | h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { 180 | display: none; 181 | margin: 0 0 0 0.3em; 182 | padding: 0 0.2em 0 0.2em; 183 | color: #aaa!important; 184 | } 185 | 186 | h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, 187 | h5:hover a.anchor, h6:hover a.anchor { 188 | display: inline; 189 | } 190 | 191 | h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, 192 | h5 a.anchor:hover, h6 a.anchor:hover { 193 | color: #777; 194 | background-color: #eee; 195 | } 196 | 197 | a.headerlink { 198 | color: #c60f0f!important; 199 | font-size: 1em; 200 | margin-left: 6px; 201 | padding: 0 4px 0 4px; 202 | text-decoration: none!important; 203 | } 204 | 205 | a.headerlink:hover { 206 | background-color: #ccc; 207 | color: white!important; 208 | } 209 | 210 | cite, code, tt { 211 | font-family: 'Consolas', 'Deja Vu Sans Mono', 212 | 'Bitstream Vera Sans Mono', monospace; 213 | font-size: 0.95em; 214 | letter-spacing: 0.01em; 215 | } 216 | 217 | tt { 218 | background-color: #f2f2f2; 219 | border-bottom: 1px solid #ddd; 220 | color: #333; 221 | } 222 | 223 | tt.descname, tt.descclassname, tt.xref { 224 | border: 0; 225 | } 226 | 227 | hr { 228 | border: 1px solid #abc; 229 | margin: 2em; 230 | } 231 | 232 | a tt { 233 | border: 0; 234 | color: #CA7900; 235 | } 236 | 237 | a tt:hover { 238 | color: #2491CF; 239 | } 240 | 241 | pre { 242 | font-family: 'Consolas', 'Deja Vu Sans Mono', 243 | 'Bitstream Vera Sans Mono', monospace; 244 | font-size: 0.95em; 245 | letter-spacing: 0.015em; 246 | line-height: 120%; 247 | padding: 0.5em; 248 | border: 1px solid #ccc; 249 | background-color: #f8f8f8; 250 | } 251 | 252 | pre a { 253 | color: inherit; 254 | text-decoration: underline; 255 | } 256 | 257 | td.linenos pre { 258 | padding: 0.5em 0; 259 | } 260 | 261 | div.quotebar { 262 | background-color: #f8f8f8; 263 | max-width: 250px; 264 | float: right; 265 | padding: 2px 7px; 266 | border: 1px solid #ccc; 267 | } 268 | 269 | div.topic { 270 | background-color: #f8f8f8; 271 | } 272 | 273 | table { 274 | border-collapse: collapse; 275 | margin: 0 -0.5em 0 -0.5em; 276 | } 277 | 278 | table td, table th { 279 | padding: 0.2em 0.5em 0.2em 0.5em; 280 | } 281 | 282 | div.admonition, div.warning { 283 | font-size: 0.9em; 284 | margin: 1em 0 1em 0; 285 | border: 1px solid #86989B; 286 | background-color: #f7f7f7; 287 | padding: 0; 288 | } 289 | 290 | div.admonition p, div.warning p { 291 | margin: 0.5em 1em 0.5em 1em; 292 | padding: 0; 293 | } 294 | 295 | div.admonition pre, div.warning pre { 296 | margin: 0.4em 1em 0.4em 1em; 297 | } 298 | 299 | div.admonition p.admonition-title, 300 | div.warning p.admonition-title { 301 | margin: 0; 302 | padding: 0.1em 0 0.1em 0.5em; 303 | color: white; 304 | border-bottom: 1px solid #86989B; 305 | font-weight: bold; 306 | background-color: #AFC1C4; 307 | } 308 | 309 | div.warning { 310 | border: 1px solid #940000; 311 | } 312 | 313 | div.warning p.admonition-title { 314 | background-color: #CF0000; 315 | border-bottom-color: #940000; 316 | } 317 | 318 | div.admonition ul, div.admonition ol, 319 | div.warning ul, div.warning ol { 320 | margin: 0.1em 0.5em 0.5em 3em; 321 | padding: 0; 322 | } 323 | 324 | div.versioninfo { 325 | margin: 1em 0 0 0; 326 | border: 1px solid #ccc; 327 | background-color: #DDEAF0; 328 | padding: 8px; 329 | line-height: 1.3em; 330 | font-size: 0.9em; 331 | } 332 | 333 | .viewcode-back { 334 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 335 | 'Verdana', sans-serif; 336 | } 337 | 338 | div.viewcode-block:target { 339 | background-color: #f4debf; 340 | border-top: 1px solid #ac9; 341 | border-bottom: 1px solid #ac9; 342 | } 343 | 344 | i, cite, em, var, address { 345 | font-style: normal; 346 | } 347 | 348 | .prebc { 349 | color: #F8F8F8; 350 | } 351 | 352 | .codenumber { 353 | color:black; 354 | font-family:segoe UI,sans-serif; 355 | } 356 | 357 | div.imagebox { 358 | min-height:25px; 359 | border:1px solid #777777; 360 | background-color:#eeeeee; 361 | background-repeat: no-repeat; 362 | padding-left:40px; 363 | padding-top:4px; 364 | padding-bottom:4px; 365 | padding-right:4px; 366 | background-position: 5px center; 367 | margin:5px; 368 | font-size:14px; 369 | } 370 | 371 | div.imagebox p{ 372 | margin:2px; 373 | line-height:100%; 374 | } 375 | 376 | form.search input[name=q]{ 377 | width:200px; 378 | } 379 | 380 | form.search input[type=submit]{ 381 | width:50px; 382 | } 383 | 384 | div.sphinxsidebar input { 385 | border: 1px solid #666; 386 | font-family:segoe UI,sans-serif; 387 | } 388 | 389 | div.figure p.caption{ 390 | padding-left:20px; 391 | background-image: url(picture.png); 392 | background-repeat:no-repeat; 393 | background-position:center left; 394 | } 395 | 396 | table.docutils{ 397 | border 1px solid #666; 398 | } 399 | 400 | table.docutils caption{ 401 | background-color: #eee; 402 | } 403 | 404 | p.logo{text-align:center;} 405 | -------------------------------------------------------------------------------- /exts/theme/book/static/booktools.js: -------------------------------------------------------------------------------- 1 | jQuery.cookie = function(name, value, options) { 2 | if (typeof value != 'undefined') { // name and value given, set cookie 3 | options = options || {}; 4 | if (value === null) { 5 | value = ''; 6 | options.expires = -1; 7 | } 8 | var expires = ''; 9 | if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { 10 | var date; 11 | if (typeof options.expires == 'number') { 12 | date = new Date(); 13 | date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); 14 | } else { 15 | date = options.expires; 16 | } 17 | expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE 18 | } 19 | var path = options.path ? '; path=' + options.path : ''; 20 | var domain = options.domain ? '; domain=' + options.domain : ''; 21 | var secure = options.secure ? '; secure' : ''; 22 | document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); 23 | } else { // only name given, get cookie 24 | var cookieValue = null; 25 | if (document.cookie && document.cookie != '') { 26 | var cookies = document.cookie.split(';'); 27 | for (var i = 0; i < cookies.length; i++) { 28 | var cookie = jQuery.trim(cookies[i]); 29 | // Does this cookie string begin with the name we want? 30 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 31 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 32 | break; 33 | } 34 | } 35 | } 36 | return cookieValue; 37 | } 38 | }; 39 | 40 | hide_sidebar = function(){ 41 | $("div.bodywrapper").css("margin-left", "0px"); 42 | $("div.sphinxsidebar").hide(); 43 | $.cookie("hide_sidebar","1"); 44 | }; 45 | 46 | show_sidebar = function(){ 47 | $("div.bodywrapper").css("margin-left", "240px"); 48 | $("div.sphinxsidebar").show(); 49 | $.cookie("hide_sidebar", "", {expires:-1}); 50 | } 51 | 52 | $(function(){ 53 | $('
  • 切换侧栏(ALT+X) |
  • ') 54 | .insertBefore($(".related:first li:eq(3)")); 55 | 56 | $("a#toggle_sidebar").toggle(hide_sidebar, show_sidebar); 57 | 58 | $(window).keydown(function(event){ 59 | if(event.altKey && event.keyCode == 88) $("a#toggle_sidebar").click(); 60 | }); 61 | 62 | }); -------------------------------------------------------------------------------- /exts/theme/book/static/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/code.png -------------------------------------------------------------------------------- /exts/theme/book/static/comment.css: -------------------------------------------------------------------------------- 1 | div.comment { 2 | position:absolute; 3 | left: -9999px; 4 | top: -9999px; 5 | } 6 | div.comment a.email_link { 7 | display: block; 8 | width: 16px; 9 | height: 16px; 10 | cursor: pointer; 11 | color: white; 12 | text-decoration:none; 13 | background: transparent url(./comment_white_yellow.gif) no-repeat -16px 0; 14 | } 15 | 16 | div.comment a.email_link:hover{ 17 | background-position:0 0; 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /exts/theme/book/static/comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | 110414 lizziesky: 3 | 仅包含 生成 comments 小icon, 且点击icon 跳转到groups的逻辑, 4 | - 优化了DOM结构, 5 | - 去除了 window.resize 事件 6 | - 去除 仿google code 文档的侧栏切换 7 | */ 8 | DESELEMENT = "h1,h2,h3,h4,.highlight-python";//"h1,h2,h3,h4,ul,div.section p,div.highlight-python"; 9 | 10 | function clean_tag(st){ 11 | return st.replace(/<[^>]+>?[^<]*>/g, ''); 12 | } 13 | 14 | $(document).ready(function(){ 15 | $("div.body > div.section").find(DESELEMENT).each(function() { 16 | if (!$(this).prev("div.comment").length) { 17 | var cmt = $('
    '); 18 | $(this).before(cmt); 19 | cmt.offset({ 20 | left: $(this).parents('.section').offset().left - 20, 21 | top: $(this).offset().top 22 | }); 23 | } 24 | }); 25 | 26 | $("a.email_link").hover(function(){ 27 | if ($(this).attr("href") == null||$(this).attr("href") == '') { 28 | var sub = $("div.documentwrapper div.body div.section:first h1").html(); 29 | var body = $(this).parent("div.comment").next().html(); 30 | // collection doc info from DOCUMENTATION_OPTIONS 31 | sub = clean_tag(sub); 32 | 33 | body = clean_tag(body); 34 | if (body.length>100) { 35 | body = body.substring(0, 100)+"..."; 36 | } 37 | //091117:Zoomq change comment aim 38 | //$(this).attr("href", "https://groups.google.com/group/obp-comment/post?hl=zh-CN&subject="+encodeURIComponent(sub)+"&body="+encodeURIComponent(body)); 39 | //$(this).attr("href", "https://bitbucket.org/ZoomQuiet/obp.rwiwpyzh/issues/new"); 40 | $(this).attr("href", "https://github.com/taobao/nginx-book/issues/new?title="+encodeURIComponent(sub)+"+"+encodeURIComponent(body)+"&body="+encodeURIComponent("current content:\n ...\nadvice:\n ...\nreason:\n ...\n")); 41 | $(this).attr("target", "_blank"); 42 | } 43 | }, function(){ 44 | }); 45 | 46 | }); 47 | 48 | -------------------------------------------------------------------------------- /exts/theme/book/static/comment_white_yellow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/comment_white_yellow.gif -------------------------------------------------------------------------------- /exts/theme/book/static/contents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/contents.png -------------------------------------------------------------------------------- /exts/theme/book/static/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/link.png -------------------------------------------------------------------------------- /exts/theme/book/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/logo.png -------------------------------------------------------------------------------- /exts/theme/book/static/logo_sphinxbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/logo_sphinxbook.png -------------------------------------------------------------------------------- /exts/theme/book/static/navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/navigation.png -------------------------------------------------------------------------------- /exts/theme/book/static/picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/picture.png -------------------------------------------------------------------------------- /exts/theme/book/static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: normal } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: normal } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: normal } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: normal } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #303030 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: normal } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: normal } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /exts/theme/book/static/think.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/think.png -------------------------------------------------------------------------------- /exts/theme/book/static/tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/tip.png -------------------------------------------------------------------------------- /exts/theme/book/static/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/exts/theme/book/static/warning.png -------------------------------------------------------------------------------- /exts/theme/book/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = book.css 4 | pygments_style = friendly -------------------------------------------------------------------------------- /exts/zh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from sphinx.search import SearchLanguage 3 | from smallseg import SEG 4 | 5 | class SearchChinese(SearchLanguage): 6 | lang = 'zh' 7 | 8 | def init(self, options): 9 | print "reading Chiniese dictionary" 10 | self.seg = SEG() 11 | 12 | def split(self, input): 13 | return self.seg.cut(input.encode("utf8")) 14 | 15 | def word_filter(self, stemmed_word): 16 | return len(stemmed_word) > 1 -------------------------------------------------------------------------------- /source/appendix_a.rst: -------------------------------------------------------------------------------- 1 | 附录A 编码风格 (100%) 2 | ======================= 3 | 4 | Nginx代码风格图示 (100%) 5 | --------------------------- 6 | 7 | 一、基本原则 8 | 9 | K&R编码风格(偏BSD子类)。 10 | 11 | 每行不能超过80列。 12 | 13 | 不用TAB对齐,用空格。 14 | 15 | 默认对齐单元是4个空格。 16 | 17 | 除宏定义外,字母均为小写,单词间用下划线_间隔。 18 | 19 | 使用C方式的注释,不得使用//形式注释。 20 | 21 | 中缀运算符的前后须空一格,如3 + 2以及a > 3。 22 | 23 | 逗号后须空一格,如foo(a, b, c); 24 | 25 | 二、风格图示 26 | 27 | .. image:: http://tengine.taobao.org/book/_images/code-style-1.JPG 28 | 29 | 1、 if/while/for/switch语句的左花括号和关键字在同一行上,和括号之间空一个空格。 30 | 31 | 2、 else关键字和两个花括号在同一行上。 32 | 33 | .. image:: http://tengine.taobao.org/book/_images/code-style-2.JPG 34 | :width: 550px 35 | 36 | 3、 文件开始的注释空一行。 37 | 38 | 4、 较为完整的代码块间的距离为空两行。如函数声明、函数定义之间等。 39 | 40 | 5、 函数声明或定义若一行显示不下,则函数原型空4个空格。 41 | 42 | 6、 结构体数组的花括号和内容之间空一个空格。 43 | 44 | .. image:: http://tengine.taobao.org/book/_images/code-style-3.JPG 45 | 46 | 7、 结构体数组的左花括号放在同一行上。 47 | 48 | 8、 较大的结构体数组元素最开始空一行。 49 | 50 | 9、 元素内容上下对齐。 51 | 52 | .. image:: http://tengine.taobao.org/book/_images/code-style-4.JPG 53 | 54 | 10、注释上下对齐。 55 | 56 | .. image:: http://tengine.taobao.org/book/_images/code-style-5.JPG 57 | 58 | 11、函数调用折行时,参数上下对齐。 59 | 60 | .. image:: http://tengine.taobao.org/book/_images/code-style-6.JPG 61 | :width: 550px 62 | 63 | 12、函数定义时,类型单独一行。 64 | 65 | 13、变量声明的类型上下排列按照从短到长的顺序。注意,最下面的变量的类型和名称间的空格为2-3个。一般情况下为2个,这是Nginx中最小的变量声明中类型和名称的距离。 66 | 67 | 14、变量名称上下对齐——字母对齐,不包括指针的\*号。 68 | 69 | .. image:: http://tengine.taobao.org/book/_images/code-style-7.JPG 70 | 71 | 15、结构体内变量上下对齐(字母,不包括指针的的\*号)。 72 | 73 | .. image:: http://tengine.taobao.org/book/_images/code-style-8.JPG 74 | :width: 550px 75 | 76 | 16、单行注释格式为/\* something \*/ 77 | 78 | .. image:: http://tengine.taobao.org/book/_images/code-style-9.JPG 79 | :width: 550px 80 | 81 | 17、多行注释的格式为: 82 | 83 | .. code:: c 84 | 85 | /* 86 | * something 87 | */ 88 | 89 | .. image:: http://tengine.taobao.org/book/_images/code-style-10.JPG 90 | :width: 550px 91 | 92 | 18、函数定义的左花括号独占一行。 93 | 94 | 19、switch语句中,switch和case关键字上下对齐。 95 | 96 | .. image:: http://tengine.taobao.org/book/_images/code-style-11.JPG 97 | :width: 550px 98 | 99 | 20、当条件表达式过长需要折行时,关系运算符须位于下一行的行首,并与上一行的条件表达式的第一个字符对齐,同时右花括号须位于单独的一行,并与if/while等关键字对齐。 100 | 101 | .. image:: http://tengine.taobao.org/book/_images/code-style-12.JPG 102 | :width: 550px 103 | 104 | 21、 else语句之前须空出一行。 105 | 106 | .. image:: http://tengine.taobao.org/book/_images/code-style-13.JPG 107 | 108 | 22、在函数中,相同类型的变量声明放在一行上。 109 | -------------------------------------------------------------------------------- /source/appendix_b.rst: -------------------------------------------------------------------------------- 1 | 附录B 常用API 2 | ================= 3 | 4 | 5 | 6 | B.1 字符串操作 7 | +++++++++++++++++++ 8 | 9 | 10 | 11 | B.2 读取请求体 12 | +++++++++++++++++++ 13 | 14 | 15 | 16 | B.3 操作请求头 17 | +++++++++++++++++++ 18 | 19 | 20 | 21 | B.4 取参数 22 | +++++++++++++ 23 | 24 | 25 | 26 | B.5 URI处理 27 | +++++++++++++ 28 | 29 | 30 | 31 | B.6 buffer操作 32 | ++++++++++++++++ 33 | 34 | 35 | 36 | B.7 变量操作 37 | ++++++++++++++++ 38 | 39 | 40 | 41 | B.8 日志输出 42 | ++++++++++++++++ 43 | 44 | 45 | 46 | B.9 配置解析接口 47 | ++++++++++++++++++++++ 48 | 49 | 50 | 51 | B.10 文件操作 52 | +++++++++++++++++ 53 | 54 | 55 | 56 | B.11 锁操作 57 | ++++++++++++++ 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /source/appendix_c.rst: -------------------------------------------------------------------------------- 1 | 附录C 模块编译,调试与测试 2 | ====================================== 3 | 4 | 5 | 6 | C.1 编译与安装 7 | ++++++++++++++++++++ 8 | 9 | 环境要求 10 | ^^^^^^^^^^^^^^^^^^^^^^^^ 11 | 12 | 操作系统:目前Nginx各版本在以下操作系统和平台测试通过: 13 | 14 | FreeBSD 3 — 10 / i386; FreeBSD 5 — 10 / amd64; 15 | 16 | Linux 2.2 — 3 / i386; Linux 2.6 — 3 / amd64; 17 | 18 | Solaris 9 / i386, sun4u; Solaris 10 / i386, amd64, sun4v; 19 | 20 | AIX 7.1 / powerpc; 21 | 22 | HP-UX 11.31 / ia64; 23 | 24 | MacOS X / ppc, i386; 25 | 26 | Windows XP, Windows Server 2003 27 | 28 | 磁盘空间:必须保证至少10M以上的磁盘空间,并且随着编译设置及第三方模块的安装而有所不同; 29 | 30 | 编译器及相关工具: 必须确保操作系统安装有GCC编译器;make工具;用户可通过yum命令安装编译器及相关工具:yum -y install gcc gcc-c++ make; 31 | 32 | 模块依赖性:Nginx的一些模块需要第三方库的支持,如rewrite模块需要pcre库,gzip模块需要zlib模块,ssl功能需要openssl库等。用户可通过yum命令安装这些依赖库:yum -y install pcre pcre-devel zlib zlib-devel openssl openssl-devel; 33 | 34 | 35 | 下载 36 | ^^^^^^^^^^^^^^^^^^^^^^^^ 37 | 38 | Nginx是开源软件,用户可以访问 http://nginx.org/ 网站获取源码包或Windows二进制文件下载。其中1.3.x版本为开发版本,1.2.x版本为稳定版本。开发版本分支会较快的获得新功能和缺陷修复,但同时也可能会遇到新的缺陷。一旦更新稳定下来,就会被加入稳定版本分支。 39 | 40 | 作为生产环境,通常建议用户使用稳定版本。 41 | 42 | Nginx在Windows环境下安装 43 | ^^^^^^^^^^^^^^^^^^^^^^^^ 44 | 45 | Nginx的windows版本使用原生win32 API(非Cygwin模拟层)。当前存在的已知问题:1.采用select作为通知方法,所以不具备很高的性能和扩展性;2.虽然可以启动若干工作进程运行,实际上只有一个进程在处理所有请求;3.一个工作进程只能处理不超过1024个并发连接;4.缓存和其他需要共享内存支持的模块在windows vista及后续版本的操作系统中无法工作,因为在这些操作系统中,地址空间的布局是随机的;5.除了XSLT过滤器、图像过滤器、GeoIP模块和嵌入Perl语言支持以外,Nginx的Windows版本与Unix版本相比,功能几乎齐全。 46 | 47 | 安装Nginx的Windows版本,建议下载最新的1.3.13开发版本,因为开发分支上包含了所有已知的问题修复,尤其是针对Windows版本的问题修复。解压下载得到的zip文件,进入nginx-1.3.13目录,运行nginx。 48 | 49 | 50 | C盘根目录下安装例子 51 | 52 | .. code:: 53 | 54 | cd c:\ 55 | unzip nginx-1.3.13.zip 56 | cd nginx-1.3.13 57 | start nginx 58 | 59 | 60 | Nginx的Windows版本的控制命令包含如下: 61 | 62 | .. code:: 63 | 64 | nginx -s stop 快速退出 65 | nginx -s quit 优雅退出 66 | nginx -s reload 更换配置,启动新的工作进程,优雅的关闭以往的工作进程 67 | nginx -s reopen 重新打开日志文件 68 | 69 | Nginx在Linux环境下安装 70 | ^^^^^^^^^^^^^^^^^^^^^^^^ 71 | 72 | Nginx在Linux环境下可以通过编译源码的方式安装,最简单的安装命令如下: 73 | 74 | .. code:: 75 | 76 | wget http://nginx.org/download/nginx-1.2.0.tar.gz 77 | tar zxvf nginx-1.2.0.tar.gz 78 | cd nginx-1.2.0 79 | ./configure 80 | make 81 | sudo make install 82 | 83 | 按照以上命令,Nginx将被默认安装到/usr/local/nginx目录下。用户可以通过./configure --help命令查看Nginx可选择的编译选项进行自定义安装配置。 84 | 85 | Nginx的configure脚本支持以下选项: 86 | 87 | .. code:: 88 | 89 | --prefix= #Nginx安装路径。如果没有指定,默认为 /usr/local/nginx 90 | 91 | --sbin-path= #Nginx可执行文件安装路径。只能安装时指定,如果没有指定,默认为/sbin/nginx 92 | 93 | --conf-path= #在没有给定-c选项下默认的nginx.conf的路径。如果没有指定,默认为/conf/nginx.conf 94 | 95 | --pid-path= #在nginx.conf中没有指定pid指令的情况下,默认的nginx.pid的路径。如果没有指定,默认为 /logs/nginx.pid 96 | 97 | --lock-path= #nginx.lock文件的路径 98 | 99 | --error-log-path= #在nginx.conf中没有指定error_log指令的情况下,默认的错误日志的路径。如果没有指定,默认为 /logs/error.log 100 | 101 | --http-log-path= #在nginx.conf中没有指定access_log指令的情况下,默认的访问日志的路径。如果没有指定,默认为 /logs/access.log。 102 | 103 | --user= #在nginx.conf中没有指定user指令的情况下,默认的nginx使用的用户。如果没有指定,默认为 nobody 104 | 105 | --group= #在nginx.conf中没有指定user指令的情况下,默认的nginx使用的组。如果没有指定,默认为 nobody 106 | 107 | --builddir=DIR #指定编译的目录 108 | 109 | --with-rtsig_module #启用 rtsig 模块 110 | 111 | --with-select_module(--without-select_module) #允许或不允许开启SELECT模式,如果configure没有找到合适的模式,比如,kqueue(sun os)、epoll(linux kenel 2.6+)、rtsig(实时信号)或/dev/poll(一种类似select的模式,底层实现与SELECT基本相同,都是采用轮询的方法),SELECT模式将是默认安装模式 112 | 113 | --with-poll_module(--without-poll_module) #允许或不允许开启POLL模式,如果没有合适的模式,比如:kqueue(sun os)、epoll(liunx kernel 2.6+),则开启该模式 114 | 115 | --with-http_ssl_module #开启HTTP SSL模块,使NGINX可以支持HTTPS请求。这个模块需要已经安装了OPENSSL,在DEBIAN上是libssl 116 | 117 | --with-http_realip_module #启用 ngx_http_realip_module 118 | 119 | --with-http_addition_module #启用 ngx_http_addition_module 120 | 121 | --with-http_sub_module #启用 ngx_http_sub_module 122 | 123 | --with-http_dav_module #启用 ngx_http_dav_module 124 | 125 | --with-http_flv_module #启用 ngx_http_flv_module 126 | 127 | --with-http_stub_status_module #启用 "server status" 页 128 | 129 | --without-http_charset_module #禁用 ngx_http_charset_module 130 | 131 | --without-http_gzip_module #禁用 ngx_http_gzip_module. 如果启用,需要 zlib 。 132 | 133 | --without-http_ssi_module #禁用 ngx_http_ssi_module 134 | 135 | --without-http_userid_module #禁用 ngx_http_userid_module 136 | 137 | --without-http_access_module #禁用 ngx_http_access_module 138 | 139 | --without-http_auth_basic_module #禁用 ngx_http_auth_basic_module 140 | 141 | --without-http_autoindex_module #禁用 ngx_http_autoindex_module 142 | 143 | --without-http_geo_module #禁用 ngx_http_geo_module 144 | 145 | --without-http_map_module #禁用 ngx_http_map_module 146 | 147 | --without-http_referer_module #禁用 ngx_http_referer_module 148 | 149 | --without-http_rewrite_module #禁用 ngx_http_rewrite_module. 如果启用需要 PCRE 。 150 | 151 | --without-http_proxy_module #禁用 ngx_http_proxy_module 152 | 153 | --without-http_fastcgi_module #禁用 ngx_http_fastcgi_module 154 | 155 | --without-http_memcached_module #禁用 ngx_http_memcached_module 156 | 157 | --without-http_limit_zone_module #禁用 ngx_http_limit_zone_module 158 | 159 | --without-http_empty_gif_module #禁用 ngx_http_empty_gif_module 160 | 161 | --without-http_browser_module #禁用 ngx_http_browser_module 162 | 163 | --without-http_upstream_ip_hash_module #禁用 ngx_http_upstream_ip_hash_module 164 | 165 | --with-http_perl_module #启用 ngx_http_perl_module 166 | 167 | --with-perl_modules_path=PATH #指定 perl 模块的路径 168 | 169 | --with-perl=PATH #指定 perl 执行文件的路径 170 | 171 | --http-log-path=PATH #指定http默认访问日志的路径 172 | 173 | --http-client-body-temp-path=PATH #指定http客户端请求缓存文件存放目录的路径 174 | 175 | --http-proxy-temp-path=PATH #指定http反向代理缓存文件存放目录的路径 176 | 177 | --http-fastcgi-temp-path=PATH #指定http FastCGI缓存文件存放目录的路径 178 | 179 | --without-http #禁用 HTTP server 180 | 181 | --with-mail #启用 IMAP4/POP3/SMTP 代理模块 182 | 183 | --with-mail_ssl_module #启用 ngx_mail_ssl_module 184 | 185 | --with-cc=PATH #指定 C 编译器的路径 186 | 187 | --with-cpp=PATH #指定 C 预处理器的路径 188 | 189 | --with-cc-opt=OPTIONS #设置C编译器的额外选项 190 | 191 | --with-ld-opt=OPTIONS #设置链接的额外选项 192 | 193 | --with-cpu-opt=CPU #为特定的 CPU 编译,有效的值包括:pentium, pentiumpro, pentium3, pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64 194 | 195 | --without-pcre #禁止 PCRE 库的使用。同时也会禁止 HTTP rewrite 模块。在 "location" 配置指令中的正则表达式也需要 PCRE 196 | 197 | --with-pcre=DIR #指定 PCRE 库的源代码的路径 198 | 199 | --with-pcre-opt=OPTIONS #设置PCRE的额外编译选项 200 | 201 | --with-md5=DIR #使用MD5汇编源码 202 | 203 | --with-md5-opt=OPTIONS #设置MD5库的额外编译选项 204 | 205 | --with-md5-asm #使用MD5汇编源码 206 | 207 | --with-sha1=DIR #设置sha1库的源代码路径 208 | 209 | --with-sha1-opt=OPTIONS #设置sha1库的额外编译选项 210 | 211 | --with-sha1-asm #使用sha1汇编源码 212 | 213 | --with-zlib=DIR #设置zlib库的源代码路径 214 | 215 | --with-zlib-opt=OPTIONS #设置zlib库的额外编译选项 216 | 217 | --with-zlib-asm=CPU #zlib针对CPU的优化,合法的值是: pentium, pentiumpro 218 | 219 | --with-openssl=DIR #设置OpenSSL库的源代码路径 220 | 221 | --with-openssl-opt=OPTIONS #设置OpenSSL库的额外编译选项 222 | 223 | --with-debug #启用调试日志 224 | 225 | --add-module=PATH #添加一个在指定路径中能够找到的第三方模块 226 | 227 | 228 | 在不同版本间,选项可能会有些许变化,请总是使用./configure --help命令来检查当前的选项列表。 229 | 230 | 测试 231 | ^^^^^^^^^^^^^^^^^^^^^^^^ 232 | 233 | 将Nginx conf文件的server block部分的配置如下: 234 | 235 | .. code:: 236 | 237 | server { 238 | listen 80; 239 | server_name localhost; 240 | 241 | location / { 242 | root html; 243 | index index.html index.htm; 244 | } 245 | 246 | # redirect server error pages to the static page /50x.html 247 | error_page 500 502 503 504 /50x.html; 248 | location = /50x.html { 249 | root html; 250 | } 251 | } 252 | 253 | 254 | 用户可以通过访问“http://localhost:80/index.html”页面来查看Nginx的欢迎页面。 255 | 256 | 257 | 在Windows环境下查看nginx进程 258 | ^^^^^^^^^^^^^^^^^^^^^^^^ 259 | 260 | 用户还可以通过命令行运行tasklist命令来查看Nginx进程: 261 | 262 | .. code:: 263 | 264 | C:\>tasklist /fi "imagename eq nginx.exe" 265 | 266 | 映像名称 PID 会话名 会话# 内存使用 267 | ========================= ======== ================ =========== ============ 268 | nginx.exe 463024 Console 1 5,036 K 269 | nginx.exe 462960 Console 1 5,280 K 270 | 271 | 272 | 如果Nginx没有启动或没有得到预期展示页面,可查看error.log文件以查看失败原因。如果日志文件不存在,可在Windows事件日志中查看。 273 | 274 | 在Linux环境下查看Nginx进程 275 | ^^^^^^^^^^^^^^^^^^^^^^^^ 276 | 用户可以通过执行ps/top命令来查看nginx进程: 277 | 278 | .. code:: 279 | 280 | ps aux|grep nginx 281 | admin 24913 0.0 0.0 58596 1048 ? Ss Feb27 0:00 nginx: master process ./nginx 282 | admin 24914 0.0 0.0 72772 5420 ? S Feb27 0:03 nginx: worker process 283 | 284 | 285 | 同上,如果nginx没有启动或者没有得到预期展示页面,可以查看error.log文件或调试来查看失败原因。 286 | 287 | 288 | 289 | 290 | 291 | 292 | C.2 调试日志 293 | +++++++++++++++++++++++++++++++++++++ 294 | 295 | 用户在使用Nginx的过程中,可能会遇到所请求的资源不正确,Nginx Core Dump,段错误等异常情况,这时需要有相应的机制来进行调试及问题定位,特别是面对大量的日志信息,合理的调试处理机制对用户来说是一件非常重要的事情。以下将着重为大家介绍调试日志。 296 | 297 | 一,开启调试日志: 298 | ^^^^^^^^^^^^^^^^^^^^^^^^ 299 | 300 | 要开启调试日志,首先需要在配置Nginx时打开调试功能,然后编译: 301 | 302 | .. code:: 303 | 304 | ./configure --with-debug ... 305 | 306 | 307 | 然后在配置文件中设置error_log的级别为: 308 | 309 | .. code:: 310 | 311 | error_log /path/to/log debug; 312 | 313 | Nginx的Windows二进制版本总是将调试日志开启的,因此只需要设置debug的日志级别即可。 314 | 315 | 二,日志级别分析: 316 | ^^^^^^^^^^^^^^^^^^^^^^^^ 317 | 318 | 在此,我们通过分析Nginx源码了解下Nginx将日志分为几个等级及不同日志等级之间的相互关系: 319 | 320 | Ngx_log.h代码 321 | 322 | .. code:: c 323 | 324 | #define NGX_LOG_STDERR 0 325 | #define NGX_LOG_EMERG 1 326 | #define NGX_LOG_ALERT 2 327 | #define NGX_LOG_CRIT 3 328 | #define NGX_LOG_ERR 4 329 | #define NGX_LOG_WARN 5 330 | #define NGX_LOG_NOTICE 6 331 | #define NGX_LOG_INFO 7 332 | #define NGX_LOG_DEBUG 8 333 | 334 | #define NGX_LOG_DEBUG_CORE 0x010 335 | #define NGX_LOG_DEBUG_ALLOC 0x020 336 | #define NGX_LOG_DEBUG_MUTEX 0x040 337 | #define NGX_LOG_DEBUG_EVENT 0x080 338 | #define NGX_LOG_DEBUG_HTTP 0x100 339 | #define NGX_LOG_DEBUG_MAIL 0x200 340 | #define NGX_LOG_DEBUG_MYSQL 0x400 341 | 342 | #define NGX_LOG_DEBUG_FIRST NGX_LOG_DEBUG_CORE 343 | #define NGX_LOG_DEBUG_LAST NGX_LOG_DEBUG_MYSQL 344 | #define NGX_LOG_DEBUG_CONNECTION 0x80000000 345 | #define NGX_LOG_DEBUG_ALL 0x7ffffff0 346 | 347 | 其中默认有效的第一级别日志是"stderr","emerg","alert","crit","error","warn","notice","info","debug"。 348 | 而ngx_log.h内列出的其他debug第二级别日志:"debug_core","debug_alloc","debug_mutex","debug_event","debug_http","debug_mail","debug_mysql"等则需要在配置Nginx时启动调试日志功能才能使用,并且用户可以通过修改ngx_log.h及ngx_log.c源码来更新debug第二级别。 349 | 350 | 我们再通过ngx_log.c的部分代码分析下可以如何使用这些日志级别: 351 | 352 | ngx_log.c代码 353 | 354 | .. code:: c 355 | 356 | char * 357 | ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log) 358 | { 359 | ... 360 | 361 | for (n = 1; n <= NGX_LOG_DEBUG; n++) { 362 | if (ngx_strcmp(value[i].data, err_levels[n].data) == 0) { 363 | 364 | if (log->log_level != 0) { 365 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 366 | "duplicate log level \"%V\"", 367 | &value[i]); 368 | return NGX_CONF_ERROR; 369 | } 370 | 371 | log->log_level = n; 372 | found = 1; 373 | break; 374 | } 375 | } 376 | 377 | for (n = 0, d = NGX_LOG_DEBUG_FIRST; d <= NGX_LOG_DEBUG_LAST; d <<= 1) { 378 | if (ngx_strcmp(value[i].data, debug_levels[n++]) == 0) { 379 | if (log->log_level & ~NGX_LOG_DEBUG_ALL) { 380 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 381 | "invalid log level \"%V\"", 382 | &value[i]); 383 | return NGX_CONF_ERROR; 384 | } 385 | 386 | log->log_level |= d; 387 | found = 1; 388 | break; 389 | } 390 | } 391 | ... 392 | if (log->log_level == NGX_LOG_DEBUG) { 393 | log->log_level = NGX_LOG_DEBUG_ALL; 394 | } 395 | ... 396 | } 397 | 398 | 按照以上代码逻辑,我们可以得出以下结论: 399 | 400 | 1\. 第一级别日志之间是互斥的,如果配置文件内加入如下配置项: 401 | 402 | .. code:: 403 | 404 | error_log path/logs/error.log warn; 405 | error_log path/logs/error.log info; 406 | 407 | 那么启动Nginx将报错如下: 408 | 409 | .. code:: 410 | 411 | [emerg]: duplicate log level "info" in /path/conf/nginx.conf:XX 412 | 413 | 但是需要注意的是,在配置文件不同block中是允许重新定义错误日志的。但是当用户在重新定义错误日志时,如果没有指定相应的日志级别,那么调试日志将会被屏蔽。下面的例子里,在server层中重新定义的日志就屏蔽了这个虚拟主机的调试日志: 414 | 415 | .. code:: 416 | 417 | error_log /path/to/log debug; 418 | 419 | http { 420 | server { 421 | error_log /path/to/log; 422 | ... 423 | 424 | 为了避免这个问题,可以注释这行重新定义日志的配置,或者也给日志指定debug级别: 425 | 426 | .. code:: 427 | 428 | error_log /path/to/log debug; 429 | 430 | http { 431 | server { 432 | error_log /path/to/log debug; 433 | ... 434 | 435 | 2\. 第二级别日志是多选的,用户可以根据项目需要配置多个第二级别日志: 436 | 437 | .. code:: 438 | 439 | error_log logs/error.log debug_mysql; 440 | error_log logs/error.log debug_core; 441 | 442 | 3\. 在第一级别日志与第二级别日志组合配置时,仅有在第一级别日志为"debug"时才可以有第二级别的配置,其他第一级别日志的情况下指定第二级别日志将无法启动Nginx,如: 443 | 444 | .. code:: 445 | 446 | error_log logs/error.log error; 447 | error_log logs/error.log debug_core; 448 | 449 | 启动Nginx将获得如下错误信息: 450 | 451 | .. code:: 452 | 453 | [emerg]: invalid log level “debug_http” in /path/conf/nginx.conf:XX 454 | 455 | 当用户开启debug级别日志时,会输出所有debug_开头的调试信息,因此可以通过上面组合debug_core|debug_http的形式来获取用户所需要的调试信息。 456 | 457 | 三,日志格式设置: 458 | ^^^^^^^^^^^^^^^^^^^^^^^^ 459 | 460 | 用户在使用Nginx提供web服务的时候,可能会有很多场景需要记录日志,如打点日志,访问日志,数据统计日志,性能分析日志等。为了更加方便的对日志进行分析,我们可以通过设置日志格式的方式来要求Nginx按照用户要求进行日志的展现。 461 | 462 | 控制nginx日志输出的指令如下: 463 | 464 | .. code:: 465 | 466 | log_format customLog "$remote_addr^A$remote_user^A$time_local^A$request_method^A$uri^A$args^A$server_protocol" 467 | "^A$status^A$body_bytes_sent^A$http_referer" 468 | "^A$http_user_agent"; 469 | access_log /path/logs/access.log customLog; 470 | 471 | 上面例子中通过使用特殊字符(^A)来作为日志字段的分隔符,用户后续可以使用sort和grep之类的工具对特定url做分析,如统计各url请求量倒排取前50个: 472 | 473 | .. code:: 474 | 475 | awk -F^A '{print $5}' /path/logs/access.log | sort | uniq -c | sort -nr | head -50 476 | 477 | 类似上面的日志定制化设置,可以让用户在调试日志的过程中随心所欲,如鱼得水。 478 | 详细的log_format指令和access_log指令,用户可以访问Nginx官网的HttpLog模块 http://wiki.nginx.org/HttpLogModule 。 479 | 480 | 四,调试日志的几个注意点: 481 | ^^^^^^^^^^^^^^^^^^^^^^^^ 482 | 483 | 1\. 勘误:在Nginx Wiki里面error log相关部分(http://wiki.nginx.org/NginxHttpMainModule#error_log )的介绍中提到 484 | 485 | .. code:: 486 | 487 | Default values for the error level: 488 | in the main section - error 489 | in the HTTP section - crit 490 | in the server section - crit 491 | 492 | 但是,我们从源码上看: 493 | 494 | .. code:: c 495 | 496 | static char * 497 | ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 498 | { 499 | ... 500 | 501 | if (cf->args->nelts == 2) { 502 | cf->cycle->new_log.log_level = NGX_LOG_ERR; 503 | return NGX_CONF_OK; 504 | } 505 | ... 506 | } 507 | 508 | 当error_log 的日志级别选项为配置时,默认日志级别为error,无上面提及的三个section的区别。故特在此勘误。 509 | 510 | 2\. 配置error_log off并不能关闭日志记录——日志信息会被写入到文件名为off的文件当中。如果要关闭日志记录,用户可以做如下配置: 511 | 512 | .. code:: 513 | 514 | error_log /dev/null crit; 515 | 516 | 3\. 如果nginx进程没有权限将日志信息写入指定的log地址,那么nginx会在启动是报错: 517 | 518 | .. code:: 519 | 520 | [alert]: could not open error log file: open() "/path/log/nginx/error.log" failed (13: Permission denied) 521 | 522 | 4\. 通过debug_connection配置项,用户可以针对某些地址开启调试日志: 523 | 524 | .. code:: 525 | 526 | error_log /path/to/log; 527 | 528 | events { 529 | debug_connection 10.232.10.1; 530 | debug_connection 10.232.10.0/24; 531 | } 532 | 533 | 534 | 535 | 536 | C.3 使用GDB调试 537 | +++++++++++++ 538 | 539 | 540 | 541 | C.4 功能测试 542 | ++++++++++++++++ 543 | 544 | 545 | 546 | C.5 性能/压力测试 547 | ++++++++++++++++ 548 | 549 | 550 | 551 | C.6 常见缺陷分析 552 | ++++++++++++++++ 553 | 554 | -------------------------------------------------------------------------------- /source/chapter_01.rst: -------------------------------------------------------------------------------- 1 | 背景介绍 2 | ====================== 3 | 4 | 5 | 6 | nginx历史 7 | --------------- 8 | 9 | 10 | 11 | 使用简介 12 | ---------------- 13 | 14 | 15 | 16 | nginx特点介绍 17 | --------------------- 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /source/chapter_04.rst: -------------------------------------------------------------------------------- 1 | 过滤模块 (90%) 2 | ====================== 3 | 4 | 过滤模块简介 (90%) 5 | ------------------------ 6 | 7 | 执行时间和内容 (90%) 8 | +++++++++++++++++++++++++++ 9 | 过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。它的处理过程分为两个阶段,过滤HTTP回复的头部和主体,在这两个阶段可以分别对头部和主体进行修改。 10 | 11 | 在代码中有类似的函数: 12 | 13 | .. code:: c 14 | 15 | ngx_http_top_header_filter(r); 16 | ngx_http_top_body_filter(r, in); 17 | 18 | 就是分别对头部和主体进行过滤的函数。所有模块的响应内容要返回给客户端,都必须调用这两个接口。 19 | 20 | 21 | 执行顺序 (90%) 22 | +++++++++++++++++++++ 23 | 24 | 过滤模块的调用是有顺序的,它的顺序在编译的时候就决定了。控制编译的脚本位于auto/modules中,当你编译完Nginx以后,可以在objs目录下面看到一个ngx_modules.c的文件。打开这个文件,有类似的代码: 25 | 26 | .. code:: c 27 | 28 | ngx_module_t *ngx_modules[] = { 29 | ... 30 | &ngx_http_write_filter_module, 31 | &ngx_http_header_filter_module, 32 | &ngx_http_chunked_filter_module, 33 | &ngx_http_range_header_filter_module, 34 | &ngx_http_gzip_filter_module, 35 | &ngx_http_postpone_filter_module, 36 | &ngx_http_ssi_filter_module, 37 | &ngx_http_charset_filter_module, 38 | &ngx_http_userid_filter_module, 39 | &ngx_http_headers_filter_module, 40 | &ngx_http_copy_filter_module, 41 | &ngx_http_range_body_filter_module, 42 | &ngx_http_not_modified_filter_module, 43 | NULL 44 | }; 45 | 46 | 从write_filter到not_modified_filter,模块的执行顺序是反向的。也就是说最早执行的是not_modified_filter,然后各个模块依次执行。一般情况下,第三方过滤模块的config文件会将模块名追加到变量HTTP_AUX_FILTER_MODULES中,此时该模块只能加入到copy_filter和headers_filter模块之间执行。 47 | 48 | Nginx执行的时候是怎么按照次序依次来执行各个过滤模块呢?它采用了一种很隐晦的方法,即通过局部的全局变量。比如,在每个filter模块,很可能看到如下代码: 49 | 50 | .. code:: c 51 | 52 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 53 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 54 | 55 | ... 56 | 57 | ngx_http_next_header_filter = ngx_http_top_header_filter; 58 | ngx_http_top_header_filter = ngx_http_example_header_filter; 59 | 60 | ngx_http_next_body_filter = ngx_http_top_body_filter; 61 | ngx_http_top_body_filter = ngx_http_example_body_filter; 62 | 63 | ngx_http_top_header_filter是一个全局变量。当编译进一个filter模块的时候,就被赋值为当前filter模块的处理函数。而ngx_http_next_header_filter是一个局部全局变量,它保存了编译前上一个filter模块的处理函数。所以整体看来,就像用全局变量组成的一条单向链表。 64 | 65 | 每个模块想执行下一个过滤函数,只要调用一下ngx_http_next_header_filter这个局部变量。而整个过滤模块链的入口,需要调用ngx_http_top_header_filter这个全局变量。ngx_http_top_body_filter的行为与header fitler类似。 66 | 67 | 响应头和响应体过滤函数的执行顺序如下所示: 68 | 69 | .. image:: http://tengine.taobao.org/book/_images/chapter-4-1.png 70 | 71 | 这图只表示了head_filter和body_filter之间的执行顺序,在header_filter和body_filter处理函数之间,在body_filter处理函数之间,可能还有其他执行代码。 72 | 73 | 模块编译 (90%) 74 | ++++++++++++++++++++ 75 | 76 | Nginx可以方便的加入第三方的过滤模块。在过滤模块的目录里,首先需要加入config文件,文件的内容如下: 77 | 78 | .. code:: c 79 | 80 | ngx_addon_name=ngx_http_example_filter_module 81 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_example_filter_module" 82 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_example_filter_module.c" 83 | 84 | 说明把这个名为ngx_http_example_filter_module的过滤模块加入,ngx_http_example_filter_module.c是该模块的源代码。 85 | 86 | 注意HTTP_AUX_FILTER_MODULES这个变量与一般的内容处理模块不同。 87 | 88 | 89 | 过滤模块的分析 (90%) 90 | -------------------------- 91 | 92 | 相关结构体 (90%) 93 | +++++++++++++++++++++ 94 | ngx_chain_t 结构非常简单,是一个单向链表: 95 | 96 | .. code:: c 97 | 98 | typedef struct ngx_chain_s ngx_chain_t; 99 | 100 | struct ngx_chain_s { 101 | ngx_buf_t *buf; 102 | ngx_chain_t *next; 103 | }; 104 | 105 | 在过滤模块中,所有输出的内容都是通过一条单向链表所组成。这种单向链表的设计,正好应和了Nginx流式的输出模式。每次Nginx都是读到一部分的内容,就放到链表,然后输出出去。这种设计的好处是简单,非阻塞,但是相应的问题就是跨链表的内容操作非常麻烦,如果需要跨链表,很多时候都只能缓存链表的内容。 106 | 107 | 单链表负载的就是ngx_buf_t,这个结构体使用非常广泛,先让我们看下该结构体的代码: 108 | 109 | .. code:: c 110 | 111 | struct ngx_buf_s { 112 | u_char *pos; /* 当前buffer真实内容的起始位置 */ 113 | u_char *last; /* 当前buffer真实内容的结束位置 */ 114 | off_t file_pos; /* 在文件中真实内容的起始位置 */ 115 | off_t file_last; /* 在文件中真实内容的结束位置 */ 116 | 117 | u_char *start; /* buffer内存的开始分配的位置 */ 118 | u_char *end; /* buffer内存的结束分配的位置 */ 119 | ngx_buf_tag_t tag; /* buffer属于哪个模块的标志 */ 120 | ngx_file_t *file; /* buffer所引用的文件 */ 121 | 122 | /* 用来引用替换过后的buffer,以便当所有buffer输出以后, 123 | * 这个影子buffer可以被释放。 124 | */ 125 | ngx_buf_t *shadow; 126 | 127 | /* the buf's content could be changed */ 128 | unsigned temporary:1; 129 | 130 | /* 131 | * the buf's content is in a memory cache or in a read only memory 132 | * and must not be changed 133 | */ 134 | unsigned memory:1; 135 | 136 | /* the buf's content is mmap()ed and must not be changed */ 137 | unsigned mmap:1; 138 | 139 | unsigned recycled:1; /* 内存可以被输出并回收 */ 140 | unsigned in_file:1; /* buffer的内容在文件中 */ 141 | /* 马上全部输出buffer的内容, gzip模块里面用得比较多 */ 142 | unsigned flush:1; 143 | /* 基本上是一段输出链的最后一个buffer带的标志,标示可以输出, 144 | * 有些零长度的buffer也可以置该标志 145 | */ 146 | unsigned sync:1; 147 | /* 所有请求里面最后一块buffer,包含子请求 */ 148 | unsigned last_buf:1; 149 | /* 当前请求输出链的最后一块buffer */ 150 | unsigned last_in_chain:1; 151 | /* shadow链里面的最后buffer,可以释放buffer了 */ 152 | unsigned last_shadow:1; 153 | /* 是否是暂存文件 */ 154 | unsigned temp_file:1; 155 | 156 | /* 统计用,表示使用次数 */ 157 | /* STUB */ int num; 158 | }; 159 | 160 | 一般buffer结构体可以表示一块内存,内存的起始和结束地址分别用start和end表示,pos和last表示实际的内容。如果内容已经处理过了,pos的位置就可以往后移动。如果读取到新的内容,last的位置就会往后移动。所以buffer可以在多次调用过程中使用。如果last等于end,就说明这块内存已经用完了。如果pos等于last,说明内存已经处理完了。下面是一个简单的示意图,说明buffer中指针的用法: 161 | 162 | .. image:: http://tengine.taobao.org/book/_images/chapter-4-2.png 163 | 164 | 165 | 响应头过滤函数 (90%) 166 | +++++++++++++++++++++++++ 167 | 168 | 响应头过滤函数主要的用处就是处理HTTP响应的头,可以根据实际情况对于响应头进行修改或者添加删除。响应头过滤函数先于响应体过滤函数,而且只调用一次,所以一般可作过滤模块的初始化工作。 169 | 170 | 响应头过滤函数的入口只有一个: 171 | 172 | .. code:: c 173 | 174 | ngx_int_t 175 | ngx_http_send_header(ngx_http_request_t *r) 176 | { 177 | ... 178 | 179 | return ngx_http_top_header_filter(r); 180 | } 181 | 182 | 该函数向客户端发送回复的时候调用,然后按前一节所述的执行顺序。该函数的返回值一般是NGX_OK,NGX_ERROR和NGX_AGAIN,分别表示处理成功,失败和未完成。 183 | 184 | 你可以把HTTP响应头的存储方式想象成一个hash表,在Nginx内部可以很方便地查找和修改各个响应头部,ngx_http_header_filter_module过滤模块把所有的HTTP头组合成一个完整的buffer,最终ngx_http_write_filter_module过滤模块把buffer输出。 185 | 186 | 按照前一节过滤模块的顺序,依次讲解如下: 187 | 188 | ===================================== ================================================================================================================= 189 | filter module description 190 | ===================================== ================================================================================================================= 191 | ngx_http_not_modified_filter_module 默认打开,如果请求的if-modified-since等于回复的last-modified间值,说明回复没有变化,清空所有回复的内容,返回304。 192 | ngx_http_range_body_filter_module 默认打开,只是响应体过滤函数,支持range功能,如果请求包含range请求,那就只发送range请求的一段内容。 193 | ngx_http_copy_filter_module 始终打开,只是响应体过滤函数, 主要工作是把文件中内容读到内存中,以便进行处理。 194 | ngx_http_headers_filter_module 始终打开,可以设置expire和Cache-control头,可以添加任意名称的头 195 | ngx_http_userid_filter_module 默认关闭,可以添加统计用的识别用户的cookie。 196 | ngx_http_charset_filter_module 默认关闭,可以添加charset,也可以将内容从一种字符集转换到另外一种字符集,不支持多字节字符集。 197 | ngx_http_ssi_filter_module 默认关闭,过滤SSI请求,可以发起子请求,去获取include进来的文件 198 | ngx_http_postpone_filter_module 始终打开,用来将子请求和主请求的输出链合并 199 | ngx_http_gzip_filter_module 默认关闭,支持流式的压缩内容 200 | ngx_http_range_header_filter_module 默认打开,只是响应头过滤函数,用来解析range头,并产生range响应的头。 201 | ngx_http_chunked_filter_module 默认打开,对于HTTP/1.1和缺少content-length的回复自动打开。 202 | ngx_http_header_filter_module 始终打开,用来将所有header组成一个完整的HTTP头。 203 | ngx_http_write_filter_module 始终打开,将输出链拷贝到r->out中,然后输出内容。 204 | ===================================== ================================================================================================================= 205 | 206 | 207 | 响应体过滤函数 (90%) 208 | ++++++++++++++++++++++++++ 209 | 210 | 响应体过滤函数是过滤响应主体的函数。ngx_http_top_body_filter这个函数每个请求可能会被执行多次,它的入口函数是ngx_http_output_filter,比如: 211 | 212 | .. code:: c 213 | 214 | ngx_int_t 215 | ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) 216 | { 217 | ngx_int_t rc; 218 | ngx_connection_t *c; 219 | 220 | c = r->connection; 221 | 222 | rc = ngx_http_top_body_filter(r, in); 223 | 224 | if (rc == NGX_ERROR) { 225 | /* NGX_ERROR may be returned by any filter */ 226 | c->error = 1; 227 | } 228 | 229 | return rc; 230 | } 231 | 232 | ngx_http_output_filter可以被一般的静态处理模块调用,也有可能是在upstream模块里面被调用,对于整个请求的处理阶段来说,他们处于的用处都是一样的,就是把响应内容过滤,然后发给客户端。 233 | 234 | 具体模块的响应体过滤函数的格式类似这样: 235 | 236 | .. code:: c 237 | 238 | static int 239 | ngx_http_example_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 240 | { 241 | ... 242 | 243 | return ngx_http_next_body_filter(r, in); 244 | } 245 | 246 | 该函数的返回值一般是NGX_OK,NGX_ERROR和NGX_AGAIN,分别表示处理成功,失败和未完成。 247 | 248 | 主要功能介绍 (90%) 249 | ^^^^^^^^^^^^^^^^^^^^^^^ 250 | 响应的主体内容就存于单链表in,链表一般不会太长,有时in参数可能为NULL。in中存有buf结构体中,对于静态文件,这个buf大小默认是32K;对于反向代理的应用,这个buf可能是4k或者8k。为了保持内存的低消耗,Nginx一般不会分配过大的内存,处理的原则是收到一定的数据,就发送出去。一个简单的例子,可以看看Nginx的chunked_filter模块,在没有content-length的情况下,chunk模块可以流式(stream)的加上长度,方便浏览器接收和显示内容。 251 | 252 | 在响应体过滤模块中,尤其要注意的是buf的标志位,完整描述可以在“相关结构体”这个节中看到。如果buf中包含last标志,说明是最后一块buf,可以直接输出并结束请求了。如果有flush标志,说明这块buf需要马上输出,不能缓存。如果整块buffer经过处理完以后,没有数据了,你可以把buffer的sync标志置上,表示只是同步的用处。 253 | 254 | 当所有的过滤模块都处理完毕时,在最后的write_fitler模块中,Nginx会将in输出链拷贝到r->out输出链的末尾,然后调用sendfile或者writev接口输出。由于Nginx是非阻塞的socket接口,写操作并不一定会成功,可能会有部分数据还残存在r->out。在下次的调用中,Nginx会继续尝试发送,直至成功。 255 | 256 | 257 | 发出子请求 (90%) 258 | ^^^^^^^^^^^^^^^^^^^^^ 259 | Nginx过滤模块一大特色就是可以发出子请求,也就是在过滤响应内容的时候,你可以发送新的请求,Nginx会根据你调用的先后顺序,将多个回复的内容拼接成正常的响应主体。一个简单的例子可以参考addition模块。 260 | 261 | Nginx是如何保证父请求和子请求的顺序呢?当Nginx发出子请求时,就会调用ngx_http_subrequest函数,将子请求插入父请求的r->postponed链表中。子请求会在主请求执行完毕时获得依次调用。子请求同样会有一个请求所有的生存期和处理过程,也会进入过滤模块流程。 262 | 263 | 关键点是在postpone_filter模块中,它会拼接主请求和子请求的响应内容。r->postponed按次序保存有父请求和子请求,它是一个链表,如果前面一个请求未完成,那后一个请求内容就不会输出。当前一个请求完成时并输出时,后一个请求才可输出,当所有的子请求都完成时,所有的响应内容也就输出完毕了。 264 | 265 | 266 | 一些优化措施 (90%) 267 | ^^^^^^^^^^^^^^^^^^^^^^ 268 | Nginx过滤模块涉及到的结构体,主要就是chain和buf,非常简单。在日常的过滤模块中,这两类结构使用非常频繁,Nginx采用类似freelist重复利用的原则,将使用完毕的chain或者buf结构体,放置到一个固定的空闲链表里,以待下次使用。 269 | 270 | 比如,在通用内存池结构体中,pool->chain变量里面就保存着释放的chain。而一般的buf结构体,没有模块间公用的空闲链表池,都是保存在各模块的缓存空闲链表池里面。对于buf结构体,还有一种busy链表,表示该链表中的buf都处于输出状态,如果buf输出完毕,这些buf就可以释放并重复利用了。 271 | 272 | ========== ======================== 273 | 功能 函数名 274 | ========== ======================== 275 | chain分配 ngx_alloc_chain_link 276 | chain释放 ngx_free_chain 277 | buf分配 ngx_chain_get_free_buf 278 | buf释放 ngx_chain_update_chains 279 | ========== ======================== 280 | 281 | 282 | 过滤内容的缓存 (90%) 283 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 284 | 由于Nginx设计流式的输出结构,当我们需要对响应内容作全文过滤的时候,必须缓存部分的buf内容。该类过滤模块往往比较复杂,比如sub,ssi,gzip等模块。这类模块的设计非常灵活,我简单讲一下设计原则: 285 | 286 | 1. 输入链in需要拷贝操作,经过缓存的过滤模块,输入输出链往往已经完全不一样了,所以需要拷贝,通过ngx_chain_add_copy函数完成。 287 | 288 | 2. 一般有自己的free和busy缓存链表池,可以提高buf分配效率。 289 | 290 | 3. 如果需要分配大块内容,一般分配固定大小的内存卡,并设置recycled标志,表示可以重复利用。 291 | 292 | 4. 原有的输入buf被替换缓存时,必须将其buf->pos设为buf->last,表明原有的buf已经被输出完毕。或者在新建立的buf,将buf->shadow指向旧的buf,以便输出完毕时及时释放旧的buf。 293 | 294 | 295 | -------------------------------------------------------------------------------- /source/chapter_05.rst: -------------------------------------------------------------------------------- 1 | upstream模块 2 | ====================== 3 | 4 | upstream模块 (100%) 5 | ----------------------- 6 | 7 | nginx模块一般被分成三大类:handler、filter和upstream。前面的章节中,读者已经了解了handler、filter。利用这两类模块,可以使nginx轻松完成任何单机工作。而本章介绍的upstream模块,将使nginx跨越单机的限制,完成网络数据的接收、处理和转发。 8 | 9 | 数据转发功能,为nginx提供了跨越单机的横向处理能力,使nginx摆脱只能为终端节点提供单一功能的限制,而使它具备了网路应用级别的拆分、封装和整合的战略功能。在云模型大行其道的今天,数据转发是nginx有能力构建一个网络应用的关键组件。当然,鉴于开发成本的问题,一个网络应用的关键组件一开始往往会采用高级编程语言开发。但是当系统到达一定规模,并且需要更重视性能的时候,为了达到所要求的性能目标,高级语言开发出的组件必须进行结构化修改。此时,对于修改代价而言,nginx的upstream模块呈现出极大的吸引力,因为它天生就快。作为附带,nginx的配置系统提供的层次化和松耦合使得系统的扩展性也达到比较高的程度。 10 | 11 | 言归正传,下面介绍upstream的写法。 12 | 13 | upstream模块接口 14 | +++++++++++++++++++++++++++ 15 | 16 | 从本质上说,upstream属于handler,只是他不产生自己的内容,而是通过请求后端服务器得到内容,所以才称为upstream(上游)。请求并取得响应内容的整个过程已经被封装到nginx内部,所以upstream模块只需要开发若干回调函数,完成构造请求和解析响应等具体的工作。 17 | 18 | 这些回调函数如下表所示: 19 | 20 | +-------------------+--------------------------------------------------------------+ 21 | |create_request |生成发送到后端服务器的请求缓冲(缓冲链),在初始化upstream | 22 | | |时使用。 | 23 | +-------------------+--------------------------------------------------------------+ 24 | |reinit_request |在某台后端服务器出错的情况,nginx会尝试另一台后端服务器。 | 25 | | |nginx选定新的服务器以后,会先调用此函数,以重新初始化 | 26 | | |upstream模块的工作状态,然后再次进行upstream连接。 | 27 | +-------------------+--------------------------------------------------------------+ 28 | |process_header |处理后端服务器返回的信息头部。所谓头部是与upstream server | 29 | | |通信的协议规定的,比如HTTP协议的header部分,或者memcached | 30 | | |协议的响应状态部分。 | 31 | +-------------------+--------------------------------------------------------------+ 32 | |abort_request |在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务 | 33 | | |器连接的功能,系统会自动完成关闭连接的步骤,所以一般此函 | 34 | | |数不会进行任何具体工作。 | 35 | +-------------------+--------------------------------------------------------------+ 36 | |finalize_request |正常完成与后端服务器的请求后调用该函数,与abort_request | 37 | | |相同,一般也不会进行任何具体工作。 | 38 | +-------------------+--------------------------------------------------------------+ 39 | |input_filter |处理后端服务器返回的响应正文。nginx默认的input_filter会 | 40 | | |将收到的内容封装成为缓冲区链ngx_chain。该链由upstream的 | 41 | | |out_bufs指针域定位,所以开发人员可以在模块以外通过该指针 | 42 | | |得到后端服务器返回的正文数据。memcached模块实现了自己的 | 43 | | |input_filter,在后面会具体分析这个模块。 | 44 | +-------------------+--------------------------------------------------------------+ 45 | |input_filter_init |初始化input filter的上下文。nginx默认的input_filter_init | 46 | | |直接返回。 | 47 | +-------------------+--------------------------------------------------------------+ 48 | 49 | memcached模块分析 50 | ++++++++++++++++++++++++++++++ 51 | 52 | memcache是一款高性能的分布式cache系统,得到了非常广泛的应用。memcache定义了一套私有通信协议,使得不能通过HTTP请求来访问memcache。但协议本身简单高效,而且memcache使用广泛,所以大部分现代开发语言和平台都提供了memcache支持,方便开发者使用memcache。 53 | 54 | nginx提供了ngx_http_memcached模块,提供从memcache读取数据的功能,而不提供向memcache写数据的功能。作为web服务器,这种设计是可以接受的。 55 | 56 | 下面,我们开始分析ngx_http_memcached模块,一窥upstream的奥秘。 57 | 58 | Handler模块? 59 | ^^^^^^^^^^^^^^^^^^^^^^^^ 60 | 61 | 初看memcached模块,大家可能觉得并无特别之处。如果稍微细看,甚至觉得有点像handler模块,当大家看到这段代码以后,必定疑惑为什么会跟handler模块一模一样。 62 | 63 | .. code:: c 64 | 65 | clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); 66 | clcf->handler = ngx_http_memcached_handler; 67 | 68 | 因为upstream模块使用的就是handler模块的接入方式。同时,upstream模块的指令系统的设计也是遵循handler模块的基本规则:配置该模块才会执行该模块。 69 | 70 | .. code:: c 71 | 72 | { ngx_string("memcached_pass"), 73 | NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 74 | ngx_http_memcached_pass, 75 | NGX_HTTP_LOC_CONF_OFFSET, 76 | 0, 77 | NULL } 78 | 79 | 所以大家觉得眼熟是好事,说明大家对Handler的写法已经很熟悉了。 80 | 81 | Upstream模块! 82 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 83 | 84 | 那么,upstream模块的特别之处究竟在哪里呢?答案是就在模块处理函数的实现中。upstream模块的处理函数进行的操作都包含一个固定的流程。在memcached的例子中,可以观察ngx_http_memcached_handler的代码,可以发现,这个固定的操作流程是: 85 | 86 | 1\. 创建upstream数据结构。 87 | 88 | .. code:: c 89 | 90 | if (ngx_http_upstream_create(r) != NGX_OK) { 91 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 92 | } 93 | 94 | 2\. 设置模块的tag和schema。schema现在只会用于日志,tag会用于buf_chain管理。 95 | 96 | .. code:: c 97 | 98 | u = r->upstream; 99 | 100 | ngx_str_set(&u->schema, "memcached://"); 101 | u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module; 102 | 103 | 3\. 设置upstream的后端服务器列表数据结构。 104 | 105 | .. code:: c 106 | 107 | mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module); 108 | u->conf = &mlcf->upstream; 109 | 110 | 4\. 设置upstream回调函数。在这里列出的代码稍稍调整了代码顺序。 111 | 112 | .. code:: c 113 | 114 | u->create_request = ngx_http_memcached_create_request; 115 | u->reinit_request = ngx_http_memcached_reinit_request; 116 | u->process_header = ngx_http_memcached_process_header; 117 | u->abort_request = ngx_http_memcached_abort_request; 118 | u->finalize_request = ngx_http_memcached_finalize_request; 119 | u->input_filter_init = ngx_http_memcached_filter_init; 120 | u->input_filter = ngx_http_memcached_filter; 121 | 122 | 5\. 创建并设置upstream环境数据结构。 123 | 124 | .. code:: c 125 | 126 | ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t)); 127 | if (ctx == NULL) { 128 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 129 | } 130 | 131 | ctx->rest = NGX_HTTP_MEMCACHED_END; 132 | ctx->request = r; 133 | 134 | ngx_http_set_ctx(r, ctx, ngx_http_memcached_module); 135 | 136 | u->input_filter_ctx = ctx; 137 | 138 | 6\. 完成upstream初始化并进行收尾工作。 139 | 140 | .. code:: c 141 | 142 | r->main->count++; 143 | ngx_http_upstream_init(r); 144 | return NGX_DONE; 145 | 146 | 任何upstream模块,简单如memcached,复杂如proxy、fastcgi都是如此。不同的upstream模块在这6步中的最大差别会出现在第2、3、4、5上。其中第2、4两步很容易理解,不同的模块设置的标志和使用的回调函数肯定不同。第5步也不难理解,只有第3步是最为晦涩的,不同的模块在取得后端服务器列表时,策略的差异非常大,有如memcached这样简单明了的,也有如proxy那样逻辑复杂的。这个问题先记下来,等把memcached剖析清楚了,再单独讨论。 147 | 148 | 第6步是一个常态。将count加1,然后返回NGX_DONE。nginx遇到这种情况,虽然会认为当前请求的处理已经结束,但是不会释放请求使用的内存资源,也不会关闭与客户端的连接。之所以需要这样,是因为nginx建立了upstream请求和客户端请求之间一对一的关系,在后续使用ngx_event_pipe将upstream响应发送回客户端时,还要使用到这些保存着客户端信息的数据结构。这部分会在后面的原理篇做具体介绍,这里不再展开。 149 | 150 | 将upstream请求和客户端请求进行一对一绑定,这个设计有优势也有缺陷。优势就是简化模块开发,可以将精力集中在模块逻辑上,而缺陷同样明显,一对一的设计很多时候都不能满足复杂逻辑的需要。对于这一点,将会在后面的原理篇来阐述。 151 | 152 | 153 | 回调函数 154 | ^^^^^^^^^^^^^^^^^^^^^ 155 | 156 | 前面剖析了memcached模块的骨架,现在开始逐个解决每个回调函数。 157 | 158 | 1\. ngx_http_memcached_create_request:很简单的按照设置的内容生成一个key,接着生成一个“get $key”的请求,放在r->upstream->request_bufs里面。 159 | 160 | 2\. ngx_http_memcached_reinit_request:无需初始化。 161 | 162 | 3\. ngx_http_memcached_abort_request:无需额外操作。 163 | 164 | 4\. ngx_http_memcached_finalize_request:无需额外操作。 165 | 166 | 5\. ngx_http_memcached_process_header:模块的业务重点函数。memcache协议的头部信息被定义为第一行文本,可以找到这段代码证明: 167 | 168 | .. code:: c 169 | 170 | for (p = u->buffer.pos; p < u->buffer.last; p++) { 171 | if ( * p == LF) { 172 | goto found; 173 | } 174 | 175 | 如果在已读入缓冲的数据中没有发现LF('\n')字符,函数返回NGX_AGAIN,表示头部未完全读入,需要继续读取数据。nginx在收到新的数据以后会再次调用该函数。 176 | 177 | nginx处理后端服务器的响应头时只会使用一块缓存,所有数据都在这块缓存中,所以解析头部信息时不需要考虑头部信息跨越多块缓存的情况。而如果头部过大,不能保存在这块缓存中,nginx会返回错误信息给客户端,并记录error log,提示缓存不够大。 178 | 179 | process_header的重要职责是将后端服务器返回的状态翻译成返回给客户端的状态。例如,在ngx_http_memcached_process_header中,有这样几段代码: 180 | 181 | .. code:: c 182 | 183 | r->headers_out.content_length_n = ngx_atoof(len, p - len - 1); 184 | 185 | u->headers_in.status_n = 200; 186 | u->state->status = 200; 187 | 188 | u->headers_in.status_n = 404; 189 | u->state->status = 404; 190 | 191 | u->state用于计算upstream相关的变量。比如u->state->status将被用于计算变量“upstream_status”的值。u->headers_in将被作为返回给客户端的响应返回状态码。而第一行则是设置返回给客户端的响应的长度。 192 | 193 | 在这个函数中不能忘记的一件事情是处理完头部信息以后需要将读指针pos后移,否则这段数据也将被复制到返回给客户端的响应的正文中,进而导致正文内容不正确。 194 | 195 | .. code:: c 196 | 197 | u->buffer.pos = p + 1; 198 | 199 | process_header函数完成响应头的正确处理,应该返回NGX_OK。如果返回NGX_AGAIN,表示未读取完整数据,需要从后端服务器继续读取数据。返回NGX_DECLINED无意义,其他任何返回值都被认为是出错状态,nginx将结束upstream请求并返回错误信息。 200 | 201 | 6\. ngx_http_memcached_filter_init:修正从后端服务器收到的内容长度。因为在处理header时没有加上这部分长度。 202 | 203 | 7\. ngx_http_memcached_filter:memcached模块是少有的带有处理正文的回调函数的模块。因为memcached模块需要过滤正文末尾CRLF "END" CRLF,所以实现了自己的filter回调函数。处理正文的实际意义是将从后端服务器收到的正文有效内容封装成ngx_chain_t,并加在u->out_bufs末尾。nginx并不进行数据拷贝,而是建立ngx_buf_t数据结构指向这些数据内存区,然后由ngx_chain_t组织这些buf。这种实现避免了内存大量搬迁,也是nginx高效的奥秘之一。 204 | 205 | 本节回顾 206 | +++++++++++++++++++++ 207 | 208 | 这一节介绍了upstream模块的基本组成。upstream模块是从handler模块发展而来,指令系统和模块生效方式与handler模块无异。不同之处在于,upstream模块在handler函数中设置众多回调函数。实际工作都是由这些回调函数完成的。每个回调函数都是在upstream的某个固定阶段执行,各司其职,大部分回调函数一般不会真正用到。upstream最重要的回调函数是create_request、process_header和input_filter,他们共同实现了与后端服务器的协议的解析部分。 209 | 210 | 211 | 负载均衡模块 (100%) 212 | ----------------------- 213 | 214 | 负载均衡模块用于从"upstream"指令定义的后端主机列表中选取一台主机。nginx先使用负载均衡模块找到一台主机,再使用upstream模块实现与这台主机的交互。为了方便介绍负载均衡模块,做到言之有物,以下选取nginx内置的ip hash模块作为实际例子进行分析。 215 | 216 | 配置 217 | ++++++++++++++ 218 | 219 | 要了解负载均衡模块的开发方法,首先需要了解负载均衡模块的使用方法。因为负载均衡模块与之前书中提到的模块差别比较大,所以我们从配置入手比较容易理解。 220 | 221 | 在配置文件中,我们如果需要使用ip hash的负载均衡算法。我们需要写一个类似下面的配置: 222 | 223 | .. code:: c 224 | 225 | upstream test { 226 | ip_hash; 227 | 228 | server 192.168.0.1; 229 | server 192.168.0.2; 230 | } 231 | 232 | 从配置我们可以看出负载均衡模块的使用场景: 233 | 1\. 核心指令"ip_hash"只能在upstream {}中使用。这条指令用于通知nginx使用ip hash负载均衡算法。如果没加这条指令,nginx会使用默认的round robin负载均衡模块。请各位读者对比handler模块的配置,是不是有共同点? 234 | 2\. upstream {}中的指令可能出现在"server"指令前,可能出现在"server"指令后,也可能出现在两条"server"指令之间。各位读者可能会有疑问,有什么差别么?那么请各位读者尝试下面这个配置: 235 | 236 | .. code:: c 237 | 238 | upstream test { 239 | server 192.168.0.1 weight=5; 240 | ip_hash; 241 | server 192.168.0.2 weight=7; 242 | } 243 | 244 | 神奇的事情出现了: 245 | 246 | .. code:: c 247 | 248 | nginx: [emerg] invalid parameter "weight=7" in nginx.conf:103 249 | configuration file nginx.conf test failed 250 | 251 | 可见ip_hash指令的确能影响到配置的解析。 252 | 253 | 指令 254 | +++++++++++++++++ 255 | 256 | 配置决定指令系统,现在就来看ip_hash的指令定义: 257 | 258 | .. code:: c 259 | 260 | static ngx_command_t ngx_http_upstream_ip_hash_commands[] = { 261 | 262 | { ngx_string("ip_hash"), 263 | NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS, 264 | ngx_http_upstream_ip_hash, 265 | 0, 266 | 0, 267 | NULL }, 268 | 269 | ngx_null_command 270 | }; 271 | 272 | 没有特别的东西,除了指令属性是NGX_HTTP_UPS_CONF。这个属性表示该指令的适用范围是upstream{}。 273 | 274 | 钩子 275 | +++++++++++++++++ 276 | 277 | 以从前面的章节得到的经验,大家应该知道这里就是模块的切入点了。负载均衡模块的钩子代码都是有规律的,这里通过ip_hash模块来分析这个规律。 278 | 279 | .. code:: c 280 | 281 | static char * 282 | ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 283 | { 284 | ngx_http_upstream_srv_conf_t *uscf; 285 | 286 | uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); 287 | 288 | uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash; 289 | 290 | uscf->flags = NGX_HTTP_UPSTREAM_CREATE 291 | |NGX_HTTP_UPSTREAM_MAX_FAILS 292 | |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 293 | |NGX_HTTP_UPSTREAM_DOWN; 294 | 295 | return NGX_CONF_OK; 296 | } 297 | 298 | 这段代码中有两点值得我们注意。一个是uscf->flags的设置,另一个是设置init_upstream回调。 299 | 300 | 设置uscf->flags 301 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 302 | 303 | 1. NGX_HTTP_UPSTREAM_CREATE:创建标志,如果含有创建标志的话,nginx会检查重复创建,以及必要参数是否填写; 304 | 305 | 2. NGX_HTTP_UPSTREAM_MAX_FAILS:可以在server中使用max_fails属性; 306 | 307 | 3. NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:可以在server中使用fail_timeout属性; 308 | 309 | 4. NGX_HTTP_UPSTREAM_DOWN:可以在server中使用down属性; 310 | 311 | 此外还有下面属性: 312 | 313 | 5. NGX_HTTP_UPSTREAM_WEIGHT:可以在server中使用weight属性; 314 | 315 | 6. NGX_HTTP_UPSTREAM_BACKUP:可以在server中使用backup属性。 316 | 317 | 聪明的读者如果联想到刚刚遇到的那个神奇的配置错误,可以得出一个结论:在负载均衡模块的指令处理函数中可以设置并修改upstream{}中"server"指令支持的属性。这是一个很重要的性质,因为不同的负载均衡模块对各种属性的支持情况都是不一样的,那么就需要在解析配置文件的时候检测出是否使用了不支持的负载均衡属性并给出错误提示,这对于提升系统维护性是很有意义的。但是,这种机制也存在缺陷,正如前面的例子所示,没有机制能够追加检查在更新支持属性之前已经配置了不支持属性的"server"指令。 318 | 319 | 设置init_upstream回调 320 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 321 | 322 | nginx初始化upstream时,会在ngx_http_upstream_init_main_conf函数中调用设置的回调函数初始化负载均衡模块。这里不太好理解的是uscf的具体位置。通过下面的示意图,说明upstream负载均衡模块的配置的内存布局。 323 | 324 | .. image:: http://tengine.taobao.org/book/_images/chapter-5-1.PNG 325 | 326 | 从图上可以看出,MAIN_CONF中ngx_upstream_module模块的配置项中有一个指针数组upstreams,数组中的每个元素对应就是配置文件中每一个upstream{}的信息。更具体的将会在后面的原理篇讨论。 327 | 328 | 初始化配置 329 | ++++++++++++++++++++++++ 330 | 331 | init_upstream回调函数执行时需要初始化负载均衡模块的配置,还要设置一个新钩子,这个钩子函数会在nginx处理每个请求时作为初始化函数调用,关于这个新钩子函数的功能,后面会有详细的描述。这里,我们先分析IP hash模块初始化配置的代码: 332 | 333 | .. code:: c 334 | 335 | ngx_http_upstream_init_round_robin(cf, us); 336 | us->peer.init = ngx_http_upstream_init_ip_hash_peer; 337 | 338 | 这段代码非常简单:IP hash模块首先调用另一个负载均衡模块Round Robin的初始化函数,然后再设置自己的处理请求阶段初始化钩子。实际上几个负载均衡模块可以组成一条链表,每次都是从链首的模块开始进行处理。如果模块决定不处理,可以将处理权交给链表中的下一个模块。这里,IP hash模块指定Round Robin模块作为自己的后继负载均衡模块,所以在自己的初始化配置函数中也对Round Robin模块进行初始化。 339 | 340 | 初始化请求 341 | ++++++++++++++++++++++++ 342 | 343 | nginx收到一个请求以后,如果发现需要访问upstream,就会执行对应的peer.init函数。这是在初始化配置时设置的回调函数。这个函数最重要的作用是构造一张表,当前请求可以使用的upstream服务器被依次添加到这张表中。之所以需要这张表,最重要的原因是如果upstream服务器出现异常,不能提供服务时,可以从这张表中取得其他服务器进行重试操作。此外,这张表也可以用于负载均衡的计算。之所以构造这张表的行为放在这里而不是在前面初始化配置的阶段,是因为upstream需要为每一个请求提供独立隔离的环境。 344 | 345 | 为了讨论peer.init的核心,我们还是看IP hash模块的实现: 346 | 347 | .. code:: c 348 | 349 | r->upstream->peer.data = &iphp->rrp; 350 | 351 | ngx_http_upstream_init_round_robin_peer(r, us); 352 | 353 | r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer; 354 | 355 | 第一行是设置数据指针,这个指针就是指向前面提到的那张表; 356 | 357 | 第二行是调用Round Robin模块的回调函数对该模块进行请求初始化。面前已经提到,一个负载均衡模块可以调用其他负载均衡模块以提供功能的补充。 358 | 359 | 第三行是设置一个新的回调函数get。该函数负责从表中取出某个服务器。除了get回调函数,还有另一个r->upstream->peer.free的回调函数。该函数在upstream请求完成后调用,负责做一些善后工作。比如我们需要维护一个upstream服务器访问计数器,那么可以在get函数中对其加1,在free中对其减1。如果是SSL的话,nginx还提供两个回调函数peer.set_session和peer.save_session。一般来说,有两个切入点实现负载均衡算法,其一是在这里,其二是在get回调函数中。 360 | 361 | peer.get和peer.free回调函数 362 | +++++++++++++++++++++++++++++++++ 363 | 364 | 这两个函数是负载均衡模块最底层的函数,负责实际获取一个连接和回收一个连接的预备操作。之所以说是预备操作,是因为在这两个函数中,并不实际进行建立连接或者释放连接的动作,而只是执行获取连接的地址或维护连接状态的操作。需要理解的清楚一点,在peer.get函数中获取连接的地址信息,并不代表这时连接一定没有被建立,相反的,通过get函数的返回值,nginx可以了解是否存在可用连接,连接是否已经建立。这些返回值总结如下: 365 | 366 | +-------------------+-------------------------------------------+-----------------------------------------+ 367 | |返回值 |说明 |nginx后续动作 | 368 | +-------------------+-------------------------------------------+-----------------------------------------+ 369 | |NGX_DONE |得到了连接地址信息,并且连接已经建立。 |直接使用连接,发送数据。 | 370 | +-------------------+-------------------------------------------+-----------------------------------------+ 371 | |NGX_OK |得到了连接地址信息,但连接并未建立。 |建立连接,如连接不能立即建立,设置事件, | 372 | | | |暂停执行本请求,执行别的请求。 | 373 | +-------------------+-------------------------------------------+-----------------------------------------+ 374 | |NGX_BUSY |所有连接均不可用。 |返回502错误至客户端。 | 375 | +-------------------+-------------------------------------------+-----------------------------------------+ 376 | 377 | 各位读者看到上面这张表,可能会有几个问题浮现出来: 378 | 379 | :Q: 什么时候连接是已经建立的? 380 | :A: 使用后端keepalive连接的时候,连接在使用完以后并不关闭,而是存放在一个队列中,新的请求只需要从队列中取出连接,这些连接都是已经准备好的。 381 | 382 | :Q: 什么叫所有连接均不可用? 383 | :A: 初始化请求的过程中,建立了一张表,get函数负责每次从这张表中不重复的取出一个连接,当无法从表中取得一个新的连接时,即所有连接均不可用。 384 | 385 | :Q: 对于一个请求,peer.get函数可能被调用多次么? 386 | :A: 正是如此。当某次peer.get函数得到的连接地址连接不上,或者请求对应的服务器得到异常响应,nginx会执行ngx_http_upstream_next,然后可能再次调用peer.get函数尝试别的连接。upstream整体流程如下: 387 | 388 | .. image:: http://tengine.taobao.org/book/_images/chapter-5-2.PNG 389 | 390 | 本节回顾 391 | +++++++++++++++++++++ 392 | 393 | 这一节介绍了负载均衡模块的基本组成。负载均衡模块的配置区集中在upstream{}块中。负载均衡模块的回调函数体系是以init_upstream为起点,经历init_peer,最终到达peer.get和peer.free。其中init_peer负责建立每个请求使用的server列表,peer.get负责从server列表中选择某个server(一般是不重复选择),而peer.free负责server释放前的资源释放工作。最后,这一节通过一张图将upstream模块和负载均衡模块在请求处理过程中的相互关系展现出来。 394 | -------------------------------------------------------------------------------- /source/chapter_06.rst: -------------------------------------------------------------------------------- 1 | 其他模块 (40%) 2 | ================== 3 | Nginx的模块种类挺多的,除了HTTP模块,还有一些核心模块和mail系列模块。核心模块主要是做一些基础功能,比如Nginx的启动初始化,event处理机制,错误日志的初始化,ssl的初始化,正则处理初始化。 4 | 5 | mail模块可以对imap,pop3,smtp等协议进行反向代理,这些模块本身不对邮件内容进行处理。 6 | 7 | core模块 (40%) 8 | ------------------ 9 | Nginx的启动模块 (40%) 10 | +++++++++++++++++++++++++++ 11 | 启动模块从启动Nginx进程开始,做了一系列的初始化工作,源代码位于src/core/nginx.c,从main函数开始: 12 | 13 | 1. 时间、正则、错误日志、ssl等初始化 14 | 15 | 2. 读入命令行参数 16 | 17 | 3. OS相关初始化 18 | 19 | 4. 读入并解析配置 20 | 21 | 5. 核心模块初始化 22 | 23 | 6. 创建各种暂时文件和目录 24 | 25 | 7. 创建共享内存 26 | 27 | 8. 打开listen的端口 28 | 29 | 9. 所有模块初始化 30 | 31 | 10. 启动worker进程 32 | 33 | 34 | event模块 (40%) 35 | -------------------- 36 | 37 | event的类型和功能 (40%) 38 | +++++++++++++++++++++++++++ 39 | Nginx是以event(事件)处理模型为基础的模块。它为了支持跨平台,抽象出了event模块。它支持的event处理类型有:AIO(异步IO),/dev/poll(Solaris 和Unix特有),epoll(Linux特有),eventport(Solaris 10特有),kqueue(BSD特有),poll,rtsig(实时信号),select等。 40 | 41 | event模块的主要功能就是,监听accept后建立的连接,对读写事件进行添加删除。事件处理模型和Nginx的非阻塞IO模型结合在一起使用。当IO可读可写的时候,相应的读写事件就会被唤醒,此时就会去处理事件的回调函数。 42 | 43 | 特别对于Linux,Nginx大部分event采用epoll EPOLLET(边沿触发)的方法来触发事件,只有listen端口的读事件是EPOLLLT(水平触发)。对于边沿触发,如果出现了可读事件,必须及时处理,否则可能会出现读事件不再触发,连接饿死的情况。 44 | 45 | .. code:: c 46 | 47 | typedef struct { 48 | /* 添加删除事件 */ 49 | ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); 50 | ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); 51 | 52 | ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); 53 | ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); 54 | 55 | /* 添加删除连接,会同时监听读写事件 */ 56 | ngx_int_t (*add_conn)(ngx_connection_t *c); 57 | ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags); 58 | 59 | ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait); 60 | /* 处理事件的函数 */ 61 | ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer, 62 | ngx_uint_t flags); 63 | 64 | ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer); 65 | void (*done)(ngx_cycle_t *cycle); 66 | } ngx_event_actions_t; 67 | 68 | 上述是event处理抽象出来的关键结构体,可以看到,每个event处理模型,都需要实现部分功能。最关键的是add和del功能,就是最基本的添加和删除事件的函数。 69 | 70 | accept锁 (40%) 71 | +++++++++++++++++++ 72 | 73 | Nginx是多进程程序,80端口是各进程所共享的,多进程同时listen 80端口,势必会产生竞争,也产生了所谓的“惊群”效应。当内核accept一个连接时,会唤醒所有等待中的进程,但实际上只有一个进程能获取连接,其他的进程都是被无效唤醒的。所以Nginx采用了自有的一套accept加锁机制,避免多个进程同时调用accept。Nginx多进程的锁在底层默认是通过CPU自旋锁来实现。如果操作系统不支持自旋锁,就采用文件锁。 74 | 75 | Nginx事件处理的入口函数是ngx_process_events_and_timers(),下面是部分代码,可以看到其加锁的过程: 76 | 77 | .. code:: c 78 | 79 | if (ngx_use_accept_mutex) { 80 | if (ngx_accept_disabled > 0) { 81 | ngx_accept_disabled--; 82 | 83 | } else { 84 | if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { 85 | return; 86 | } 87 | 88 | if (ngx_accept_mutex_held) { 89 | flags |= NGX_POST_EVENTS; 90 | 91 | } else { 92 | if (timer == NGX_TIMER_INFINITE 93 | || timer > ngx_accept_mutex_delay) 94 | { 95 | timer = ngx_accept_mutex_delay; 96 | } 97 | } 98 | } 99 | } 100 | 101 | 在ngx_trylock_accept_mutex()函数里面,如果拿到了锁,Nginx会把listen的端口读事件加入event处理,该进程在有新连接进来时就可以进行accept了。注意accept操作是一个普通的读事件。下面的代码说明了这点: 102 | 103 | .. code:: c 104 | 105 | (void) ngx_process_events(cycle, timer, flags); 106 | 107 | if (ngx_posted_accept_events) { 108 | ngx_event_process_posted(cycle, &ngx_posted_accept_events); 109 | } 110 | 111 | if (ngx_accept_mutex_held) { 112 | ngx_shmtx_unlock(&ngx_accept_mutex); 113 | } 114 | 115 | ngx_process_events()函数是所有事件处理的入口,它会遍历所有的事件。抢到了accept锁的进程跟一般进程稍微不同的是,它被加上了NGX_POST_EVENTS标志,也就是说在ngx_process_events() 函数里面只接受而不处理事件,并加入post_events的队列里面。直到ngx_accept_mutex锁去掉以后才去处理具体的事件。为什么这样?因为ngx_accept_mutex是全局锁,这样做可以尽量减少该进程抢到锁以后,从accept开始到结束的时间,以便其他进程继续接收新的连接,提高吞吐量。 116 | 117 | ngx_posted_accept_events和ngx_posted_events就分别是accept延迟事件队列和普通延迟事件队列。可以看到ngx_posted_accept_events还是放到ngx_accept_mutex锁里面处理的。该队列里面处理的都是accept事件,它会一口气把内核backlog里等待的连接都accept进来,注册到读写事件里。 118 | 119 | 而ngx_posted_events是普通的延迟事件队列。一般情况下,什么样的事件会放到这个普通延迟队列里面呢?我的理解是,那些CPU耗时比较多的都可以放进去。因为Nginx事件处理都是根据触发顺序在一个大循环里依次处理的,因为Nginx一个进程同时只能处理一个事件,所以有些耗时多的事件会把后面所有事件的处理都耽搁了。 120 | 121 | 除了加锁,Nginx也对各进程的请求处理的均衡性作了优化,也就是说,如果在负载高的时候,进程抢到的锁过多,会导致这个进程被禁止接受请求一段时间。 122 | 123 | 比如,在ngx_event_accept函数中,有类似代码: 124 | 125 | .. code:: c 126 | 127 | ngx_accept_disabled = ngx_cycle->connection_n / 8 128 | - ngx_cycle->free_connection_n; 129 | 130 | ngx_cycle->connection_n是进程可以分配的连接总数,ngx_cycle->free_connection_n是空闲的进程数。上述等式说明了,当前进程的空闲进程数小于1/8的话,就会被禁止accept一段时间。 131 | 132 | 133 | 定时器 (40%) 134 | ++++++++++++++++ 135 | Nginx在需要用到超时的时候,都会用到定时器机制。比如,建立连接以后的那些读写超时。Nginx使用红黑树来构造定期器,红黑树是一种有序的二叉平衡树,其查找插入和删除的复杂度都为O(logn),所以是一种比较理想的二叉树。 136 | 137 | 定时器的机制就是,二叉树的值是其超时时间,每次查找二叉树的最小值,如果最小值已经过期,就删除该节点,然后继续查找,直到所有超时节点都被删除。 138 | 139 | mail模块 140 | --------------- 141 | 142 | mail模块的实现 143 | +++++++++++++++ 144 | 145 | mail模块的功能 146 | +++++++++++++++ 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /source/chapter_07.rst: -------------------------------------------------------------------------------- 1 | 模块开发高级篇(30%) 2 | =============================== 3 | 4 | 5 | 变量(80%) 6 | ---------------- 7 | 8 | 9 | 综述 10 | +++++++++++++++++++++++++++ 11 | 在Nginx中同一个请求需要在模块之间数据的传递或者说在配置文件里面使用模块动态的数据一般来说都是使用变量,比如在HTTP模块中导出了host/remote_addr等变量,这样我们就可以在配置文件中以及在其他的模块使用这个变量。在Nginx中,有两种定义变量的方式,一种是在配置文件中,使用set指令,一种就是上面我们提到的在模块中定义变量,然后导出. 12 | 13 | 在Nginx中所有的变量都是与HTTP相关的(也就是说赋值都是在请求阶段),并且基本上是同时保存在两个数据结构中,一个就是hash表(可选),另一个是数组. 比如一些特殊的变量,比如arg_xxx/cookie_xxx等,这些变量的名字是不确定的(因此不能内置),而且他们还是只读的(不能交由用户修改),如果每个都要放到hash表中的话(不知道用户会取多少个),会很占空间的,因此这些变量就没有hash,只有索引.这里要注意,用户不能定义这样的变量,这样的变量只存在于Nginx内部. 14 | 15 | 对应的变量结构体是这样子(每一个变量都是一个ngx_http_variable_s结构体)的: 16 | 17 | .. code:: c 18 | 19 | struct ngx_http_variable_s { 20 | ngx_str_t name; /* must be first to build the hash */ 21 | ngx_http_set_variable_pt set_handler; 22 | ngx_http_get_variable_pt get_handler; 23 | uintptr_t data; 24 | ngx_uint_t flags; 25 | ngx_uint_t index; 26 | }; 27 | 28 | 其中name表示对应的变量名字,set/get_handler表示对应的设置以及读取回调,而data是传递给回调的参数,flags表示变量的属性,index提供了一个索引(数组的脚标),从而可以迅速定位到对应的变量。set/get_handler只有在真正读取设置变量的时候才会被调用. 29 | 30 | 这里要注意flag属性,flag属性就是由下面的几个属性组合而成: 31 | 32 | .. code:: c 33 | 34 | #define NGX_HTTP_VAR_CHANGEABLE 1 35 | #define NGX_HTTP_VAR_NOCACHEABLE 2 36 | #define NGX_HTTP_VAR_INDEXED 4 37 | #define NGX_HTTP_VAR_NOHASH 8 38 | 39 | 1. NGX_HTTP_VAR_CHANGEABLE表示这个变量是可变的.Nginx有很多内置变量是不可变的,比如arg_xxx这类变量,如果你使用set指令来修改,那么Nginx就会报错. 40 | 2. NGX_HTTP_VAR_NOCACHEABLE表示这个变量每次都要去取值,而不是直接返回上次cache的值(配合对应的接口). 41 | 3. NGX_HTTP_VAR_INDEXED表示这个变量是用索引读取的. 42 | 4. NGX_HTTP_VAR_NOHASH表示这个变量不需要被hash. 43 | 44 | 而变量在Nginx中的初始化流程是这样的: 45 | 46 | 1. 首先当解析HTTP之前会调用ngx_http_variables_add_core_vars(pre_config)来将HTTP core模块导出的变量(http_host/remote_addr...)添加进全局的hash key链中. 47 | 48 | 2. 解析完HTTP模块之后,会调用ngx_http_variables_init_vars来初始化所有的变量(不仅包括HTTP core模块的变量,也包括其他的HTTP模块导出的变量,以及配置文件中使用set命令设置的变量),这里的初始化包括初始化hash表,以及初始化数组索引. 49 | 50 | 3. 当每次请求到来时会给每个请求创建一个变量数组(数组的个数就是上面第二步所保存的变量个数)。然后只有取变量值的时候,才会将变量保存在对应的变量数组位置。 51 | 52 | 创建变量 53 | +++++++++++++++++++++++++++ 54 | 在Nginx中,创建变量有两种方式,分别是在配置文件中使用set指令,和在模块中调用对应的接口,在配置文件中创建变量比较简单,因此我们主要来看如何在模块中创建自己的变量。 55 | 56 | 在Nginx中提供了下面的接口,可以供模块调用来创建变量。 57 | 58 | .. code:: c 59 | 60 | ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags); 61 | 62 | 这个函数所做的工作就是将变量 "name"添加进全局的hash key表中,然后初始化一些域,不过这里要注意,对应的变量的get/set回调,需要当这个函数返回之后,显式的设置,比如在split_clients模块中的例子: 63 | 64 | .. code:: c 65 | 66 | var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); 67 | if (var == NULL) { 68 | return NGX_CONF_ERROR; 69 | } 70 | //设置回调 71 | var->get_handler = ngx_http_split_clients_variable; 72 | var->data = (uintptr_t) ctx; 73 | 74 | 而对应的回调函数原型是这样的: 75 | 76 | .. code:: c 77 | 78 | typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r, 79 | ngx_http_variable_value_t *v, uintptr_t data); 80 | typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r, 81 | ngx_http_variable_value_t *v, uintptr_t data); 82 | 83 | 回调函数比较简单,第一个参数是当前请求,第二个是需要设置或者获取的变量值,第三个是初始化时的回调指针,这里我们着重来看一下ngx_http_variable_value_t,下面就是这个结构体的原型: 84 | 85 | .. code:: c 86 | 87 | typedef struct { 88 | unsigned len:28; 89 | 90 | unsigned valid:1; 91 | unsigned no_cacheable:1; 92 | unsigned not_found:1; 93 | unsigned escape:1; 94 | u_char *data; 95 | } ngx_variable_value_t; 96 | 97 | 这里主要是data域,当我们在get_handle中设置变量值的时候,只需要将对应的值放入到data中就可以了,这里data需要在get_handle中分配内存,比如下面的例子(ngx_http_fastcgi_script_name_variable),就是fastcgi_script_name变量的get_handler代码片段: 98 | 99 | .. code:: c 100 | 101 | v->len = f->script_name.len + flcf->index.len; 102 | 103 | v->data = ngx_pnalloc(r->pool, v->len); 104 | if (v->data == NULL) { 105 | return NGX_ERROR; 106 | } 107 | 108 | p = ngx_copy(v->data, f->script_name.data, f->script_name.len); 109 | ngx_memcpy(p, flcf->index.data, flcf->index.len); 110 | 111 | 112 | 使用变量 113 | +++++++++++++++++++++++++++ 114 | 115 | Nginx的内部变量指的就是Nginx的官方模块中所导出的变量,在Nginx中,大部分常用的变量都是CORE HTTP模块导出的。而在Nginx中,不仅可以在模块代码中使用变量,而且还可以在配置文件中使用。 116 | 117 | 假设我们需要在配置文件中使用http模块的host变量,那么只需要这样在变量名前加一个$符号就可以了($host).而如果需要在模块中使用host变量,那么就比较麻烦,Nginx提供了下面几个接口来取得变量: 118 | 119 | .. code:: c 120 | 121 | ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, 122 | ngx_uint_t index); 123 | ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, 124 | ngx_uint_t index); 125 | ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, 126 | ngx_str_t *name, ngx_uint_t key); 127 | 128 | 他们的区别是这样子的,ngx_http_get_indexed_variable和ngx_http_get_flushed_variable都是用来取得有索引的变量,不过他们的区别是后一个会处理 129 | NGX_HTTP_VAR_NOCACHEABLE这个标记,也就是说如果你想要cache你的变量值,那么你的变量属性就不能设置NGX_HTTP_VAR_NOCACHEABLE,并且通过ngx_http_get_flushed_variable来获取变量值.而ngx_http_get_variable和上面的区别就是它能够得到没有索引的变量值. 130 | 131 | 通过上面我们知道可以通过索引来得到变量值,可是这个索引该如何取得呢,Nginx也提供了对应的接口: 132 | 133 | .. code:: c 134 | 135 | ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name); 136 | 137 | 138 | 通过这个接口,就可以取得对应变量名的索引值。 139 | 140 | 接下来来看对应的例子,比如在http_log模块中,如果在log_format中配置了对应的变量,那么它会调用ngx_http_get_variable_index来保存索引: 141 | 142 | .. code:: c 143 | 144 | static ngx_int_t 145 | ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, 146 | ngx_str_t *value) 147 | { 148 | ngx_int_t index; 149 | //得到变量的索引 150 | index = ngx_http_get_variable_index(cf, value); 151 | if (index == NGX_ERROR) { 152 | return NGX_ERROR; 153 | } 154 | 155 | op->len = 0; 156 | op->getlen = ngx_http_log_variable_getlen; 157 | op->run = ngx_http_log_variable; 158 | //保存索引值 159 | op->data = index; 160 | 161 | return NGX_OK; 162 | } 163 | 164 | 然后http_log模块会使用ngx_http_get_indexed_variable来得到对应的变量值,这里要注意,就是使用这个接口的时候,判断返回值,不仅要判断是否为空,也需要判断value->not_found,这是因为只有第一次调用才会返回空,后续返回就不是空,因此需要判断value->not_found: 165 | 166 | .. code:: c 167 | 168 | static u_char * 169 | ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) 170 | { 171 | ngx_http_variable_value_t *value; 172 | //获取变量值 173 | value = ngx_http_get_indexed_variable(r, op->data); 174 | 175 | if (value == NULL || value->not_found) { 176 | *buf = '-'; 177 | return buf + 1; 178 | } 179 | 180 | if (value->escape == 0) { 181 | return ngx_cpymem(buf, value->data, value->len); 182 | 183 | } else { 184 | return (u_char *) ngx_http_log_escape(buf, value->data, value->len); 185 | } 186 | } 187 | 188 | 189 | upstream 190 | ------------------ 191 | 192 | 使用subrequest访问upstream 193 | +++++++++++++++++++++++++++ 194 | 195 | 196 | 超越upstream 197 | +++++++++++++++++++++++++++ 198 | 199 | 200 | event机制 201 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 202 | 203 | 204 | 例讲(主动健康检查模块) 205 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 206 | 207 | 208 | 209 | 使用lua模块 210 | ------------------- 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /source/chapter_08.rst: -------------------------------------------------------------------------------- 1 | 高性能服务器设计 2 | ================================== 3 | 4 | 5 | 6 | c10k问题 7 | -------------- 8 | 9 | 10 | 11 | 高性能服务器编写的关键原则 12 | ------------------------------------------- 13 | 14 | 15 | 16 | 事件驱动的核心引擎 17 | ------------------------------- 18 | 19 | 20 | 21 | 定时器管理 22 | ------------------- 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /source/chapter_09.rst: -------------------------------------------------------------------------------- 1 | nginx架构详解(50%) 2 | =========================== 3 | nginx的下篇将会更加深入的介绍nginx的实现原理。上一章,我们了解到了如何设计一个高性能服务器,那这一章将会开始讲解,nginx是如何一步一步实现高性能服务器的。 4 | 5 | 6 | 7 | nginx的源码目录结构(100%) 8 | ------------------------------ 9 | 10 | nginx的优秀除了体现在程序结构以及代码风格上,nginx的源码组织也同样简洁明了,目录结构层次结构清晰,值得我们去学习。nginx的源码目录与nginx的模块化以及功能的划分是紧密结合,这也使得我们可以很方便地找到相关功能的代码。这节先介绍nginx源码的目录结构,先对nginx的源码有一个大致的认识,下节会讲解nginx如何编译。 11 | 12 | 下面是nginx源码的目录结构: :: 13 | 14 | . 15 | ├── auto 自动检测系统环境以及编译相关的脚本 16 | │ ├── cc 关于编译器相关的编译选项的检测脚本 17 | │ ├── lib nginx编译所需要的一些库的检测脚本 18 | │ ├── os 与平台相关的一些系统参数与系统调用相关的检测 19 | │ └── types 与数据类型相关的一些辅助脚本 20 | ├── conf 存放默认配置文件,在make install后,会拷贝到安装目录中去 21 | ├── contrib 存放一些实用工具,如geo配置生成工具(geo2nginx.pl) 22 | ├── html 存放默认的网页文件,在make install后,会拷贝到安装目录中去 23 | ├── man nginx的man手册 24 | └── src 存放nginx的源代码 25 | ├── core nginx的核心源代码,包括常用数据结构的定义,以及nginx初始化运行的核心代码如main函数 26 | ├── event 对系统事件处理机制的封装,以及定时器的实现相关代码 27 | │ └── modules 不同事件处理方式的模块化,如select、poll、epoll、kqueue等 28 | ├── http nginx作为http服务器相关的代码 29 | │ └── modules 包含http的各种功能模块 30 | ├── mail nginx作为邮件代理服务器相关的代码 31 | ├── misc 一些辅助代码,测试c++头的兼容性,以及对google_perftools的支持 32 | └── os 主要是对各种不同体系统结构所提供的系统函数的封装,对外提供统一的系统调用接口 33 | 34 | 35 | 36 | nginx的configure原理(100%) 37 | --------------------------- 38 | 39 | nginx的编译旅程将从configure开始,configure脚本将根据我们输入的选项、系统环境参与来生成所需的文件(包含源文件与Makefile文件)。configure会调用一系列auto脚本来实现编译环境的初始化。 40 | 41 | 42 | 43 | auto脚本 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | auto脚本由一系列脚本组成,他们有一些是实现一些通用功能由其它脚本来调用(如have),有一些则是完成一些特定的功能(如option)。脚本之间的主要执行顺序及调用关系如下图所示(由上到下,表示主流程的执行): 47 | 48 | .. image:: https://github.com/taobao/nginx-book/blob/master/source/images/chapter-9-1.jpg 49 | 50 | 接下来,我们结合代码来分析下configure的原理: 51 | 52 | 1) 初始化 53 | 54 | .. code:: c 55 | 56 | . auto/options 57 | . auto/init 58 | . auto/sources 59 | 60 | 这是configure源码开始执行的前三行,依次交由auto目录下面的option、init、sources来处理。 61 | 62 | 2) auto/options主要是处理用户输入的configure选项,以及输出帮助信息等。读者可以结合nginx的源码来阅读本章内容。由于篇幅关系,这里大致列出此文件的结构: 63 | 64 | .. code:: c 65 | 66 | ##1. 设置选项对应的shell变量以及他们的初始值 67 | help=no 68 | NGX_PREFIX= 69 | NGX_SBIN_PATH= 70 | NGX_CONF_PREFIX= 71 | NGX_CONF_PATH= 72 | NGX_ERROR_LOG_PATH= 73 | NGX_PID_PATH= 74 | NGX_LOCK_PATH= 75 | NGX_USER= 76 | NGX_GROUP= 77 | 78 | ... 79 | 80 | 81 | ## 2, 处理每一个选项值,并设置到对应的全局变量中 82 | for option 83 | do 84 | opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`" 85 | 86 | # 得到此选项的value部分 87 | case "$option" in 88 | -*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;; 89 | *) value="" ;; 90 | esac 91 | 92 | # 根据option内容进行匹配,并设置相应的选项 93 | case "$option" in 94 | --help) help=yes ;; 95 | --prefix=) NGX_PREFIX="!" ;; 96 | --prefix=*) NGX_PREFIX="$value" ;; 97 | --sbin-path=*) NGX_SBIN_PATH="$value" ;; 98 | --conf-path=*) NGX_CONF_PATH="$value" ;; 99 | --error-log-path=*) NGX_ERROR_LOG_PATH="$value";; 100 | --pid-path=*) NGX_PID_PATH="$value" ;; 101 | --lock-path=*) NGX_LOCK_PATH="$value" ;; 102 | --user=*) NGX_USER="$value" ;; 103 | --group=*) NGX_GROUP="$value" ;; 104 | 105 | ... 106 | 107 | *) 108 | # 没有找到的对应选项 109 | echo "$0: error: invalid option \"$option\"" 110 | exit 1 111 | ;; 112 | esac 113 | done 114 | 115 | ## 3. 对选项进行处理 116 | 117 | # 如果有--help,则输出帮助信息 118 | if [ $help = yes ]; then 119 | 120 | cat << END 121 | 122 | --help print this message 123 | 124 | --prefix=PATH set installation prefix 125 | --sbin-path=PATH set nginx binary pathname 126 | --conf-path=PATH set nginx.conf pathname 127 | --error-log-path=PATH set error log pathname 128 | --pid-path=PATH set nginx.pid pathname 129 | --lock-path=PATH set nginx.lock pathname 130 | 131 | --user=USER set non-privileged user for 132 | worker processes 133 | --group=GROUP set non-privileged group for 134 | worker processes 135 | END 136 | 137 | exit 1 138 | fi 139 | 140 | # 默认文件路径 141 | NGX_CONF_PATH=${NGX_CONF_PATH:-conf/nginx.conf} 142 | NGX_CONF_PREFIX=`dirname $NGX_CONF_PATH` 143 | NGX_PID_PATH=${NGX_PID_PATH:-logs/nginx.pid} 144 | NGX_LOCK_PATH=${NGX_LOCK_PATH:-logs/nginx.lock} 145 | 146 | ... 147 | 148 | 上面的代码中,我们选用了文件中的部分代码进行了说明。大家可结合源码再进行分析。auto/options的目的主要是处理用户选项,并由选项生成一些全局变量的值,这些值在其它文件中会用到。该文件也会输出configure的帮助信息。 149 | 150 | 3) auto/init 151 | 152 | 该文件的目的在于初始化一些临时文件的路径,检查echo的兼容性,并创建Makefile。 153 | 154 | .. code:: c 155 | 156 | # 生成最终执行编译的makefile文件路径 157 | NGX_MAKEFILE=$NGX_OBJS/Makefile 158 | # 动态生成nginx模块列表的路径,由于nginx的的一些模块是可以选择编译的,而且可以添加自己的模块,所以模块列表是动态生成的 159 | NGX_MODULES_C=$NGX_OBJS/ngx_modules.c 160 | 161 | NGX_AUTO_HEADERS_H=$NGX_OBJS/ngx_auto_headers.h 162 | NGX_AUTO_CONFIG_H=$NGX_OBJS/ngx_auto_config.h 163 | 164 | # 自动测试目录与日志输出文件 165 | NGX_AUTOTEST=$NGX_OBJS/autotest 166 | # 如果configure出错,可用来查找出错的原因 167 | NGX_AUTOCONF_ERR=$NGX_OBJS/autoconf.err 168 | 169 | NGX_ERR=$NGX_OBJS/autoconf.err 170 | MAKEFILE=$NGX_OBJS/Makefile 171 | 172 | 173 | NGX_PCH= 174 | NGX_USE_PCH= 175 | 176 | 177 | # 检查echo是否支持-n或\c 178 | 179 | # check the echo's "-n" option and "\c" capability 180 | 181 | if echo "test\c" | grep c >/dev/null; then 182 | 183 | # 不支持-c的方式,检查是否支持-n的方式 184 | 185 | if echo -n test | grep n >/dev/null; then 186 | ngx_n= 187 | ngx_c= 188 | 189 | else 190 | ngx_n=-n 191 | ngx_c= 192 | fi 193 | 194 | else 195 | ngx_n= 196 | ngx_c='\c' 197 | fi 198 | 199 | # 创建最初始的makefile文件 200 | # default表示目前编译对象 201 | # clean表示执行clean工作时,需要删除makefile文件以及objs目录 202 | # 整个过程中只会生成makefile文件以及objs目录,其它所有临时文件都在objs目录之下,所以执行clean后,整个目录还原到初始状态 203 | # 要再次执行编译,需要重新执行configure命令 204 | 205 | # create Makefile 206 | 207 | cat << END > Makefile 208 | 209 | default: build 210 | 211 | clean: 212 | rm -rf Makefile $NGX_OBJS 213 | END 214 | 215 | 4) auto/sources 216 | 217 | 该文件从文件名中就可以看出,它的主要功能是跟源文件相关的。它的主要作用是定义不同功能或系统所需要的文件的变量。根据功能,分为CORE/REGEX/EVENT/UNIX/FREEBSD/HTTP等。每一个功能将会由四个变量组成,"_MODULES"表示此功能相关的模块,最终会输出到ngx_modules.c文件中,即动态生成需要编译到nginx中的模块;"INCS"表示此功能依赖的源码目录,查找头文件的时候会用到,在编译选项中,会出现在"-I"中;”DEPS"显示指明在Makefile中需要依赖的文件名,即编译时,需要检查这些文件的更新时间;"SRCS"表示需要此功能编译需要的源文件。 218 | 219 | 拿core来说: 220 | 221 | .. code:: c 222 | 223 | CORE_MODULES="ngx_core_module ngx_errlog_module ngx_conf_module ngx_emp_server_module ngx_emp_server_core_module" 224 | 225 | CORE_INCS="src/core" 226 | 227 | CORE_DEPS="src/core/nginx.h \ 228 | src/core/ngx_config.h \ 229 | src/core/ngx_core.h \ 230 | src/core/ngx_log.h \ 231 | src/core/ngx_palloc.h \ 232 | src/core/ngx_array.h \ 233 | src/core/ngx_list.h \ 234 | src/core/ngx_hash.h \ 235 | src/core/ngx_buf.h \ 236 | src/core/ngx_queue.h \ 237 | src/core/ngx_string.h \ 238 | src/core/ngx_parse.h \ 239 | src/core/ngx_inet.h \ 240 | src/core/ngx_file.h \ 241 | src/core/ngx_crc.h \ 242 | src/core/ngx_crc32.h \ 243 | src/core/ngx_murmurhash.h \ 244 | src/core/ngx_md5.h \ 245 | src/core/ngx_sha1.h \ 246 | src/core/ngx_rbtree.h \ 247 | src/core/ngx_radix_tree.h \ 248 | src/core/ngx_slab.h \ 249 | src/core/ngx_times.h \ 250 | src/core/ngx_shmtx.h \ 251 | src/core/ngx_connection.h \ 252 | src/core/ngx_cycle.h \ 253 | src/core/ngx_conf_file.h \ 254 | src/core/ngx_resolver.h \ 255 | src/core/ngx_open_file_cache.h \ 256 | src/core/nginx_emp_server.h \ 257 | src/core/emp_server.h \ 258 | src/core/task_thread.h \ 259 | src/core/standard.h \ 260 | src/core/dprint.h \ 261 | src/core/ngx_crypt.h" 262 | 263 | CORE_SRCS="src/core/nginx.c \ 264 | src/core/ngx_log.c \ 265 | src/core/ngx_palloc.c \ 266 | src/core/ngx_array.c \ 267 | src/core/ngx_list.c \ 268 | src/core/ngx_hash.c \ 269 | src/core/ngx_buf.c \ 270 | src/core/ngx_queue.c \ 271 | src/core/ngx_output_chain.c \ 272 | src/core/ngx_string.c \ 273 | src/core/ngx_parse.c \ 274 | src/core/ngx_inet.c \ 275 | src/core/ngx_file.c \ 276 | src/core/ngx_crc32.c \ 277 | src/core/ngx_murmurhash.c \ 278 | src/core/ngx_md5.c \ 279 | src/core/ngx_rbtree.c \ 280 | src/core/ngx_radix_tree.c \ 281 | src/core/ngx_slab.c \ 282 | src/core/ngx_times.c \ 283 | src/core/ngx_shmtx.c \ 284 | src/core/ngx_connection.c \ 285 | src/core/ngx_cycle.c \ 286 | src/core/ngx_spinlock.c \ 287 | src/core/ngx_cpuinfo.c \ 288 | src/core/ngx_conf_file.c \ 289 | src/core/ngx_resolver.c \ 290 | src/core/ngx_open_file_cache.c \ 291 | src/core/nginx_emp_server.c \ 292 | src/core/emp_server.c \ 293 | src/core/standard.c \ 294 | src/core/task_thread.c \ 295 | src/core/dprint.c \ 296 | src/core/ngx_crypt.c" 297 | 298 | 如果我们自己写一个第三方模块,我们可能会引用到这些变量的值,或对这些变量进行修改,比如添加我们自己的模块,或添加自己的一个头文件查找目录(在第三方模块的config中),在后面,我们会看到它是如何加框第三方模块的。 299 | 在继续分析执行流程之前,我们先介绍一些工具脚本。 300 | 301 | 5) auto/have 302 | 303 | .. code:: c 304 | 305 | cat << END >> $NGX_AUTO_CONFIG_H 306 | 307 | #ifndef $have 308 | #define $have 1 309 | #endif 310 | 311 | END 312 | 313 | 从代码中,我们可以看到,这个工具的作用是,将$have变量的值,宏定义为1,并输出到auto_config文件中。通常我们通过这个工具来控制是否打开某个特性。这个工具在使用前,需要先定义宏的名称 ,即$have变量。 314 | 315 | 6) 再回到configure文件中来: 316 | 317 | .. code:: c 318 | 319 | # NGX_DEBUG是在auto/options文件中处理的,如果有--with-debug选项,则其值是YES 320 | if [ $NGX_DEBUG = YES ]; then 321 | # 当有debug选项时,会定义NGX_DEBUG宏 322 | have=NGX_DEBUG . auto/have 323 | fi 324 | 325 | 这段代码中,可以看出,configure是如何定义一个特性的:通过宏定义,输出到config头文件中,然后在程序中可以判断这个宏是否有定义,来实现不同的特性。 326 | 327 | configure文件中继续向下: 328 | 329 | .. code:: c 330 | 331 | # 编译器选项 332 | . auto/cc/conf 333 | 334 | # 头文件支持宏定义 335 | if [ "$NGX_PLATFORM" != win32 ]; then 336 | . auto/headers 337 | fi 338 | 339 | # 操作系统相关的配置的检测 340 | . auto/os/conf 341 | 342 | # unix体系下的通用配置检测 343 | if [ "$NGX_PLATFORM" != win32 ]; then 344 | . auto/unix 345 | fi 346 | 347 | configure会依次调用其它几个文件,来进行环境的检测,包括编译器、操作系统相关。 348 | 349 | 7) auto/feature 350 | 351 | nginx的configure会自动检测不同平台的特性,神奇之处就是auto/feature的实现,在继续向下分析之前,我们先来看看这个工具的实现原理。此工具的核心思想是,输出一小段代表性c程序,然后设置好编译选项,再进行编译连接运行,再对结果进行分析。例如,如果想检测某个库是否存在,就在小段c程序里面调用库里面的某个函数,再进行编译链接,如果出错,则表示库的环境不正常,如果编译成功,且运行正常,则库的环境检测正常。我们在写nginx第三方模块时,也常使用此工具来进行环境的检测,所以,此工具的作用贯穿整个configure过程。 352 | 353 | 先看一小段使用例子: 354 | 355 | .. code:: c 356 | 357 | ngx_feature="poll()" 358 | ngx_feature_name= 359 | ngx_feature_run=no 360 | ngx_feature_incs="#include " 361 | ngx_feature_path= 362 | ngx_feature_libs= 363 | ngx_feature_test="int n; struct pollfd pl; 364 | pl.fd = 0; 365 | pl.events = 0; 366 | pl.revents = 0; 367 | n = poll(&pl, 1, 0); 368 | if (n == -1) return 1" 369 | . auto/feature 370 | 371 | if [ $ngx_found = no ]; then 372 | # 如果没有找到poll,就设置变量的值 373 | EVENT_POLL=NONE 374 | fi 375 | 376 | 这段代码在auto/unix里面实现,用来检测当前操作系统是否支持poll函数调用。在调用auto/feature之前,需要先设置几个输入参数变量的值,然后结果会存在$ngx_found变量里面, 并输出宏定义以表示支持此特性: 377 | 378 | .. code:: c 379 | 380 | $ngx_feature 特性名称 381 | $ngx_feature_name 特性的宏定义名称,如果特性测试成功,则会定义该宏定义 382 | $ngx_feature_path 编译时要查找头文件目录 383 | $ngx_feature_test 要执行的测试代码 384 | $ngx_feature_incs 在代码中要include的头文件 385 | $ngx_feature_libs 编译时需要link的库文件选项 386 | $ngx_feature_run 编译成功后,对二进制文件需要做的动作,可以是yes value bug 其它 387 | 388 | #ngx_found 如果找到,并测试成功,其值为yes,否则其值为no 389 | 390 | 看看auto/feature的关键代码: 391 | 392 | .. code:: c 393 | 394 | # 初始化输出结果为no 395 | ngx_found=no 396 | 397 | #将特性名称小写转换成大写 398 | if test -n "$ngx_feature_name"; then 399 | # 小写转大写 400 | ngx_have_feature=`echo $ngx_feature_name \ 401 | | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` 402 | fi 403 | 404 | # 将所有include目录转换成编译选项 405 | if test -n "$ngx_feature_path"; then 406 | for ngx_temp in $ngx_feature_path; do 407 | ngx_feature_inc_path="$ngx_feature_inc_path -I $ngx_temp" 408 | done 409 | fi 410 | 411 | 412 | # 生成临时的小段c程序代码。 413 | # $ngx_feature_incs变量是程序需要include的头文件 414 | # $ngx_feature_test是测试代码 415 | cat << END > $NGX_AUTOTEST.c 416 | 417 | #include 418 | $NGX_INCLUDE_UNISTD_H 419 | $ngx_feature_incs 420 | 421 | int main() { 422 | $ngx_feature_test; 423 | return 0; 424 | } 425 | 426 | END 427 | 428 | # 编译命令 429 | # 编译之后的目标文件是 $NGX_AUTOTEST,后面会判断这个文件是否存在来判断是否编译成功 430 | ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS $ngx_feature_inc_path \ 431 | -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_TEST_LD_OPT $ngx_feature_libs" 432 | 433 | # 执行编译过程 434 | # 编译成功后,会生成$NGX_AUTOTEST命名的文件 435 | eval "/bin/sh -c \"$ngx_test\" >> $NGX_AUTOCONF_ERR 2>&1" 436 | 437 | # 如果文件存在,则编译成功 438 | if [ -x $NGX_AUTOTEST ]; then 439 | 440 | case "$ngx_feature_run" in 441 | 442 | # 需要运行来判断是否支持特性 443 | # 测试程序能否正常执行(即程序退出后的状态码是否是0),如果正常退出,则特性测试成功,设置ngx_found为yes,并添加名为ngx_feature_name的宏定义,宏的值为1 444 | yes) 445 | # 如果程序正常退出,退出码为0,则程序执行成功,我们可以在测试代码里面手动返回非0来表示程序出错 446 | # /bin/sh is used to intercept "Killed" or "Abort trap" messages 447 | if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then 448 | echo " found" 449 | ngx_found=yes 450 | 451 | # 添加宏定义,宏的值为1 452 | if test -n "$ngx_feature_name"; then 453 | have=$ngx_have_feature . auto/have 454 | fi 455 | 456 | else 457 | echo " found but is not working" 458 | fi 459 | ;; 460 | 461 | # 需要运行程序来判断是否支持特性,如果支持,将程序标准输出的结果作为宏的值 462 | value) 463 | # /bin/sh is used to intercept "Killed" or "Abort trap" messages 464 | if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then 465 | echo " found" 466 | ngx_found=yes 467 | 468 | # 与yes不一样的是,value会将程序从标准输出里面打印出来的值,设置为ngx_feature_name宏变量的值 469 | # 在此种情况下,程序需要设置ngx_feature_name变量名 470 | cat << END >> $NGX_AUTO_CONFIG_H 471 | 472 | #ifndef $ngx_feature_name 473 | #define $ngx_feature_name `$NGX_AUTOTEST` 474 | #endif 475 | 476 | END 477 | else 478 | echo " found but is not working" 479 | fi 480 | ;; 481 | 482 | # 与yes正好相反 483 | bug) 484 | # /bin/sh is used to intercept "Killed" or "Abort trap" messages 485 | if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then 486 | echo " not found" 487 | 488 | else 489 | echo " found" 490 | ngx_found=yes 491 | 492 | if test -n "$ngx_feature_name"; then 493 | have=$ngx_have_feature . auto/have 494 | fi 495 | fi 496 | ;; 497 | 498 | # 不需要运行程序,最后定义宏变量 499 | *) 500 | echo " found" 501 | ngx_found=yes 502 | 503 | if test -n "$ngx_feature_name"; then 504 | have=$ngx_have_feature . auto/have 505 | fi 506 | ;; 507 | 508 | esac 509 | else 510 | # 编译失败 511 | echo " not found" 512 | 513 | # 编译失败,会保存信息到日志文件中 514 | echo "----------" >> $NGX_AUTOCONF_ERR 515 | # 保留编译文件的内容 516 | cat $NGX_AUTOTEST.c >> $NGX_AUTOCONF_ERR 517 | echo "----------" >> $NGX_AUTOCONF_ERR 518 | # 保留编译文件的选项 519 | echo $ngx_test >> $NGX_AUTOCONF_ERR 520 | echo "----------" >> $NGX_AUTOCONF_ERR 521 | fi 522 | 523 | # 最后删除生成的临时文件 524 | rm $NGX_AUTOTEST* 525 | 526 | 8) auto/cc/conf 527 | 528 | 在了解了工具auto/feature后,继续我们的主流程,auto/cc/conf的代码就很好理解了,这一步主要是检测编译器,并设置编译器相关的选项。它先调用auto/cc/name来得到编译器的名称,然后根据编译器选择执行不同的编译器相关的文件如gcc执行auto/cc/gcc来设置编译器相关的一些选项。 529 | 530 | 9) auto/include 531 | 532 | 这个工具用来检测是头文件是否支持。需要检测的头文件放在$ngx_include里面,如果支持,则$ngx_found变量的值为yes,并且会产生NGX_HAVE_{ngx_include}的宏定义。 533 | 534 | 10) auto/headers 535 | 536 | 生成头文件的宏定义。生成的定义放在objs/ngx_auto_headers.h里面: 537 | 538 | .. code:: c 539 | 540 | #ifndef NGX_HAVE_UNISTD_H 541 | #define NGX_HAVE_UNISTD_H 1 542 | #endif 543 | 544 | 545 | #ifndef NGX_HAVE_INTTYPES_H 546 | #define NGX_HAVE_INTTYPES_H 1 547 | #endif 548 | 549 | 550 | #ifndef NGX_HAVE_LIMITS_H 551 | #define NGX_HAVE_LIMITS_H 1 552 | #endif 553 | 554 | 555 | #ifndef NGX_HAVE_SYS_FILIO_H 556 | #define NGX_HAVE_SYS_FILIO_H 1 557 | #endif 558 | 559 | 560 | #ifndef NGX_HAVE_SYS_PARAM_H 561 | #define NGX_HAVE_SYS_PARAM_H 1 562 | #endif 563 | 564 | 11) auto/os/conf 565 | 566 | 针对不同的操作系统平台特性的检测,并针对不同的操作系统,设置不同的CORE_INCS、CORE_DEPS、CORE_SRCS变量。nginx跨平台的支持就是在这个地方体现出来的。 567 | 568 | 12) auto/unix 569 | 570 | 针对unix体系的通用配置或系统调用的检测,如poll等事件处理系统调用的检测等。 571 | 572 | 13) 回到configure里面 573 | 574 | .. code:: c 575 | 576 | # 生成模块列表 577 | . auto/modules 578 | # 配置库的依赖 579 | . auto/lib/conf 580 | 581 | 14) auto/modules 582 | 583 | 该脚本根据不同的条件,输出不同的模块列表,最后输出的模块列表的文件在objs/ngx_modules.c: 584 | 585 | .. code:: c 586 | 587 | #include 588 | #include 589 | 590 | 591 | extern ngx_module_t ngx_core_module; 592 | extern ngx_module_t ngx_errlog_module; 593 | extern ngx_module_t ngx_conf_module; 594 | extern ngx_module_t ngx_emp_server_module; 595 | 596 | ... 597 | 598 | 599 | ngx_module_t *ngx_modules[] = { 600 | &ngx_core_module, 601 | &ngx_errlog_module, 602 | &ngx_conf_module, 603 | &ngx_emp_server_module, 604 | ... 605 | NULL 606 | }; 607 | 608 | 这个文件会决定所有模块的顺序,这会直接影响到最后的功能,下一小节我们将讨论模块间的顺序。这个文件会加载我们的第三方模块,这也是我们值得关注的地方: 609 | 610 | .. code:: c 611 | 612 | if test -n "$NGX_ADDONS"; then 613 | 614 | echo configuring additional modules 615 | 616 | for ngx_addon_dir in $NGX_ADDONS 617 | do 618 | echo "adding module in $ngx_addon_dir" 619 | 620 | if test -f $ngx_addon_dir/config; then 621 | # 执行第三方模块的配置 622 | . $ngx_addon_dir/config 623 | 624 | echo " + $ngx_addon_name was configured" 625 | 626 | else 627 | echo "$0: error: no $ngx_addon_dir/config was found" 628 | exit 1 629 | fi 630 | done 631 | fi 632 | 633 | 这段代码比较简单,确实现了nginx很强大的扩展性,加载第三方模块。$ngx_addon_dir变量是在configure执行时,命令行参数--add-module加入的,它是一个目录列表,每一个目录,表示一个第三方模块。从代码中,我们可以看到,它就是针对每一个第三方模块执行其目录下的config文件。于是我们可以在config文件里面执行我们自己的检测逻辑,比如检测库依赖,添加编译选项等。 634 | 635 | 15) auto/lib/conf 636 | 637 | 该文件会针对nginx编译所需要的基础库的检测,比如rewrite模块需要的PCRE库的检测支持。 638 | 639 | 16) configure接下来定义一些宏常量,主要是是文件路径方面的: 640 | 641 | .. code:: c 642 | 643 | case ".$NGX_PREFIX" in 644 | .) 645 | NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx} 646 | have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define 647 | ;; 648 | 649 | .!) 650 | NGX_PREFIX= 651 | ;; 652 | 653 | *) 654 | have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define 655 | ;; 656 | esac 657 | 658 | if [ ".$NGX_CONF_PREFIX" != "." ]; then 659 | have=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/define 660 | fi 661 | 662 | have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/define 663 | have=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/define 664 | have=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/define 665 | have=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/define 666 | have=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/define 667 | 668 | have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define 669 | have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" 670 | . auto/define 671 | have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\"" 672 | . auto/define 673 | have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\"" 674 | . auto/define 675 | have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\"" 676 | . auto/define 677 | have=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\"" 678 | . auto/define 679 | 680 | 17) configure最后的工作,生成编译安装的makefile 681 | 682 | .. code:: c 683 | 684 | # 生成objs/makefile文件 685 | . auto/make 686 | 687 | # 生成关于库的编译选项到makefile文件 688 | . auto/lib/make 689 | # 生成与安装相关的makefile文件内容,并生成最外层的makefile文件 690 | . auto/install 691 | 692 | # STUB 693 | . auto/stubs 694 | 695 | have=NGX_USER value="\"$NGX_USER\"" . auto/define 696 | have=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define 697 | 698 | # 编译的最后阶段,汇总信息 699 | . auto/summary 700 | 701 | 702 | 模块编译顺序 703 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 704 | 705 | 上一节中,提到过,nginx模块的顺序很重要,会直接影响到程序的功能。而且,nginx和部分模块,也有着自己特定的顺序要求,比如ngx_http_write_filter_module模块一定要在filter模块的最后一步执行。想查看模块的执行顺序,可以在objs/ngx_modules.c这个文件中找到,这个文件在configure之后生成,上一节中,我们看过这个文件里面的内容。 706 | 707 | 下面是一个ngx_modules.c文件的示例: 708 | 709 | .. code:: c 710 | 711 | ngx_module_t *ngx_modules[] = { 712 | // 全局core模块 713 | &ngx_core_module, 714 | &ngx_errlog_module, 715 | &ngx_conf_module, 716 | &ngx_emp_server_module, 717 | &ngx_emp_server_core_module, 718 | 719 | // event模块 720 | &ngx_events_module, 721 | &ngx_event_core_module, 722 | &ngx_kqueue_module, 723 | 724 | // 正则模块 725 | &ngx_regex_module, 726 | 727 | // http模块 728 | &ngx_http_module, 729 | &ngx_http_core_module, 730 | &ngx_http_log_module, 731 | &ngx_http_upstream_module, 732 | 733 | // http handler模块 734 | &ngx_http_static_module, 735 | &ngx_http_autoindex_module, 736 | &ngx_http_index_module, 737 | &ngx_http_auth_basic_module, 738 | &ngx_http_access_module, 739 | &ngx_http_limit_conn_module, 740 | &ngx_http_limit_req_module, 741 | &ngx_http_geo_module, 742 | &ngx_http_map_module, 743 | &ngx_http_split_clients_module, 744 | &ngx_http_referer_module, 745 | &ngx_http_rewrite_module, 746 | &ngx_http_proxy_module, 747 | &ngx_http_fastcgi_module, 748 | &ngx_http_uwsgi_module, 749 | &ngx_http_scgi_module, 750 | &ngx_http_memcached_module, 751 | &ngx_http_empty_gif_module, 752 | &ngx_http_browser_module, 753 | &ngx_http_upstream_ip_hash_module, 754 | &ngx_http_upstream_keepalive_module, 755 | //此处是第三方handler模块 756 | 757 | // http filter模块 758 | &ngx_http_write_filter_module, 759 | &ngx_http_header_filter_module, 760 | &ngx_http_chunked_filter_module, 761 | &ngx_http_range_header_filter_module, 762 | &ngx_http_gzip_filter_module, 763 | &ngx_http_postpone_filter_module, 764 | &ngx_http_ssi_filter_module, 765 | &ngx_http_charset_filter_module, 766 | &ngx_http_userid_filter_module, 767 | &ngx_http_headers_filter_module, 768 | // 第三方filter模块 769 | &ngx_http_copy_filter_module, 770 | &ngx_http_range_body_filter_module, 771 | &ngx_http_not_modified_filter_module, 772 | NULL 773 | }; 774 | 775 | http handler模块与http filter模块的顺序很重要,这里我们主要关注一下这两类模块。 776 | 777 | http handler模块,在后面的章节里面会讲到多阶段请求的处理链。对于content phase之前的handler,同一个阶段的handler,模块是顺序执行的。比如上面的示例代码中,ngx_http_auth_basic_module与ngx_http_access_module这两个模块都是在access phase阶段,由于ngx_http_auth_basic_module在前面,所以会先执行。由于content phase只会有一个执行,所以不存在顺序问题。另外,我们加载的第三方handler模块永远是在最后执行。 778 | 779 | http filter模块,filter模块会将所有的filter handler排成一个倒序链,所以在最前面的最后执行。上面的例子中,&ngx_http_write_filter_module最后执行,ngx_http_not_modified_filter_module最先执行。注意,我们加载的第三方filter模块是在copy_filter模块之后,headers_filter模块之前执行。 780 | 781 | 782 | nginx的事件机制 783 | ------------------------ 784 | 785 | 786 | 787 | event框架及非阻塞模型 788 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 789 | 790 | 791 | 792 | 定时器实现 793 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 794 | 795 | 796 | 797 | 信号处理 798 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 799 | 800 | 801 | 802 | 惊群问题 803 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 804 | 805 | 806 | 807 | nginx的进程机制 808 | ------------------------ 809 | 810 | 811 | 812 | master进程 813 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 814 | 815 | 816 | 817 | worker进程 818 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 819 | 820 | 821 | 822 | 进程间通讯 823 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 824 | 825 | 826 | 827 | -------------------------------------------------------------------------------- /source/chapter_10.rst: -------------------------------------------------------------------------------- 1 | nginx基础设施 2 | =========================== 3 | 4 | 5 | 6 | 内存池 7 | -------------- 8 | 9 | 简介: 10 | ~~~~~~~~~~~~~~ 11 | 12 | Nginx里内存的使用大都十分有特色:申请了永久保存,抑或伴随着请求的结束而全部释放,还有写满了缓冲再从头接着写.这么做的原因也主要取决于Web Server的特殊的场景,内存的分配和请求相关,一条请求处理完毕,即可释放其相关的内存池,降低了开发中对内存资源管理的复杂度,也减少了内存碎片的存在. 13 | 14 | 所以在Nginx使用内存池时总是只申请,不释放,使用完毕后直接destroy整个内存池.我们来看下内存池相关的实现。 15 | 16 | 结构: 17 | ~~~~~~~~~~~~ 18 | 19 | .. code:: c 20 | 21 | struct ngx_pool_s { 22 | ngx_pool_data_t d; 23 | size_t max; 24 | ngx_pool_t *current; 25 | ngx_chain_t *chain; 26 | ngx_pool_large_t *large; 27 | ngx_pool_cleanup_t *cleanup; 28 | ngx_log_t *log; 29 | }; 30 | 31 | struct ngx_pool_large_s { 32 | ngx_pool_large_t *next; 33 | void *alloc; 34 | }; 35 | 36 | typedef struct { 37 | u_char *last; 38 | u_char *end; 39 | ngx_pool_t *next; 40 | ngx_uint_t failed; 41 | } ngx_pool_data_t; 42 | 43 | .. image:: https://raw.github.com/yzprofile/nginx-book/master/source/images/chapter-10-1.PNG 44 | :alt: 内存池 45 | :align: center 46 | 47 | 实现: 48 | ~~~~~~~~~~~~ 49 | 50 | 这三个数据结构构成了基本的内存池的主体.通过ngx_create_pool可以创建一个内存池,通过ngx_palloc可以从内存池中分配指定大小的内存。 51 | 52 | .. code:: c 53 | 54 | ngx_pool_t * 55 | ngx_create_pool(size_t size, ngx_log_t *log) 56 | { 57 | ngx_pool_t *p; 58 | 59 | p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); 60 | if (p == NULL) { 61 | return NULL; 62 | } 63 | 64 | p->d.last = (u_char *) p + sizeof(ngx_pool_t); 65 | p->d.end = (u_char *) p + size; 66 | p->d.next = NULL; 67 | p->d.failed = 0; 68 | 69 | size = size - sizeof(ngx_pool_t); 70 | p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; 71 | 72 | p->current = p; 73 | p->chain = NULL; 74 | p->large = NULL; 75 | p->cleanup = NULL; 76 | p->log = log; 77 | 78 | return p; 79 | } 80 | 81 | 这里首申请了一块大小为size的内存区域,其前sizeof(ngx_pool_t)字节用来存储ngx_pool_t这个结构体自身自身.所以若size小于sizeof(ngx_pool_t)将会有coredump的可能性。 82 | 83 | 我们常用来分配内存的有三个接口:ngx_palloc,ngx_pnalloc,ngx_pcalloc。 84 | 85 | 分别来看下它们的实现: 86 | 87 | .. code:: c 88 | 89 | void * 90 | ngx_palloc(ngx_pool_t *pool, size_t size) 91 | { 92 | u_char *m; 93 | ngx_pool_t *p; 94 | 95 | if (size <= pool->max) { 96 | 97 | p = pool->current; 98 | 99 | do { 100 | m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); 101 | 102 | if ((size_t) (p->d.end - m) >= size) { 103 | p->d.last = m + size; 104 | 105 | return m; 106 | } 107 | 108 | p = p->d.next; 109 | 110 | } while (p); 111 | 112 | return ngx_palloc_block(pool, size); 113 | } 114 | 115 | return ngx_palloc_large(pool, size); 116 | } 117 | 118 | 119 | void * 120 | ngx_pnalloc(ngx_pool_t *pool, size_t size) 121 | { 122 | u_char *m; 123 | ngx_pool_t *p; 124 | 125 | if (size <= pool->max) { 126 | 127 | p = pool->current; 128 | 129 | do { 130 | m = p->d.last; 131 | 132 | if ((size_t) (p->d.end - m) >= size) { 133 | p->d.last = m + size; 134 | 135 | return m; 136 | } 137 | 138 | p = p->d.next; 139 | 140 | } while (p); 141 | 142 | return ngx_palloc_block(pool, size); 143 | } 144 | 145 | return ngx_palloc_large(pool, size); 146 | } 147 | 148 | 149 | void * 150 | ngx_pcalloc(ngx_pool_t *pool, size_t size) 151 | { 152 | void *p; 153 | 154 | p = ngx_palloc(pool, size); 155 | if (p) { 156 | ngx_memzero(p, size); 157 | } 158 | 159 | return p; 160 | } 161 | 162 | ngx_pcalloc其只是ngx_palloc的一个封装,将申请到的内存全部初始化为0。 163 | 164 | ngx_palloc相对ngx_pnalloc,其会将申请的内存大小向上扩增到NGX_ALIGNMENT的倍数,以方便内存对齐,减少内存访问次数。 165 | 166 | Nginx的内存池不仅用于内存方面的管理,还可以通过`ngx_pool_cleanup_add`来添加内存池释放时的回调函数,以便用来释放自己申请的其他相关资源。 167 | 168 | .. code:: c 169 | ngx_pool_cleanup_t * 170 | ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) 171 | { 172 | ngx_pool_cleanup_t *c; 173 | 174 | c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); 175 | if (c == NULL) { 176 | return NULL; 177 | } 178 | 179 | if (size) { 180 | c->data = ngx_palloc(p, size); 181 | if (c->data == NULL) { 182 | return NULL; 183 | } 184 | 185 | } else { 186 | c->data = NULL; 187 | } 188 | 189 | c->handler = NULL; 190 | c->next = p->cleanup; 191 | 192 | p->cleanup = c; 193 | 194 | ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c); 195 | 196 | return c; 197 | } 198 | 199 | 200 | 从代码中可以看出,这些由自己添加的释放回调是以链表形式保存的,也就是说你可以添加多个回调函数来管理不同的资源。 201 | 202 | 203 | 共享内存 204 | ----------------- 205 | 206 | 207 | 208 | slab算法 209 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 210 | 211 | 212 | 213 | buffer管理 214 | ----------------- 215 | 216 | 217 | 218 | buffer重用机制 219 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 220 | 221 | 222 | 223 | buffer防拷贝机制 224 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 225 | 226 | 227 | 228 | chain管理 229 | ---------------- 230 | 231 | 232 | 233 | chain重用机制 234 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 235 | 236 | 237 | 238 | aio原理 239 | -------------- 240 | 241 | 242 | 243 | 锁实现 244 | -------------- 245 | 246 | 247 | 248 | 基本数据结构 249 | ----------------------- 250 | 251 | 252 | 253 | 时间缓存 254 | ----------------- 255 | 256 | 257 | 258 | 文件缓存 259 | ----------------- 260 | 261 | 262 | 263 | log机制 264 | --------------- 265 | -------------------------------------------------------------------------------- /source/chapter_11.rst: -------------------------------------------------------------------------------- 1 | nginx的启动阶段 (30%) 2 | ================================= 3 | 4 | 概述 (100%) 5 | ----------------- 6 | nginx启动阶段指从nginx初始化直至准备好按最新配置提供服务的过程。 7 | 8 | 在不考虑nginx单进程工作的情况下,这个过程包含三种方式: 9 | 10 | 1. 启动新的nginx 11 | 12 | 2. reload配置 13 | 14 | 3. 热替换nginx代码 15 | 16 | 三种方式有共同的流程,下面这幅图向我们展现了这个流程: 17 | 18 | 图11-1 19 | 20 | 流程的开端是解析nginx配置、初始化模块,接着是初始化文件句柄,初始化共享内存,然后是监听端口,再后来创建worker子进程和其他辅助子进程,最后是worker初始化事件机制。以上步骤结束以后,nginx各个子进程开始各司其职,比如worker进程开始accept请求并按最新配置处理请求,cache-manager进程开始管理cache文件目录等等。 21 | 22 | 除了这些共同流程,这三种方式的差异也非常明显。第一种方式包含命令行解析的过程,同时输出有一段时间是输出到控制台。reload配置有两种形式,一种是使用nginx命令行,一种是向master进程发送HUP信号,前者表面上与第一种方式无异,但实际上差别很大,后者则完全不支持控制台输出,无法直接查看nginx的启动情况。而且reload配置时,nginx需要自动停止以往生成的子进程,所以还包含复杂的进程管理操作,这一点在启动新的nginx的方式中是不存在的。热替换nginx代码虽然使用上与reload配置的后一种形式相似,但在解析nginx配置方面,与reload配置的方式差距非常大。另外,热替换nginx代码时,对以往创建的子进程管理也不像reload配置那样,需要手工触发进行。所以,我们想弄懂nginx的启动阶段,就必须理解所有这三种方式下nginx都是如何工作的。 23 | 24 | 共有流程 (100%) 25 | ----------------- 26 | 从概述中我们了解到,nginx启动分为三种方式,虽然各有不同,但也有一段相同的流程。在这一节中,我们对nginx启动阶段的共用流程进行讨论。 27 | 28 | 共有流程的代码主要集中在ngx_cycle.c、ngx_process.c、ngx_process_cycle.c和ngx_event.c这四个文件中。我们这一节只讨论nginx的框架代码,而与http相关的模块代码,我们会在后面进行分析。 29 | 30 | 共有流程开始于解析nginx配置,这个过程集中在ngx_init_cycle函数中。ngx_init_cycle是nginx的一个核心函数,共有流程中与配置相关的几个过程都在这个函数中实现,其中包括解析nginx配置、初始化CORE模块,接着是初始化文件句柄,初始化错误日志,初始化共享内存,然后是监听端口。可以说共有流程80%都是现在ngx_init_cycle函数中。 31 | 32 | 在具体介绍以前,我们先解决一个概念问题——什么叫cycle? 33 | 34 | cycle就是周期的意思,对应着一次启动过程。也就是说,不论发生了上节介绍的三种启动方式的哪一种,nginx都会创建一个新的cycle与这次启动对应。 35 | 36 | 配置解析接口 (100%) 37 | ~~~~~~~~~~~~~~~~~~~~~~ 38 | ngx_init_cycle提供的是配置解析接口。接口是一个切入点,通过少量代码提供一个完整功能的调用。配置解析接口分为两个阶段,一个是准备阶段,另一个就是真正开始调用配置解析。准备阶段指什么呢?主要是准备三点: 39 | 40 | 1. 准备内存 41 | 42 | nginx根据以往的经验(old_cycle)预测这一次的配置需要分配多少内存。比如,我们可以看这段: 43 | 44 | .. code-block:: none 45 | 46 | if (old_cycle->shared_memory.part.nelts) { 47 | n = old_cycle->shared_memory.part.nelts; 48 | for (part = old_cycle->shared_memory.part.next; part; part = part->next) 49 | { 50 | n += part->nelts; 51 | } 52 | 53 | } else { 54 | n = 1; 55 | } 56 | 57 | if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t)) 58 | != NGX_OK) 59 | { 60 | ngx_destroy_pool(pool); 61 | return NULL; 62 | } 63 | 64 | 这段代码的意思是遍历old_cycle,统计上一次系统中分配了多少块共享内存,接着就按这个数据初始化当前cycle中共享内存的规模。 65 | 66 | 2. 准备错误日志 67 | 68 | nginx启动可能出错,出错就要记录到错误日志中。而错误日志本身也是配置的一部分,所以不解析完配置,nginx就不能了解错误日志的信息。nginx通过使用上一个周期的错误日志来记录解析配置时发生的错误,而在配置解析完成以后,nginx就用新的错误日志替换旧的错误日志。具体代码摘抄如下,以说明nginx解析配置时使用old_cycle的错误日志: 69 | 70 | .. code-block:: none 71 | 72 | log = old_cycle->log; 73 | pool->log = log; 74 | cycle->log = log; 75 | 76 | 3. 准备数据结构 77 | 78 | 主要是两个数据结果,一个是ngx_cycle_t结构,一个是ngx_conf_t结构。前者用于存放所有CORE模块的配置,后者则是用于存放解析配置的上下文信息。具体代码如下: 79 | 80 | .. code-block:: none 81 | 82 | for (i = 0; ngx_modules[i]; i++) { 83 | if (ngx_modules[i]->type != NGX_CORE_MODULE) { 84 | continue; 85 | } 86 | 87 | module = ngx_modules[i]->ctx; 88 | 89 | if (module->create_conf) { 90 | rv = module->create_conf(cycle); 91 | if (rv == NULL) { 92 | ngx_destroy_pool(pool); 93 | return NULL; 94 | } 95 | cycle->conf_ctx[ngx_modules[i]->index] = rv; 96 | } 97 | } 98 | 99 | conf.ctx = cycle->conf_ctx; 100 | conf.cycle = cycle; 101 | conf.pool = pool; 102 | conf.log = log; 103 | conf.module_type = NGX_CORE_MODULE; 104 | conf.cmd_type = NGX_MAIN_CONF; 105 | 106 | 准备好了这些内容,nginx开始调用配置解析模块,其代码如下: 107 | 108 | .. code-block:: none 109 | 110 | if (ngx_conf_param(&conf) != NGX_CONF_OK) { 111 | environ = senv; 112 | ngx_destroy_cycle_pools(&conf); 113 | return NULL; 114 | } 115 | 116 | if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) { 117 | environ = senv; 118 | ngx_destroy_cycle_pools(&conf); 119 | return NULL; 120 | } 121 | 122 | 第一个if解析nginx命令行参数'-g'加入的配置。第二个if解析nginx配置文件。好的设计就体现在接口极度简化,模块之间的耦合非常低。这里只使用区区10行完成了配置的解析。在这里,我们先浅尝辄止,具体nginx如何解析配置,我们将在后面的小节做细致的介绍。 123 | 124 | 配置解析 125 | ----------------- 126 | 127 | 通用过程 (100%) 128 | ~~~~~~~~~~~~~~~~~ 129 | 130 | 配置解析模块在ngx_conf_file.c中实现。模块提供的接口函数主要是ngx_conf_parse,另外,模块提供一个单独的接口ngx_conf_param,用来解析命令行传递的配置,当然,这个接口也是对ngx_conf_parse的包装。 131 | 132 | ngx_conf_parse函数支持三种不同的解析环境: 133 | 134 | 1. parse_file:解析配置文件; 135 | 136 | 2. parse_block:解析块配置。块配置一定是由“{”和“}”包裹起来的; 137 | 138 | 3. parse_param:解析命令行配置。命令行配置中不支持块指令。 139 | 140 | 我们先来鸟瞰nginx解析配置的流程,整个过程可参见下面示意图: 141 | 142 | 图11-2 143 | 144 | 这是一个递归的过程。nginx首先解析core模块的配置。core模块提供一些块指令,这些指令引入其他类型的模块,nginx遇到这些指令,就重新迭代解析过程,解析其他模块的配置。这些模块配置中又有一些块指令引入新的模块类型或者指令类型,nginx就会再次迭代,解析这些新的配置类型。比如上图,nginx遇到“events”指令,就重新调用ngx_conf_parse()解析event模块配置,解析完以后ngx_conf_parse()返回,nginx继续解析core模块指令,直到遇到“http”指令。nginx再次调用ngx_conf_parse()解析http模块配置的http级指令,当遇到“server”指令时,nginx又一次调用ngx_conf_parse()解析http模块配置的server级指令。 145 | 146 | 了解了nginx解析配置的流程,我们来看其中的关键函数ngx_conf_parse()。 147 | 148 | ngx_conf_parse()解析配置分成两个主要阶段,一个是词法分析,一个是指令解析。 149 | 150 | 词法分析通过ngx_conf_read_token()函数完成。指令解析有两种方式,其一是使用nginx内建的指令解析机制,其二是使用第三方自定义指令解析机制。自定义指令解析可以参见下面的代码: 151 | 152 | .. code-block:: none 153 | 154 | if (cf->handler) { 155 | rv = (*cf->handler)(cf, NULL, cf->handler_conf); 156 | if (rv == NGX_CONF_OK) { 157 | continue; 158 | } 159 | 160 | if (rv == NGX_CONF_ERROR) { 161 | goto failed; 162 | } 163 | 164 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv); 165 | 166 | goto failed; 167 | } 168 | 169 | 这里注意cf->handler和cf->handler_conf两个属性,其中handler是自定义解析函数指针,handler_conf是conf指针。 170 | 171 | 下面着重介绍nginx内建的指令解析机制。本机制分为4个步骤: 172 | 173 | 1. 只有处理的模块的类型是NGX_CONF_MODULE或者是当前正在处理的模块类型,才可能被执行。nginx中有一种模块类型是NGX_CONF_MODULE,当前只有ngx_conf_module一种,只支持一条指令“include”。“include”指令的实现我们后面再进行介绍。 174 | 175 | .. code-block:: none 176 | 177 | ngx_modules[i]->type != NGX_CONF_MODULE && ngx_modules[i]->type != cf->module_type 178 | 179 | 2. 匹配指令名,判断指令用法是否正确。 180 | 181 | a) 指令的Context必须当前解析Context相符; 182 | 183 | .. code-block:: none 184 | 185 | !(cmd->type & cf->cmd_type) 186 | 187 | b) 非块指令必须以“;”结尾; 188 | 189 | .. code-block:: none 190 | 191 | !(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK 192 | 193 | c) 块指令必须后接“{”; 194 | 195 | .. code-block:: none 196 | 197 | (cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START 198 | 199 | d) 指令参数个数必须正确。注意指令参数有最大值NGX_CONF_MAX_ARGS,目前值为8。 200 | 201 | .. code-block:: none 202 | 203 | if (!(cmd->type & NGX_CONF_ANY)) { 204 | 205 | if (cmd->type & NGX_CONF_FLAG) { 206 | 207 | if (cf->args->nelts != 2) { 208 | goto invalid; 209 | } 210 | 211 | } else if (cmd->type & NGX_CONF_1MORE) { 212 | 213 | if (cf->args->nelts < 2) { 214 | goto invalid; 215 | } 216 | 217 | } else if (cmd->type & NGX_CONF_2MORE) { 218 | 219 | if (cf->args->nelts < 3) { 220 | goto invalid; 221 | } 222 | 223 | } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) { 224 | 225 | goto invalid; 226 | 227 | } else if (!(cmd->type & argument_number[cf->args->nelts - 1])) { 228 | goto invalid; 229 | } 230 | } 231 | 232 | 3. 取得指令工作的conf指针。 233 | 234 | .. code-block:: none 235 | 236 | if (cmd->type & NGX_DIRECT_CONF) { 237 | conf = ((void **) cf->ctx)[ngx_modules[i]->index]; 238 | 239 | } else if (cmd->type & NGX_MAIN_CONF) { 240 | conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); 241 | 242 | } else if (cf->ctx) { 243 | confp = *(void **) ((char *) cf->ctx + cmd->conf); 244 | 245 | if (confp) { 246 | conf = confp[ngx_modules[i]->ctx_index]; 247 | } 248 | } 249 | 250 | a) NGX_DIRECT_CONF常量单纯用来指定配置存储区的寻址方法,只用于core模块。 251 | 252 | b) NGX_MAIN_CONF常量有两重含义,其一是指定指令的使用上下文是main(其实还是指core模块),其二是指定配置存储区的寻址方法。所以,在代码中常常可以见到使用上下文是main的指令的cmd->type属性定义如下: 253 | 254 | .. code-block:: none 255 | 256 | NGX_MAIN_CONF|NGX_DIRECT_CONF|... 257 | 258 | 表示指令使用上下文是main,conf寻址方式是直接寻址。 259 | 260 | 使用NGX_MAIN_CONF还表示指定配置存储区的寻址方法的指令有4个:“events”、“http”、“mail”、“imap”。这四个指令也有共同之处——都是使用上下文是main的块指令,并且块中的指令都使用其他类型的模块(分别是event模块、http模块、mail模块和mail模块)来处理。 261 | 262 | .. code-block:: none 263 | 264 | NGX_MAIN_CONF|NGX_CONF_BLOCK|... 265 | 266 | 后面分析ngx_http_block()函数时,再具体分析为什么需要NGX_MAIN_CONF这种配置寻址方式。 267 | 268 | c) 除开core模块,其他类型的模块都会使用第三种配置寻址方式,也就是根据cmd->conf的值从cf->ctx中取出对应的配置。举http模块为例,cf->conf的可选值是NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET,分别对应“http{}”、“server{}”、“location{}”这三个http配置级别。 269 | 270 | 4. 执行指令解析回调函数 271 | 272 | .. code-block:: none 273 | 274 | rv = cmd->set(cf, cmd, conf); 275 | 276 | cmd是词法分析得到的结果,conf是上一步得到的配置存贮区地址。 277 | 278 | http的解析 279 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 280 | 281 | http是作为一个core模块被nginx通用解析过程解析的,其核心就是“http”块指令回调,它完成了http解析的整个功能,从初始化到计算配置结果。 282 | 283 | 因为这是本书第一次提到块指令,所以在这里对其做基本介绍。 284 | 285 | 块指令的流程是: 286 | 287 | 1. 创建并初始化上下文环境; 288 | 289 | 2. 调用通用解析流程解析; 290 | 291 | 3. 根据解析结果进行后续合并处理; 292 | 293 | 4. 善后工作。 294 | 295 | 下面我们以“http”指令为例来介绍这个流程: 296 | 297 | 创建并初始化上下文环境 298 | .......................... 299 | 300 | .. code-block:: none 301 | 302 | ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); 303 | 304 | *(ngx_http_conf_ctx_t **) conf = ctx; 305 | 306 | ... 307 | 308 | ctx->main_conf = ngx_pcalloc(cf->pool, 309 | sizeof(void *) * ngx_http_max_module); 310 | 311 | ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); 312 | 313 | ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); 314 | 315 | for (m = 0; ngx_modules[m]; m++) { 316 | if (ngx_modules[m]->type != NGX_HTTP_MODULE) { 317 | continue; 318 | } 319 | 320 | module = ngx_modules[m]->ctx; 321 | mi = ngx_modules[m]->ctx_index; 322 | 323 | if (module->create_main_conf) { 324 | ctx->main_conf[mi] = module->create_main_conf(cf); 325 | } 326 | 327 | if (module->create_srv_conf) { 328 | ctx->srv_conf[mi] = module->create_srv_conf(cf); 329 | } 330 | 331 | if (module->create_loc_conf) { 332 | ctx->loc_conf[mi] = module->create_loc_conf(cf); 333 | } 334 | } 335 | 336 | pcf = *cf; 337 | cf->ctx = ctx; 338 | 339 | for (m = 0; ngx_modules[m]; m++) { 340 | if (ngx_modules[m]->type != NGX_HTTP_MODULE) { 341 | continue; 342 | } 343 | 344 | module = ngx_modules[m]->ctx; 345 | 346 | if (module->preconfiguration) { 347 | if (module->preconfiguration(cf) != NGX_OK) { 348 | return NGX_CONF_ERROR; 349 | } 350 | } 351 | } 352 | 353 | http模块的上下文环境ctx(注意我们在通用解析流程中提到的ctx是同一个东西)非常复杂,它是由三个指针数组组成的:main_conf、srv_conf、loc_conf。根据上面的代码可以看到,这三个数组的元素个数等于系统中http模块的个数。想想我们平时三四十个http模块的规模,大家也应该可以理解这一块结构的庞大。nginx还为每个模块分别执行对应的create函数分配空间。我们需要注意后面的这一句“cf->ctx = ctx;”,正是这一句将解析配置的上下文切换成刚刚建立的ctx。最后一段代码通过调用各个http模块的preconfiguration回调函数完成了对应模块的预处理操作,其主要工作是创建模块用到的变量。 354 | 355 | 调用通用解析流程解析 356 | .......................... 357 | 358 | .. code-block:: none 359 | 360 | cf->module_type = NGX_HTTP_MODULE; 361 | cf->cmd_type = NGX_HTTP_MAIN_CONF; 362 | rv = ngx_conf_parse(cf, NULL); 363 | 364 | 基本上所有的块指令都类似上面的三行语句(例外是map,它用的是cf->handler),改变通用解析流程的工作状态,然后调用通用解析流程。 365 | 366 | 根据解析结果进行后续合并处理 367 | .................................. 368 | 369 | .. code-block:: none 370 | 371 | for (m = 0; ngx_modules[m]; m++) { 372 | if (module->init_main_conf) { 373 | rv = module->init_main_conf(cf, ctx->main_conf[mi]); 374 | } 375 | 376 | rv = ngx_http_merge_servers(cf, cmcf, module, mi); 377 | } 378 | 379 | for (s = 0; s < cmcf->servers.nelts; s++) { 380 | 381 | if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) { 382 | return NGX_CONF_ERROR; 383 | } 384 | 385 | if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) { 386 | return NGX_CONF_ERROR; 387 | } 388 | } 389 | 390 | if (ngx_http_init_phases(cf, cmcf) != NGX_OK) { 391 | return NGX_CONF_ERROR; 392 | } 393 | 394 | if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) { 395 | return NGX_CONF_ERROR; 396 | } 397 | 398 | for (m = 0; ngx_modules[m]; m++) { 399 | if (module->postconfiguration) { 400 | if (module->postconfiguration(cf) != NGX_OK) { 401 | return NGX_CONF_ERROR; 402 | } 403 | } 404 | } 405 | 406 | if (ngx_http_variables_init_vars(cf) != NGX_OK) { 407 | return NGX_CONF_ERROR; 408 | } 409 | 410 | if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) { 411 | return NGX_CONF_ERROR; 412 | } 413 | 414 | if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { 415 | return NGX_CONF_ERROR; 416 | } 417 | 418 | 以上是http配置处理最重要的步骤。首先,在这里调用了各个模块的postconfiguration回调函数完成了模块配置过程。更重要的是,它为nginx建立了一棵完整的配置树(叶子节点为location,包含location的完整配置)、完整的location搜索树、一张变量表、一张完成的阶段处理回调表(phase handler)、一张server对照表和一张端口监听表。下面我们将分别介绍这些配置表的生成过程。 419 | 420 | location配置树 421 | ^^^^^^^^^^^^^^^^^^^^^ 422 | 423 | 介绍这部分以前,先说明一个nginx的公理 424 | 425 | 公理11-1:所有存放参数为NGX_HTTP_SRV_CONF_OFFSET的配置,配置仅在请求匹配的虚拟主机(server)上下文中生效,而所有存放参数为NGX_HTTP_LOC_CONF_OFFSET的配置,配置仅在请求匹配的路径(location)上下文中生效。 426 | 427 | 正因为有公理11-1,所以nginx需要调用merge_XXX回调函数合并配置。具体的原因是很多配置指令可以放在不同配置层级,比如access_log既可以在http块中配置,又可以在server块中配置,还可以在location块中配置。 428 | 但是因为公理11-1,access_log指令配置只有在路径(location)上下文中生效,所以需要将在http块中配置的access_log指令的配置向路径上下文做两次传递,第一次从HTTP(http)上下文到虚拟主机(server)上下文,第二次从虚拟主机上下文到路径上下文。 429 | 430 | 可能有人会疑惑,为什么需要传递和合并呢?难道它们不在一张表里么?对,在创建并初始化上下文环境的过程中,大家已经看到,nginx为HTTP上下文创建了main_conf,为虚拟主机上下文创建了srv_conf,为路径上下文创建了loc_conf。但是,这张表只是用于解析在http块但不包含server块中定义的指令。而后面我们会看到,在server块指令中,同样建立了srv_conf和loc_conf,用于解析在server块但不含location块中定义的指令。所以nginx其实维护了很多张配置表,因此nginx必须将配置在这些表中从顶至下不断传递。 431 | 432 | 前面列出的 433 | 434 | .. code-block:: none 435 | 436 | for (m = 0; ngx_modules[m]; m++) { 437 | if (module->init_main_conf) { 438 | rv = module->init_main_conf(cf, ctx->main_conf[mi]); 439 | } 440 | 441 | rv = ngx_http_merge_servers(cf, cmcf, module, mi); 442 | } 443 | 444 | 就是初始化HTTP上下文,并且完成两步配置合并操作:从HTTP上下文合并到虚拟主机上下文,以及从虚拟主机上下文合并到路径上下文。其中,合并到路径上下问的操作是在ngx_http_merge_servers函数中进行的,见 445 | 446 | .. code-block:: none 447 | 448 | if (module->merge_loc_conf) { 449 | 450 | /* merge the server{}'s loc_conf */ 451 | 452 | /* merge the locations{}' loc_conf's */ 453 | 454 | } 455 | 456 | 大家注意观察ngx_http_merge_servers函数中的这段,先将HTTP上下文中的location配置合并到虚拟主机上下文,再将虚拟主机上下文中的location配置合并到路径上下文。 457 | 458 | location搜索树 459 | ^^^^^^^^^^^^^^^^^^^^^ 460 | 461 | 公理11-2:nginx搜索路径时,正则匹配路径和其他的路径分开搜。 462 | 463 | 公理11-3:nginx路径可以嵌套。 464 | 465 | 所以,nginx存放location的有两个指针,分别是 466 | 467 | .. code-block:: none 468 | 469 | struct ngx_http_core_loc_conf_s { 470 | 471 | ... 472 | 473 | ngx_http_location_tree_node_t *static_locations; 474 | #if (NGX_PCRE) 475 | ngx_http_core_loc_conf_t **regex_locations; 476 | #endif 477 | 478 | ... 479 | } 480 | 481 | 通过这段代码,大家还可以发现一点——nginx的正则表达式需要PCRE支持。 482 | 483 | 正则表达式的路径是个指针数组,指针类型就是ngx_http_core_loc_conf_t,所以数据结构决定算法,正则表达式路径的添加非常简单,就是在表中插入一项,这里不做介绍。 484 | 485 | 而其他路径,保存在ngx_http_location_tree_node_t指针指向的搜索树static_locations,则是变态复杂,可以看得各位大汗淋漓。 486 | 487 | 为了说明这棵树的构建,我们先了解其他路径包含哪些: 488 | 489 | 1. 普通前端匹配的路径,例如location / {} 490 | 491 | 2. 抢占式前缀匹配的路径,例如location ^~ / {} 492 | 493 | 3. 精确匹配的路径,例如location = / {} 494 | 495 | 4. 命名路径,比如location @a {} 496 | 497 | 5. 无名路径,比如if {}或者limit_except {}生成的路径 498 | 499 | 我们再来看ngx_http_core_loc_conf_t中如何体现这些路径: 500 | 501 | +---------------------+--------------------------------------------------------------+ 502 | |普通前端匹配的路径 |无 | 503 | +---------------------+--------------------------------------------------------------+ 504 | |抢占式前缀匹配的路径 |noregex = 1 | 505 | +---------------------+--------------------------------------------------------------+ 506 | |精确匹配的路径 |exact_match = 1 | 507 | +---------------------+--------------------------------------------------------------+ 508 | |命名路径 |named = 1 | 509 | +---------------------+--------------------------------------------------------------+ 510 | |无名路径 |noname = 1 | 511 | +---------------------+--------------------------------------------------------------+ 512 | |正则路径 |regex != NULL | 513 | +---------------------+--------------------------------------------------------------+ 514 | 515 | 有了这些基础知识,可以看代码了。首先是ngx_http_init_locations函数 516 | 517 | .. code-block:: none 518 | 519 | ngx_queue_sort(locations, ngx_http_cmp_locations); 520 | 521 | for (q = ngx_queue_head(locations); 522 | q != ngx_queue_sentinel(locations); 523 | q = ngx_queue_next(q)) 524 | { 525 | clcf = lq->exact ? lq->exact : lq->inclusive; 526 | 527 | if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) { 528 | return NGX_ERROR; 529 | } 530 | 531 | if (clcf->regex) { 532 | r++; 533 | 534 | if (regex == NULL) { 535 | regex = q; 536 | } 537 | 538 | continue; 539 | } 540 | 541 | if (clcf->named) { 542 | n++; 543 | 544 | if (named == NULL) { 545 | named = q; 546 | } 547 | 548 | continue; 549 | } 550 | 551 | if (clcf->noname) { 552 | break; 553 | } 554 | } 555 | 556 | if (q != ngx_queue_sentinel(locations)) { 557 | ngx_queue_split(locations, q, &tail); 558 | } 559 | 560 | if (named) { 561 | ... 562 | cscf->named_locations = clcfp; 563 | ... 564 | } 565 | 566 | if (regex) { 567 | ... 568 | pclcf->regex_locations = clcfp; 569 | ... 570 | } 571 | 572 | 大家可以看到,这个函数正是根据不同的路径类型将locations分成多段,并以不同的指针引用。首先注意开始的排序,根据ngx_http_cmp_locations比较各个location,排序以后的顺序依次是 573 | 574 | 1. 精确匹配的路径和两类前缀匹配的路径(字母序,如果某个精确匹配的路径的名字和前缀匹配的路径相同,精确匹配的路径排在前面) 575 | 576 | 2. 正则路径(出现序) 577 | 578 | 3. 命名路径(字母序) 579 | 580 | 4. 无名路径(出现序) 581 | 582 | 这样nginx可以简单的截断列表得到不同类型的路径,nginx也正是这样处理的。 583 | 584 | 另外还要注意一点,就是ngx_http_init_locations的迭代调用,这里的clcf引用了两个我们没有介绍过的字段exact和inclusive。这两个字段最初是在ngx_http_add_location函数(添加location配置时必然调用)中设置的: 585 | 586 | .. code-block:: none 587 | 588 | if (clcf->exact_match 589 | #if (NGX_PCRE) 590 | || clcf->regex 591 | #endif 592 | || clcf->named || clcf->noname) 593 | { 594 | lq->exact = clcf; 595 | lq->inclusive = NULL; 596 | 597 | } else { 598 | lq->exact = NULL; 599 | lq->inclusive = clcf; 600 | } 601 | 602 | 当然这部分的具体逻辑我们在介绍location解析是再具体说明。 603 | 604 | 接着我们看ngx_http_init_static_location_trees函数。通过刚才的ngx_http_init_locations函数,留在locations数组里面的还有哪些类型的路径呢? 605 | 606 | 还有普通前端匹配的路径、抢占式前缀匹配的路径和精确匹配的路径这三类。 607 | 608 | .. code-block:: none 609 | 610 | if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) { 611 | return NGX_ERROR; 612 | } 613 | 614 | ngx_http_create_locations_list(locations, ngx_queue_head(locations)); 615 | 616 | pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0); 617 | if (pclcf->static_locations == NULL) { 618 | return NGX_ERROR; 619 | } 620 | 621 | 请注意除开这段核心代码,这个函数也有一个自迭代过程。 622 | 623 | ngx_http_join_exact_locations函数是将名字相同的精确匹配的路径和两类前缀匹配的路径合并,合并方法 624 | 625 | .. code-block:: none 626 | 627 | lq->inclusive = lx->inclusive; 628 | 629 | ngx_queue_remove(x); 630 | 631 | 简言之,就是将前缀匹配的路径放入精确匹配的路径的inclusive指针中,然后从列表删除前缀匹配的路径。 632 | 633 | ngx_http_create_locations_list函数将和某个路径名拥有相同名称前缀的路径添加到此路径节点的list指针域下,并将这些路径从locations中摘除。其核心代码是 634 | 635 | .. code-block:: none 636 | 637 | ngx_queue_split(&lq->list, x, &tail); 638 | ngx_queue_add(locations, &tail); 639 | 640 | ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list)); 641 | 642 | ngx_http_create_locations_list(locations, x); 643 | 644 | ngx_http_create_locations_tree函数则将刚才划分的各个list继续细分,形成一个二分搜索树,每个中间节点代表一个location,每个location有如下字段: 645 | 646 | 1. exact:两类前缀匹配路径的inclusive指针域指向这两类路径的配置上下文; 647 | 2. inclusive:精确匹配路径的exact指针域指向这些路径的配置上下文; 648 | 3. auto_redirect:为各种upstream模块,比如proxy、fastcgi等等开启自动URI填充的功能; 649 | 4. len:路径前缀的长度。任何相同前缀的路径的len等于该路径名长度减去公共前缀的长度。比如路径/a和/ab,前者的len为2,后者的len也为1; 650 | 5. name:路径前缀,任何相同前缀的路径的name是其已于公共前缀的部分。仍举路径/a和/ab为例,前者的name为/a,后者的name为b; 651 | 6. left:左子树,当然是长度短或者字母序小的不同前缀的路径; 652 | 7. right:右子树,当然是长度长或者字母序大的不同前缀的路径。 653 | 654 | 通过上面三个步骤,nginx就将locations列表中各种类型的路径分类处理并由不同的指针引用。对于前缀路径和精确匹配的路径,形成一棵独特的二分前缀树。 655 | 656 | 变量表 657 | ^^^^^^^^^^^^^^^^^^^^^^^ 658 | 659 | 变量表的处理相对简单,即对照变量名表,为变量表中的每一个元素设置对应的get_handler和data字段。在前面的章节大家已经知道,变量表variables用以处理索引变量,而变量名表variables_keys用于处理可按变量名查找的变量。对于通过ngx_http_get_variable_index函数创建的索引变量,在变量表variables中的get_handler初始为空,如果没有认为设置的话,将会在这里进行初始化。 660 | 661 | 特殊变量的get_handler初始化也在这里进行: 662 | 663 | +---------------+---------------------------------------+-----------------------------+ 664 | |变量前缀 |get_handler |标志 | 665 | +---------------+---------------------------------------+-----------------------------+ 666 | |http |ngx_http_variable_unknown_header_in | | 667 | +---------------+---------------------------------------+-----------------------------+ 668 | |sent_http |ngx_http_variable_unknown_header_out | | 669 | +---------------+---------------------------------------+-----------------------------+ 670 | |upstream_http |ngx_http_upstream_header_variable |NGX_HTTP_VAR_NOCACHEABLE | 671 | +---------------+---------------------------------------+-----------------------------+ 672 | |cookie |ngx_http_variable_cookie | | 673 | +---------------+---------------------------------------+-----------------------------+ 674 | |arg |ngx_http_variable_argument |NGX_HTTP_VAR_NOCACHEABLE | 675 | +---------------+---------------------------------------+-----------------------------+ 676 | 677 | 阶段处理回调表 678 | ^^^^^^^^^^^^^^^^^^^^^^^^ 679 | 680 | 按照下表顺序将各个模块设置的phase handler依次加入cmcf->phase_engine.handlers列表,各个phase的phase handler的checker不同。checker主要用于限定某个phase的框架逻辑,包括处理返回值。 681 | 682 | +-------------------------------+---------------------------------+-------------------+ 683 | +处理阶段PHASE |checker |可自定义handler | 684 | +-------------------------------+---------------------------------+-------------------+ 685 | +NGX_HTTP_POST_READ_PHASE |ngx_http_core_generic_phase |是 | 686 | +-------------------------------+---------------------------------+-------------------+ 687 | +NGX_HTTP_SERVER_REWRITE_PHASE |ngx_http_core_rewrite_phase |是 | 688 | +-------------------------------+---------------------------------+-------------------+ 689 | +NGX_HTTP_FIND_CONFIG_PHASE |ngx_http_core_find_config_phase |否 | 690 | +-------------------------------+---------------------------------+-------------------+ 691 | +NGX_HTTP_REWRITE_PHASE |ngx_http_core_rewrite_phase |是 | 692 | +-------------------------------+---------------------------------+-------------------+ 693 | +NGX_HTTP_POST_REWRITE_PHASE |ngx_http_core_post_rewrite_phase |否 | 694 | +-------------------------------+---------------------------------+-------------------+ 695 | +NGX_HTTP_PREACCESS_PHASE |ngx_http_core_generic_phase |是 | 696 | +-------------------------------+---------------------------------+-------------------+ 697 | +NGX_HTTP_ACCESS_PHASE |ngx_http_core_access_phase |是 | 698 | +-------------------------------+---------------------------------+-------------------+ 699 | +NGX_HTTP_POST_ACCESS_PHASE |ngx_http_core_post_access_phase |否 | 700 | +-------------------------------+---------------------------------+-------------------+ 701 | +NGX_HTTP_TRY_FILES_PHASE |ngx_http_core_try_files_phase |否 | 702 | +-------------------------------+---------------------------------+-------------------+ 703 | +NGX_HTTP_CONTENT_PHASE |ngx_http_core_content_phase |是 | 704 | +-------------------------------+---------------------------------+-------------------+ 705 | 706 | 注意相同PHASE的phase handler是按模块顺序的反序加入回调表的。另外在NGX_HTTP_POST_REWRITE_PHASE中,ph->next指向NGX_HTTP_FIND_CONFIG_PHASE第一个phase handler,以实现rewrite last逻辑。 707 | 708 | server对照表 709 | ^^^^^^^^^^^^^^^^^^^^^^ 710 | 711 | 大家如果读过nginx的“Server names”这篇官方文档,会了解nginx对于server name的处理分为4中情况:精确匹配、前缀通配符匹配、后缀通配符匹配和正则匹配。那么,下面是又一个公理, 712 | 713 | 公理11-4:nginx对于不同类型的server name分别处理。 714 | 715 | 所以,所谓server对照表,其实是四张表,分别对应四种类型的server。数据结构决定算法,四张表决定了nginx必须建立这四张表的行为。鉴于前三种类型和正则匹配可以分成两大类,nginx使用两套策略生成server对照表——对正则匹配的虚拟主机名,nginx为其建立一个数组,按照主机名在配置文件的出现顺序依次写入数组;而对于其他虚拟主机名,nginx根据它们的类型为它们分别存放在三张hash表中。三张hash表的结构完全相同,但对于前缀通配或者后缀通配这两种类型的主机名,nginx对通配符进行的预处理不同。其中“.taobao.com”这种特殊的前缀通配与普通的前缀通配处理又有不同。我们现在来介绍这些不同。 716 | 717 | 处理前缀通配是将字符串按节翻转,然后去掉通配符。举个例子,“\*.example.com”会被转换成“com.example.\\0”,而特殊的前缀通配“.example.com”会被转换成“com.example\\0”。 718 | 719 | 处理后缀通配更简单,直接去掉通配符。也举个例子,“www.example.\*”会被转换成“www.example\\0”。 720 | 721 | 端口监听表 722 | ^^^^^^^^^^^^^^^^^^^^^^ 723 | 724 | 对于所有写在server配置中的listen指令,nginx开始会建立一张server和端口的对照索引表。虽然这不是本节的要点,但要说明索引表到监听表的转换过程,还是需要描述其结构。如图11-3所示,这张索引表是二级索引,第一级索引以listen指定的端口为键,第二级索引以listen指定的地址为键,索引的对象就是server上下文数据结构。而端口监视表是两张表,其结构如图11-4所示。 725 | 索引表和监听表在结构上非常类似,但是却有一个非常明显的不同。索引表中第一张表的各表项的端口是唯一的,而监听表的第一张表中的不同表项的端口却可能是相同的。之所以出现这样的差别,是因为nginx会为监听表第一张表中的每一项分别建立监听套接字,而在索引表中,如果配置显式定义了需要监听不同IP地址的相同端口,它在索引表中会放在同一个端口的二级索引中,而在监听表中必须存放为两个端口相同的不同监听表项。 726 | 727 | 说明了两张表的结构,现在可以介绍转换过程: 728 | 729 | 第一步,在ngx_http_optimize_servers()函数中,对索引表一级索引中的所有port下辖的二级索引分别进行排序。排序的规则是 730 | 731 | 1. 含wildcard属性的二级索引最终会尽可能排到尾部。这些二级索引类似于 732 | 733 | .. code-block:: none 734 | 735 | listen *:80; 736 | listen 80; 737 | 738 | 2. 含bind属性的二级索引最终会尽可能排到首部。这些二级索引是由那些设置了"bind"、"backlog"、"rcvbuf"、"sndbuf"、"accept_filter"、"deferred"、"ipv6only"和"so_keepalive"参数的listen指令生成的。 739 | 740 | 3. 其他二级索引,其相对顺序不变,排在含bind属性的二级索引之后,而在含wildcard属性的二级索引之前。 741 | 742 | 第二步,将索引表转换为监听表,这是在ngx_http_init_listening()函数中实现的。其步骤是 743 | 744 | 1. 得到是否有二级索引含有wildcard属性,只需要看看排序后的二级索引的最后一项就可以了。 745 | 746 | 2. 顺次将所有含有bind属性的二级索引以一对一的方式生成监听表的表项(第一级和第二级都只有一项)。 747 | 748 | 3. 如果第一步检测到不含wildcard属性,则顺次将后续所有二级索引以一对一的方式生成监听表的表项。 749 | 750 | 4. 如果第一步检测到含wildcard属性,则以含wildcard属性的二级索引创建监听表的一级表项,并将二级索引中从第一不含bind属性的表项开始的所有表项一同转换成为刚刚创建的监听表一级表项的下级表项。 751 | 752 | 善后工作 753 | .................... 754 | 755 | 善后工作基本的就是一件事,还原解析上下文。“http”指令是这个进行的 756 | 757 | .. code-block:: none 758 | 759 | *cf = pcf; 760 | 761 | server的管理 762 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 763 | 764 | 前面介绍的http处理逻辑在处理“server {}”时仍然适用。server相对较为特殊的是两个指令,一个是"server_name",一个是"listen"。 765 | 766 | 就在上一节,我们已经介绍了"server_name" 767 | 768 | 769 | location的管理 770 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 771 | 772 | 773 | 774 | 模块初始化 775 | -------------------- 776 | 777 | 778 | 779 | 热代码部署 780 | -------------------- 781 | 782 | 783 | 784 | reload过程解析 785 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 786 | 787 | 788 | 789 | upgrade过程解析 790 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 791 | 792 | 793 | 794 | -------------------------------------------------------------------------------- /source/chapter_13.rst: -------------------------------------------------------------------------------- 1 | nginx的upstream原理解析 2 | ========================================= 3 | 4 | 5 | 6 | 无缓冲的处理流程 7 | ----------------------------- 8 | 9 | 10 | 11 | 带缓冲的处理流程 12 | ----------------------------- 13 | 14 | 15 | 16 | subrequest访问upstream的处理流程 17 | -------------------------------------------- 18 | 19 | 20 | 21 | 负载均衡算法 22 | ----------------------- 23 | 24 | 25 | 26 | round-robin算法 27 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 28 | 29 | 30 | 31 | 基于ip的hash算法 32 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | 34 | 35 | 36 | upstream缓存机制 37 | ------------------------- 38 | 39 | 40 | 41 | 常用upstream模块分析 42 | ------------------------------- 43 | 44 | 45 | 46 | proxy模块 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | 50 | 51 | fastcgi模块 52 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /source/chapter_14.rst: -------------------------------------------------------------------------------- 1 | nginx的script处理 2 | ================================= 3 | 4 | 5 | 6 | 正则表达式 7 | -------------------- 8 | 9 | 10 | 11 | 变量 12 | ----------- 13 | 14 | 15 | 16 | 复杂变量 17 | ----------------- 18 | 19 | 20 | 21 | if的处理 22 | ---------------- 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # sphinxdoc documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jul 19 09:39:30 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | _exts = "../exts" 17 | TITLE = u"Nginx开发从入门到精通" 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ----------------------------------------------------- 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be extensions 30 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 31 | sys.path.append(os.path.abspath(_exts)) 32 | extensions = ['sphinx.ext.todo', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'number_ref', 33 | 'number_label', 'literal_include', 'block', 'image', 'basic', "latex_fix"] 34 | 35 | #extensions.append('nohighlight') 36 | extensions.append('chinese_search') 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix of source filenames. 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'Nginx开发从入门到精通' 52 | copyright = u'2012, taobao' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = '0.1' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '0.1' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | language = "zh_CN" 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = [] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | 98 | # -- Options for HTML output --------------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'book' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | html_theme_path = [_exts +"/theme"] 111 | 112 | # The name for this set of Sphinx documents. If None, it defaults to 113 | # " v documentation". 114 | html_title = TITLE 115 | 116 | # A shorter title for the navigation bar. Default is the same as html_title. 117 | #html_short_title = None 118 | 119 | # The name of an image file (relative to this directory) to place at the top 120 | # of the sidebar. 121 | #html_logo = "cover_sphinx.png" 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | #html_favicon = None 127 | 128 | # Add any paths that contain custom static files (such as style sheets) here, 129 | # relative to this directory. They are copied after the builtin static files, 130 | # so a file named "default.css" will overwrite the builtin "default.css". 131 | html_static_path = ['_static'] 132 | 133 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 134 | # using the given strftime format. 135 | #html_last_updated_fmt = '%b %d, %Y' 136 | 137 | # If true, SmartyPants will be used to convert quotes and dashes to 138 | # typographically correct entities. 139 | #html_use_smartypants = True 140 | 141 | # Custom sidebar templates, maps document names to template names. 142 | #html_sidebars = {} 143 | 144 | # Additional templates that should be rendered to pages, maps page names to 145 | # template names. 146 | #html_additional_pages = {} 147 | 148 | # If false, no module index is generated. 149 | #html_domain_indices = True 150 | 151 | # If false, no index is generated. 152 | #html_use_index = True 153 | 154 | # If true, the index is split into individual pages for each letter. 155 | #html_split_index = False 156 | 157 | # If true, links to the reST sources are added to the pages. 158 | #html_show_sourcelink = True 159 | 160 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 161 | #html_show_sphinx = True 162 | 163 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 164 | #html_show_copyright = True 165 | 166 | # If true, an OpenSearch description file will be output, and all pages will 167 | # contain a tag referring to it. The value of this option must be the 168 | # base URL from which the finished HTML is served. 169 | #html_use_opensearch = '' 170 | 171 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 172 | #html_file_suffix = None 173 | 174 | # Output file base name for HTML help builder. 175 | htmlhelp_basename = 'nginx_bookdoc' 176 | 177 | 178 | # -- Options for LaTeX output -------------------------------------------------- 179 | 180 | # The paper size ('letter' or 'a4'). 181 | #latex_paper_size = 'letter' 182 | 183 | # The font size ('10pt', '11pt' or '12pt'). 184 | #latex_font_size = '10pt' 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ('index', 'nginxbook.tex', TITLE, 190 | u'taobao server platform', 'manual'), 191 | ] 192 | 193 | # The name of an image file (relative to this directory) to place at the top of 194 | # the title page. 195 | #latex_logo = None 196 | 197 | # For "manual" documents, if this is true, then toplevel headings are parts, 198 | # not chapters. 199 | #latex_use_parts = False 200 | 201 | # If true, show page references after internal links. 202 | #latex_show_pagerefs = False 203 | 204 | # If true, show URL addresses after external links. 205 | #latex_show_urls = False 206 | 207 | # Additional stuff for the LaTeX preamble. 208 | latex_preamble = r""" 209 | % \pdfpagewidth 195mm 210 | % \pdfpageheight 271mm 211 | % \textwidth 6.0in 212 | % \textheight 8.8in 213 | % \oddsidemargin -0.1in 214 | % \evensidemargin -0.1in 215 | 216 | \textwidth 6.8in 217 | \oddsidemargin -0.2in 218 | \evensidemargin -0.3in 219 | 220 | \usepackage{pdfpages} 221 | \usepackage[BoldFont,CJKchecksingle]{xeCJK} 222 | \usepackage{float} 223 | \usepackage{ccaption} 224 | \usepackage{pifont} 225 | % \usepackage{fancybox} 226 | \usepackage{fontspec,xunicode,xltxtra} 227 | 228 | \setsansfont{DejaVu Serif} 229 | % \setromanfont{DejaVu Sans Mono} 230 | \setmainfont{DejaVu Serif} 231 | \setmonofont{DejaVu Sans Mono} 232 | 233 | % \setsansfont{WenQuanYi Micro Hei Light} 234 | % \setromanfont{WenQuanYi Micro Hei Light} 235 | % \setmainfont{WenQuanYi Micro Hei Light} 236 | % \setmonofont{WenQuanYi Micro Hei Mono Light} 237 | % STXihei 238 | \setCJKsansfont[BoldFont={SimSun},ItalicFont={SimSun}]{SimSun} 239 | \setCJKromanfont[BoldFont={SimSun},ItalicFont={SimSun}]{SimSun} 240 | \setCJKmainfont[BoldFont={SimSun},ItalicFont={SimSun}]{SimSun} 241 | \setCJKmonofont[BoldFont={SimSun},ItalicFont={SimSun}]{SimSun} 242 | 243 | % \setCJKsansfont{Microsoft YaHei} 244 | % \setCJKromanfont{Microsoft YaHei} 245 | % \setCJKmainfont{Microsoft YaHei} 246 | % \setCJKmonofont{Microsoft YaHei} 247 | 248 | % \CJKaddspaces\CJKsetecglue{\hskip 0.15em plus 0.05em minus 0.05em} 249 | 250 | \XeTeXlinebreaklocale "zh" 251 | \XeTeXlinebreakskip = 0pt plus 1pt 252 | \renewcommand{\baselinestretch}{1.3} 253 | \setcounter{tocdepth}{3} 254 | \captiontitlefont{\small\sffamily} 255 | \captiondelim{ - } 256 | \renewcommand\today{\number\year年\number\month月\number\day日} 257 | \makeatletter 258 | \renewcommand*\l@subsection{\@dottedtocline{2}{2.0em}{4.0em}} 259 | \renewcommand*\l@subsubsection{\@dottedtocline{3}{3em}{5em}} 260 | \makeatother 261 | \titleformat{\chapter}[display] 262 | {\bfseries\Huge} 263 | {\filleft \Huge 第 \hspace{2 mm} \thechapter \hspace{4 mm} 章} 264 | {4ex} 265 | {\titlerule 266 | \vspace{1ex}% 267 | \filright} 268 | [\vspace{1ex}% 269 | \titlerule] 270 | %\definecolor{VerbatimBorderColor}{rgb}{0.2,0.2,0.2} 271 | \definecolor{VerbatimColor}{rgb}{0.95,0.95,0.95} 272 | """.decode("utf-8") 273 | 274 | latex_elements = { 275 | "maketitle":ur""" 276 | \maketitle 277 | \renewcommand\contentsname{目 录} 278 | \renewcommand\partname{部分} 279 | \renewcommand{\chaptermark}[1]{\markboth{\textnormal{第 \thechapter\ 章 \hspace{4mm} #1}}{}} 280 | \renewcommand{\sectionmark}[1]{\markright{\textnormal{\thesection \hspace{2mm} #1}}{}} 281 | \renewcommand{\figurename}{\textsc{图}} 282 | \renewcommand{\tablename}{\textsc{表}} 283 | \chapter*{前言} 284 | \addcontentsline{toc}{chapter}{前言} 285 | """, 286 | "tableofcontents":ur""" 287 | \tableofcontents 288 | \fancyhead[LE,RO]{%s} 289 | """ % TITLE 290 | } 291 | 292 | # Documents to append as an appendix to all manuals. 293 | #latex_appendices = [] 294 | 295 | # If false, no module index is generated. 296 | #latex_domain_indices = True 297 | 298 | 299 | # -- Options for manual page output -------------------------------------------- 300 | 301 | # One entry per manual page. List of tuples 302 | # (source start file, name, description, authors, manual section). 303 | man_pages = [ 304 | ('index', 'Nginx开发从入门到精通', u'Nginx开发从入门到精通 Documentation', 305 | [u'taobao'], 1) 306 | ] 307 | 308 | 309 | # -- Options for Epub output --------------------------------------------------- 310 | 311 | # Bibliographic Dublin Core info. 312 | epub_title = u'Nginx开发从入门到精通' 313 | epub_author = u'taobao server platform' 314 | epub_publisher = u'taobao' 315 | epub_copyright = u'2012, taobao' 316 | 317 | # The language of the text. It defaults to the language option 318 | # or en if the language is not set. 319 | #epub_language = '' 320 | 321 | # The scheme of the identifier. Typical schemes are ISBN or URL. 322 | #epub_scheme = '' 323 | 324 | # The unique identifier of the text. This can be a ISBN number 325 | # or the project homepage. 326 | #epub_identifier = '' 327 | 328 | # A unique identification for the text. 329 | #epub_uid = '' 330 | 331 | # HTML files that should be inserted before the pages created by sphinx. 332 | # The format is a list of tuples containing the path and title. 333 | #epub_pre_files = [] 334 | 335 | # HTML files shat should be inserted after the pages created by sphinx. 336 | # The format is a list of tuples containing the path and title. 337 | #epub_post_files = [] 338 | 339 | # A list of files that should not be packed into the epub file. 340 | #epub_exclude_files = [] 341 | 342 | # The depth of the table of contents in toc.ncx. 343 | #epub_tocdepth = 3 344 | 345 | # Allow duplicate toc entries. 346 | #epub_tocdup = True 347 | -------------------------------------------------------------------------------- /source/images/chapter-10-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-10-1.PNG -------------------------------------------------------------------------------- /source/images/chapter-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-12-1.png -------------------------------------------------------------------------------- /source/images/chapter-2-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-2-1.PNG -------------------------------------------------------------------------------- /source/images/chapter-2-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-2-2.PNG -------------------------------------------------------------------------------- /source/images/chapter-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-4-1.png -------------------------------------------------------------------------------- /source/images/chapter-4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-4-2.png -------------------------------------------------------------------------------- /source/images/chapter-5-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-5-1.PNG -------------------------------------------------------------------------------- /source/images/chapter-5-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-5-2.PNG -------------------------------------------------------------------------------- /source/images/chapter-9-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/chapter-9-1.jpg -------------------------------------------------------------------------------- /source/images/code-style-1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-1.JPG -------------------------------------------------------------------------------- /source/images/code-style-10.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-10.JPG -------------------------------------------------------------------------------- /source/images/code-style-11.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-11.JPG -------------------------------------------------------------------------------- /source/images/code-style-12.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-12.JPG -------------------------------------------------------------------------------- /source/images/code-style-13.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-13.JPG -------------------------------------------------------------------------------- /source/images/code-style-2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-2.JPG -------------------------------------------------------------------------------- /source/images/code-style-3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-3.JPG -------------------------------------------------------------------------------- /source/images/code-style-4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-4.JPG -------------------------------------------------------------------------------- /source/images/code-style-5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-5.JPG -------------------------------------------------------------------------------- /source/images/code-style-6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-6.JPG -------------------------------------------------------------------------------- /source/images/code-style-7.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-7.JPG -------------------------------------------------------------------------------- /source/images/code-style-8.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-8.JPG -------------------------------------------------------------------------------- /source/images/code-style-9.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taobao/nginx-book/3c991efab397edb7cefdb11ceea126812c256aab/source/images/code-style-9.JPG -------------------------------------------------------------------------------- /source/index.rst: -------------------------------------------------------------------------------- 1 | .. nginx_book documentation master file, created by 2 | sphinx-quickstart on Wed Feb 29 17:58:19 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Nginx开发从入门到精通 7 | ============================= 8 | 9 | 10 | 缘起 11 | ++++++ 12 | 13 | nginx由于出色的性能,在世界范围内受到了越来越多人的关注,在淘宝内部它更是被广泛的使用,众多的开发以及运维同学都迫切的想要了解nginx模块的开发以及它的内部原理,但是国内却没有一本关于这方面的书,源于此我们决定自己来写一本。本书的作者为淘宝核心系统服务器平台组的成员,本书写作的思路是从模块开发逐渐过渡到nginx原理剖析。书籍的内容会定期在这里更新,欢迎大家提出宝贵意见,不管是本书的内容问题,还是字词错误,都欢迎大家提交issue(章节标题的左侧有评注按钮),我们会及时的跟进。 14 | 15 | .. topic:: 更新历史 16 | 17 | .. csv-table:: 18 | :header: 日期, 描述 19 | :widths: 20, 160 20 | :quote: $ 21 | :delim: | 22 | 23 | 2012/03/01|创建目录大纲 24 | 2012/03/28|增加了样章 25 | 2012/05/25|更新样章 26 | 2012/06/08|增加第5章 27 | 2012/06/11|增加第4章 28 | 2012/06/26|增加第6章(event module) 29 | 2012/06/27|更新第5章部分内容 30 | 2012/07/04|更新第6章event module部分内容 31 | 2012/07/12|增加第12章(请求头读取,subrequest解析) 32 | 2012/08/14|增加第2章(nginx基础架构及基础概念) 33 | 2012/08/14|增加第2章(ngx_str_t数据结构介绍) 34 | 2012/08/17|增加第7章(模块开发高级篇之变量) 35 | 2012/08/25|增加第11章(nginx的启动阶段部分内容) 36 | 2012/09/26|增加第2章(ngx_array_t,ngx_hash_t及ngx_pool_t介绍) 37 | 2012/10/08|增加第11章(配置解析综述) 38 | 2012/10/12|增加第2章(ngx_hash_wildcard_t,ngx_hash_combined_t及ngx_hash_keys_arrays_t介绍) 39 | 2012/10/21|增加第2章(ngx_chain_t,ngx_list_t及ngx_buf_t介绍) 40 | 2012/11/09|增加第12章(请求体的读取和丢弃解析) 41 | 2012/11/24|更新第2章(ngx_buf_t的部分字段以及其他一些书写错误和表达) 42 | 2012/12/18|更新第11章(解析http块) 43 | 2012/12/10|增加第3章的内容 44 | 2012/12/28|补充和完善了第3章的内容 45 | 2013/01/25|增加了第2章(nginx的配置系统) 46 | 2013/02/18|增加了第2章(nginx的模块化体系结构, nginx的请求处理) 47 | 2013/03/05|增加了第12章部分内容(多阶段请求处理) 48 | 2013/03/08|完成第11章第1节(配置解析综述、ngx_http_block) 49 | 2013/04/16|完成第9章第1节(源码目录结构、configure原理) 50 | 2013/09/30|完成第12章部分内容(多阶段执行链各个阶段解析) 51 | 2013/10/11|完成第12章部分内容(filter解析) 52 | 2013/10/11|完成第12章部分内容(ssl解析) 53 | 54 | 版权申明 55 | ++++++++++++ 56 | 57 | 本书的著作权归作者淘宝核心系统服务器平台组成员所有。你可以: 58 | 59 | - 下载、保存以及打印本书 60 | - 网络链接、转载本书的部分或者全部内容,但是必须在明显处提供读者访问本书发布网站的链接 61 | - 在你的程序中任意使用本书所附的程序代码,但是由本书的程序所引起的任何问题,作者不承担任何责任 62 | 63 | 你不可以: 64 | 65 | - 以任何形式出售本书的电子版或者打印版 66 | - 擅自印刷、出版本书 67 | - 以纸媒出版为目的,改写、改编以及摘抄本书的内容 68 | 69 | 目录 70 | ++++++ 71 | 72 | .. toctree:: 73 | :maxdepth: 4 74 | 75 | module_development.rst 76 | source_analysis.rst 77 | appendix_a.rst 78 | appendix_b.rst 79 | appendix_c.rst 80 | 81 | 团队成员 82 | ++++++++++++ 83 | 84 | 叔度 (http://blog.zhuzhaoyuan.com) 85 | 86 | 雕梁 (http://www.pagefault.info) 87 | 88 | 文景 (http://yaoweibin.cn) 89 | 90 | 李子 (http://blog.lifeibo.com) 91 | 92 | 卫越 (http://blog.sina.com.cn/u/1929617884) 93 | 94 | 袁茁 (http://yzprofile.me) 95 | 96 | 小熊 (http://dinic.iteye.com) 97 | 98 | 吉兆 (http://jizhao.blog.chinaunix.net) 99 | 100 | 静龙 (http://blog.csdn.net/fengmo_q) 101 | 102 | 竹权 (http://weibo.com/u/2199139545) 103 | 104 | 公远 (http://100continue.iteye.com/) 105 | 106 | 布可 (http://weibo.com/sifeierss) 107 | 108 | -------------------------------------------------------------------------------- /source/module_development.rst: -------------------------------------------------------------------------------- 1 | .. 模块开发篇 2 | 3 | 上篇:nginx模块开发篇 4 | ====================== 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | chapter_01.rst 10 | chapter_02.rst 11 | chapter_03.rst 12 | chapter_04.rst 13 | chapter_05.rst 14 | chapter_06.rst 15 | chapter_07.rst 16 | 17 | 18 | -------------------------------------------------------------------------------- /source/source_analysis.rst: -------------------------------------------------------------------------------- 1 | 下篇:nginx原理解析篇 2 | ====================== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | chapter_08.rst 8 | chapter_09.rst 9 | chapter_10.rst 10 | chapter_11.rst 11 | chapter_12.rst 12 | chapter_13.rst 13 | chapter_14.rst 14 | 15 | --------------------------------------------------------------------------------