Open solomonxie opened 6 years ago
以下方法:
都知道Markdown中插入公式不易,需要平台自己提供LaTex渲染引擎。 如果是自己的平台倒还好说,很简单引入一个js文件即可。 但是Github的README和issues等都默认是不支持LaTex的,也不能插入外部js文件。 所以在Github中显示正常的公式唯一的方法就是引用公式的图片。 目前最流行的是Google Chart API和Forkosh服务器,但是forkosh服务器连接不畅,除非自己架服务器搭建forkosh平台,否则就只能选google了。
用法如下:
![](http://chart.googleapis.com/chart?cht=tx&chl= 在此插入Latex公式)
注意:公式虽然是LaTex语法,但是特殊符号必须经过url编码才行,否则Github不显示。 例:
https://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFF00&chl=%0D%0A4x_0%5CDelta%28x%29%2B3%5CDelta%28x%29%2B2%5CDelta%28x%5E2%29%3E0%0D%0A
相比Google来说,Codecogs采用了一样的url引用方式,一样的语法,且也必须是url编码。但是Codecogs提供了一个网页编辑器,可以直接在上面可视化编辑公式,然后直接拷贝图片链接即可,所见即所得,要快捷的多了。 当然如果在意服务器稳定性还是想用Google的话,那还可以把图片链接前面直接改成Google api的地址即可。
生成好图片后,直接右键点击生成的图片获取链接即可。
Wordpress实在是太好用,就连鄙视PHP的人也不能说Wordpress不好。作为纯傻瓜式Web应用框架来说,Wordpress实在是让我这个小白从零开始做了一套完整的企业前后台网站。不能不在这里记录一些经验以供使用。
一般是通过ssh连接远程服务器,在命令行里操作。
#安装环境
sudo apt-get install php5-fpm nginx mysql-server php5-mysql
# 启动Nginx服务
service nginx start
# 下载wordpress
wget http://wordpress.org/latest.zip
# 解压wordpress
apt-get install unzip
unzip latest.zip
# 设置权限
chmod 777 -R /var/www/html/网站文件夹名
# 登录mysql并设置密码
mysql -u root -p
##--创建数据库--
create database 数据库名;
Github Pages对于建立静态网站来说真的是超级方便,概念方便,配置方便。 只要你不超出HTML+Javascript+CSS的范围,一切都好说。 如果为了漂亮,可以使用Bootstrap等各种技术加强页面显示,只要是静态的,一切都好说。
参考:单个GitHub帐号下添加多个GitHub Pages的相关问题
Github Pages有两种建站方案,一种叫个人主页,一种叫项目主页:
solomonxie.github.io
。这是最简单的方法,网页放在master分支就可以显示。但是这种方法会有比较多限制:
user.github.io
这种形式,user必须与自己的用户名完全相同。user.github.io
这么简单的形式了。而是user.github.io/repo
这种形式。
同时,你必须要把网页放在这个repo的gh-pages
分支里,才能显示出来。注意一般即使上传好了网页,也不会及时显示出来,有时可能会等几个小时Github才会显示最新的页面。
一般solomonxie.github.io
这种域名虽然已经很简单了,但还是挂着github的名字且有点长,始终摆脱不了供应商的影子。如果做为个人网站的话,这一点的确会影响些形象和印象的独立性。
所以有必要把这个域名映射到自己申请的外部域名上去。
以下为域名映射的操作步骤:
CNAME
,内容极其简单,只有一行,即你申请的域名,如:solomonxiexie.com
。然后Github会根据这个域名设置,一直替你监听这个域名的访问,然后自动帮你做所有的映射工作。提前声明:Jekyll并不简单,必须要正确的看待它。把它和PHP,JSP和Django等放在一起讨论会减少很多失落感。它的学习曲线几乎相当于Wordpress,工作流程和结构也几乎一样。
Jekyll与Wordpress最大不同的就是,没有数据库。但是体验上来说也算不上什么大差别。 彻底摒弃数据库,这算是一种Jekyll式的新思路。 因为你需要的只是定期更新一些Markdown格式的文章,然后让它显示成网页,并放在一起成为网站而已。没必要大动干戈的设计数据库什么的。
简单的说,Jekyll是一个基于Ruby语言的静态博客网站制作工具,它可以把Markdown转换成HTML网页。
不过对于一个HTML网页来说,它得有标题、样式、日期什么的,甚至一些根据文章的不同而动态改变的内容等。这些就不仅是把Markdown转换成HTML而已了。很多内容需要你在Markdown文件里面就写明指定。
另注:Jekyll虽然和Github Pages搭配免费,但其实是完全独立的产品。可以在任何地方使用,像Wordpress一样。
安装Jekyll需要用Ruby的包管理器gem下载,像Python的pip一样:
$ gem install jekyll
但是如果本机的gem版本不够新,是装不了jekyll的,所以就依照官网从ruby从头开始安装:
sudo apt-get install ruby ruby-dev build-essential
echo '# Install Ruby Gems to ~/gems' >> ~/.bashrc
echo 'export GEM_HOME=$HOME/gems' >> ~/.bashrc
echo 'export PATH=$HOME/gems/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
gem install jekyll bundler
用Jekyll创建一个网站(自动生成名为test_blog的文件夹和一个完整的Demo网站):
$ jekyll new test_blog
目录里面内容如下: 这里面是完整的一个网站,可以直接运行浏览。 然后你就可以根据自己的主页、其它网页什么的,在这个基础上修改了。
Bundler: ruby: No such file or directory
我的Mac机上从来没做过任何Ruby项目,也不懂gem使用方法。全部原始配置后,使用gem install jekyll
没问题,但是在jekyll new ..
时,就发送这个错误:
Bundler: ruby: No such file or directory -- /usr/local/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/exe/bundle (LoadError)
发生错误后,项目文件夹是生成了,但是不完整,也不能执行。
问题在于本机的gem和bundler都不是很新,需要更新一下。 参考:https://github.com/rubygems/rubygems/issues/2180#issuecomment-369423426
更新如下:
# Install latest version of Rubygems
gem update --system
# Install latest version of bundler system-wide
gem install bundler
更新时间会很长,等全部安装好后,就可以正常的使用jekyll了。
渲染网站
$ cd test_blog
$ jekyll serve
或实时渲染网站:
$ jekyll server --watch
如果加上了--watch
参数,jekyll就会实时监控你的文件,只要那个文件有变动了,比如新增了markdown文件,或修改了layout模板,它都会即时渲染生成网站,总保持更新。
运行渲染的命令后,jekyll就会把你的Markdown根据指定的模板渲染为静态网站, 同时还会把网站映射到本机的一个端口,你可以打开命令行里提示的url链接察看网站效果。
如果jekyll部署在了公网上的服务器上,那么很轻松就可以公开给所有人访问了。 语法如下:
$ jekyll serve --detach --host 0.0.0.0
# 或
$ jekyll serve --force_polling -H 0.0.0.0 -P 4000
然后就会显示如下:
也就是说公网运行jekyll的话,程序就转到后台了,需要退出的话需要手动关闭进程。
然后根据网站设计时候指定的端口,相应的在服务器防火墙上开放这个端口,比如4000。
然后用http://服务器IP:4000
这样的就能访问了。
如果要不带端口号访问,就在_config.yml
中把端口号设计为80。(但是经常有冲突,需要解决)
使用Jekyll,主要难就在一开始,需要设计网页样式,设置全站的规则等等。 但是一旦这些基本设置都完成了,以后更新就只需要专注的写Markdown文件即可。
Jekyll new
命令新建一个网站结构后,文件夹里面有很多文件。这些文件结构都是什么作用,是我们必须要学习的。
_config.yml
文件:这是你第一个需要修改的东西。全网站的通用设置都保存在这里,比如网站主题,名称,介绍,域名,Github用户名等。.yml
是像.ini
一样的配置文件类型。_site
文件夹:这个存放你的完整静态网站的文件夹,但是这是不需要你去碰的文件夹,它是Jekyll根据你的设置和模板之类的内容,自动生成的静态网站。_layout
文件夹:是存放各种网页模板的地方,主页什么样子,列表页什么样子,博客内容页面什么样子,这些分别的页面模板都是放在这里的。_includes
文件夹:存放所有重复使用的、比较固定的页面模块。比如每个网页都一样的页头、页脚,导航栏,侧边栏等等。这里面的HTML文件,都不是完整的HTML网页,都只是模块,可能只是一个<div>
标签。_posts
文件夹:存放所有的Markdown格式文件。你所有的Markdown博客内容,都放在这里。文件命名也是有规定的,比如必须是data-filename.markdown
这种。注意:
_site
文件夹需要你在.gitignore
中加入屏蔽,因为这个动态生成的东西,完全不需要在git里面进行追踪。而且放在Github Pages上的话,Github引擎也不会在你的目录里面生成这个文件夹,而是在后台直接给你生成页面。之所以会有它,主要是本地设计时候用。Front-Matter 文件头信息
文件头信息在这里被叫做front-matter
,或yml-header
,它是写在每个Markdown文件头部的设置信息。主要是指明这篇文章标题、日期、使用的模板、样式、标签、分类等,这样Jekyll就可以根据这些设置把markdown文件转换成你想要的最终HTML网页了。
头信息的常用参数如下:
layout
: 指明模板名称,即指定使用_layout
文件夹中哪个HTML网页做为模板。title
: 这篇文章的标题。data
: 这篇文章的日期。categories
: 这篇文章的分类。Jekyll的最终目标和整个存在意义都是生成静态网站。
但是,
默认情况下,所谓生成出来的静态HTML页面,你也不能直接打开看到效果!必须要运行jekyll serve
才行,或者把它放到Github的Repo里。
那还叫什么静态网站?!
真正的静态网站不是生成HTML就行了,而是让你双击打开HTML就能在浏览器看到效果。
避开这个有点矛盾的逻辑不说了,我们有比较方便的外部工具来做到这点。
那就是最常用的wget
下载命令。
wget
可以把网页或整个网站下载下来,并且能自动转换各种文件里的路径。
命令如下:
$ wget -r --convert-links <URL>
所以当你运行Jekyll serve
成功编译生成_site
目录后,就可以用wget下载本地的这个网站了。
目前体验极其糟糕:
实际上,Jekyll安装主题是非常反人类的——它一点也不比自己写模版简单,学习成本真是高。 安装主题不是把人家做好的template直接复制过来就能用了。 每个模版设置的变量设置名、依赖的gem包都不一样,还经常需要在本地安装所有依赖包,安装jekyll插件等。如果不懂Ruby gem的话,还真是不简单。
到了这里,一般人真的会问自己应不应该再继续下去。因为明明简单的东西,不知道是不是还值得了。
我相信所有坚持学习jekyll的人,都有自己非学不可的理由吧。
Jekyll是用Ruby语言构建的,且每个主题都会有超多的Ruby依赖包。在这里需要先理解一些基本概念才能进行下去。
Ruby
:是语言。这就不说了Gem
:全称RubyGems
,是Ruby的包管理器。相当于Python的pip。每一个包都叫a gem
,在Python里叫package
.Bundler
:是管理gem管理器的管理器……相当于Python的pipenv,管理每个项目的gem包依赖。简单说,gem主要管理整个系统的Ruby包,下载安装卸载之类。而Bundler只负责管理每个项目的Ruby包依赖。
先讲讲一般通用的模板安装方法:
$ jekyll serve
$ bundle install
bundle
命令就会会根据文件夹中的Gemfile
文件下载安装所有模版所需的依赖环境。$ jekyll serve
直接运行网站了。_posts
文件夹里了。Front Matter
里,把theme
改成这个模版的名称,layout
改成这个模版要求的layout等。jekyll serve
,开始运行服务。$ bundle exec jekyll serve
至此,一般简单的模版都可以搞定了。如果超出任何以上提及内容,我们就要到"特殊安装方法"一节来分析了。
一般安装方法解决不了的,基本上算是特殊安装方法了。
经过我尝试了下载和安装几十个下载的主题后,发现如果碰见一个连bundle install
命令都不用,直接jekyll serve
就打开服务的,那简直是像中大奖一样的。
每个主题的安装都不太一样,且遇到的错误都完全不同。通用性极其小。
要想真正安装好一个主题,必须掌握基本的Debug能力,命令行信息的理解能力,如果精通Ruby那么就再好不过了。
基本上我不打算在这里浪费时间把这些情况列出来讨论,只是想把坑分享出来,提醒你不要跳。
如果不是100%确定真的想用这个主题,就不要浪费时间去调试和修改gem环境了,不值得。
我的经验是:安装越麻烦的,模版本身其实反而更丑更差劲👎。
Github社区里的Jekyll模版流行使用nodejs的npm的gulp来编译scss这些东西。
所以如果你没注意到主题的说明文档里提到的gulp
, npm
之类,就会发现用jekyll serve
无法进行网站生成。
这种情况下,只需要:安装Node.js
-> 使用npm
-> 安装gulp
-> 命令行使用gulp
编译网站中的css文件。不过大多数情况下你的机器已经自带了nodejs和npm(Windows除外),所以:
$ cd <主题的目录>
# 安装此项目的依赖环境
$ npm install
# 编译此项目中的相关文件
$ gulp run
小1分钟后,就会看到gulp开启了一个端口,并自动打开了你的chrome浏览器,打开了这个项目的网页(你会看到无法显示出正常效果,因为这里gulp这是用来编译css的,它运行不了jekyll项目)。 然后ctrl+c退出,再打开jekyll命令编译网站就行了-_-!
吐槽一下:请回想,为了安装个jekyll和主题,此时你已经经历了一个真的不算短的学习历程了: GitHub Pages -> Jekyll框架 ->
.yml
文件语法和配置 -> Liquid动态语言 -> Ruby -> gem -> bundle -> HTML+CSS+JS -> nodejs -> npm -> gulp…… 这其中哪一步都值得说上一段时间。 然后回想起当初,其实你只是想在GIthub Pages里建个静态网页而已。
根据这个Stackoverflow的回答:
sw.js
是Service worker的意思,是自动生成的。
基本上不会造成什么影响,但是主要出现这个错误,jekyll就没法同步更新。
根据我的实际体验,这不是主题的问题,而是jekyll的问题:对每个主题都报有这个错误。
基本步骤:
git clone .....
_config.yml
git push origin gh-pages
gh-pages
注意:这个服务的文档不能用Markdown,只能用reStructuredText.
虽然比较像Jekyll,是快速建站类服务,但是是完全不同的逻辑和构建思维。 这个方式是快速建立一本书、一份完整文档、一个规范攻略的方式,与github和Readthedocs结合非常棒。
大概运行逻辑是:
Sphinx是基于Python构建的,所以一般在各个平台直接用pip安装最方便:
# 建议在虚拟环境里安装
$ pip install sphinx --user
# 创建项目文件夹
$ mkdir test
$ cd test
# 生成项目结构
$ python -m sphinx-quickstart
在执行sphinx-quickstart
后,会进入十几二十个问题选择环节,可以一直按回车选择默认选项,但是前几项一定要认真自己选择,要不然很麻烦:
$ python -m sphinx.quickstart
Welcome to the Sphinx 1.7.6 quickstart utility.
Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).
Selected root path: .
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]: y
Inside the root directory, two more directories will be created; "_templates"
for custom HTML templates and "_static" for custom stylesheets and other static
files. You can enter another prefix (such as ".") to replace the underscore.
> Name prefix for templates and static dir [_]: .
The project name will occur in several places in the built documentation.
> Project name: testtest
> Author name(s): Solomon
> Project release []: 0.01
If the documents are to be written in a language other than English,
you can select a language here by its language code. Sphinx will then
translate text that it generates into that language.
For a list of supported codes, see
http://sphinx-doc.org/config.html#confval-language.
> Project language [en]: en
The file name suffix for source files. Commonly, this is either ".txt"
or ".rst". Only files with this suffix are considered documents.
> Source file suffix [.rst]: .rst
One document is special in that it is considered the top node of the
"contents tree", that is, it is the root of the hierarchical structure
of the documents. Normally, this is "index", but if your "index"
document is a custom template, you can also set this to another filename.
> Name of your master document (without suffix) [index]: index
Sphinx can also add configuration for epub output:
> Do you want to use the epub builder (y/n) [n]: n
Indicate which of the following Sphinx extensions should be enabled:
> autodoc: automatically insert docstrings from modules (y/n) [n]: n
> doctest: automatically test code snippets in doctest blocks (y/n) [n]:
> intersphinx: link between Sphinx documentation of different projects (y/n) [n]:
> todo: write "todo" entries that can be shown or hidden on build (y/n) [n]:
> coverage: checks for documentation coverage (y/n) [n]:
> imgmath: include math, rendered as PNG or SVG images (y/n) [n]:
> mathjax: include math, rendered in the browser by MathJax (y/n) [n]:
> ifconfig: conditional inclusion of content based on config values (y/n) [n]:
> viewcode: include links to the source code of documented Python objects (y/n) [n]:
> githubpages: create .nojekyll file to publish the document on GitHub pages (y/n) [n]:
A Makefile and a Windows command file can be generated for you so that you
only have to run e.g. `make html' instead of invoking sphinx-build
directly.
> Create Makefile? (y/n) [y]:
> Create Windows command file? (y/n) [y]:
副标题:Liquid Template Engine
学习制作Jekyll模版,其实主要是学习Liquid语法。
参考:Liquid官方文档。
就像PHP、ASP、Python等一切网络动态语言一样,Liquid
也相当于一种独立的动态语言,没什么大差别,基本功能都有。
说白了就是动态生成HTML,可以输出变量,操作数组,调用外部数据,设置IF ELSE判断,FOR循环等,这些都能达到。
开始讲语法前,大概说明一下运行流程:
site对象是全站都能调用的变量,全部都在_config.yml
文件中定义。
常用变量如下:
site.pages
: 所有`_site.posts
: 所有文章site.categories
: 所有的 categoriessite.tags
: 所有的 tagssite.related_posts
: 从LSI Keywords
提取出来最相关最新的10篇文章site.collections
: 所有集合(与posts逻辑很大不同,一般用来放members等特殊数据)site.documents
: 所有 collections 里面的文档site.data
: _data
目录下的数据site.[abc]
: 自定义的变量(手动在_config.yml
中指定)page.content
: 页面的内容page.title
: 标题 (手动在Front Matters中指定)page.excerpt
: 摘要page.url
: 链接page.date
: 时间page.id
: 唯一标示page.categories
: 分类(手动在Front Matters中指定)page.tags
: 标签(手动在Front Matters中指定)page.path
: 源代码位置page.next
: 下一篇文章page.previous
: 上一篇文章从site.categories
列表中循环得到的是一个一个的category
,其中包括这些属性:
cat[0]
: 返回cat
的名称cat[0].size
: 返回这个分类里的文章数量cat[1]
: 返回一个post_list
列表,包含这个category里所有的post对象。cat[1].size
: 返回这个post_list
列表中的对象数量。从site.tags
列表中循环得到的是一个一个的tag
,其中包括这些属性:
tag[0]
: 返回tag
的名称tag[0].size
: 返回这个tags里的文章数量tag[1]
: 返回一个post_list
列表,包含这个tags里所有的post对象。tag[1].size
: 返回这个post_list
列表中的对象数量。paginator.per_page
: 每一页的数量paginator.posts
: 这一页的数量paginator.total_posts
: 所有文章的数量paginator.total_pages
: 总的页数paginator.page
: 当前页数paginator.previous_page
: 上一页的页数paginator.previous_page_path
: 上一页的路径paginator.next_page
: 下一页的页数paginator.next_page_path
: 下一页的路径读取全站所有的posts:
{% for post in site.posts %}
<h2> {{ post.title }} </h2>
<h2> {{ post.url }} </h2>
<h2> {{ post.category }} </h2>
<h2> {{ post.excerpt }} </h2> ◀︎ 文章摘要,自动生成的
{% endfor %}
只读取_posts/
文件夹中某个category中的posts,
例如_posts/tech
文件夹中放的是一些category为tech
的文章,那么读取方式是:
{% for post in site.categories.tech %}
<h2> {{ post.title }} </h2>
{% endfor %}
注意,在_posts
中nested文件夹里的文章,也必须在Front matter中指定分类,要不然读不出来。
读取全站所有的分类:
{% for cat in site.categories %}
<h2> {{ cat[0] }} </h2>
{% endfor %}
读取所有分类下的所有文章:
{% for cat in site.categories %}
{% for post in cat[1] %}
<h2> {{ post.title }} </h2>
{% endfor %}
{% endfor %}
读取某个分类下所有的文章:
{% for post in site.categories.blog %}
<h2> {{ post.title }} </h2>
{% endfor %}
读取全站所有的tags:
{% for tag in site.tags %}
<h1> {{ tag[0] }} </h1>
{% endfor %}
读取所有tags下的所有文章:
{% for tag in site.tags %}
{% for post in cat[1] %}
<h2> {{ post.title }} </h2>
{% endfor %}
{% endfor %}
读取某个tag下所有的文章:
{% for post in site.tags.math %}
<h2> {{ post.title }} </h2>
{% endfor %}
{% for post in site.categories.Tech %} ◀︎ 先读取某分类下所有的文章
{% assign tags = tags |concat: post.tags |uniq %} ◀︎ 把每篇文章的tags存到列表里,并删除重复项
{% endfor %}
{% for tag in tags %}
<h2> {{ tag }} </h2> ◀︎ 循环输出这个category中的所有tags
{% for post in site.categories.calculus %}
{% if post.tags contains tag %} ◀︎ 循环判断如果文章属于此tag则显示出来
<h4> {{ post.title }} </h4>
{% endif %}
{% endfor %}
{% endfor %}
需要在MD文档里指定layout
才能调用。比如文档里指定了layout: post
,那么系统就找到_layouts/post.html
这个文件;如果文档指定了layout: blog
,那么系统就会找到_layout/blog.html
这个文件。
在layout里面读取post数据很简单,不需要for循环,不需要if判断,直接用post
这个对象就行。因为系统已经把文章的数据传过来了。
假如我们在_posts/xx.md
文章的头信息中,定义了这些数据:
---
layout: post
title: I'm a post
category: 'blog'
tags: ['jekyll', 'wordpress', 'blog']
---
(注:tags列表等,在yaml中可以用- tag
或['tag']
表示,一样的 )
以下就是这个post.html
文件读取post数据的方式:
<h2> {{ post.title }} </h2>
<h2> {{ post.category }} </h2>
<h2> {{ post.content }} </h2>
{% for tag in post.tags %}
<h2> {{ tag }} </h2>
{% endfor %}
官方的group_by
做到了复杂查询的功能,比如查找某个category下的全部文章并按tag分组显示。
相对自己写for/if
实现来说,虽然官方提供了这个功能,但是你仔细阅读文档就会发现,这个group_by必须配合单独的静态的额外的文档才能实现。
也就是说,你必须手动写个mygroup.doc
文件,一个一个指定每篇文章的分组、分类、顺序等。
那实在太麻烦了。
不像Readthedocs那么复杂,Gitbook所需的文件和设置极其少,而且原生支持Markdown和Github仓库自动同步。
一般本地无需安装,只要在Github中存入相应的Markdown文件就能自动生成了。 不过为了随时测试和预览,有必要在本地也弄一套。
安装(不要安装旧版的gitbook
,而应该是gitbook-cli
):
$ sudo npm install gitbook-cli -g
常见安装问题:
#如果提示`npm`版本过低,则升级npm:
$ npm i npm@latest -g
#如果提示网络问题,则用-d参数
$ npm install gitbook -g -d
#如果网络还是有问题,则用代理联网
$ npm config set proxy http://127.0.0.1:1080
$ npm config set https-proxy http://127.0.0.1:1080
gitbook程序和npm的问题太多,我在Mac本机、Ubuntu国外服务器上测试都装不好。所以只能使出最终杀器:
docker
.
gitbook有官方的Docker Hub账号,但是好像没有官方的gitbook程序image。推荐第三方排名较高的billryan/gitbook
。
在本机已有docker的情况下,如此运行:
# 对当前文件夹进行gitbook初始化(容器在命令执行完后会自动消失 因为--rm选项)
$ docker run --rm -v "$PWD:/gitbook" -p 4000:4000 billryan/gitbook gitbook init
# 对当前文件夹的gitbook编译并提供预览(容器在命令执行完后会自动消失 因为--rm选项)
$ docker run --rm -v "$PWD:/gitbook" -p 4000:4000 billryan/gitbook gitbook serve
# build
$ docker run --rm -v "$PWD:/gitbook" -p 4000:4000 billryan/gitbook gitbook build
# 最高将docker变成alias快捷键,相当于本机的gitbook命令了
$ alias gitbook='docker run --rm -v "$PWD":/gitbook -p 4000:4000 billryan/gitbook gitbook'
以上docker会把当前文件夹映射为虚拟系统里的/gitbook
文件夹,并且将4000端口映射到本机的4000.而且由于--rm选项,docker不会存储container。这样一来就和本机安装的gitbook没什么两样了。
本地项目创建及初始化:
# 初始化本地一个项目
$ cd book
$ gitbook init
# 编译并预览书籍(生成好后,会显示一个本地链接,可以在浏览器里打开预览)
$ gitbook serve
如果提示类似这样的错误:Error: ENOENT: no such file or directory, stat '/gitbook/_book/gitbook/gitbook-plugin-lunr/lunr.min.js'
那么就需要安装插件。首先要在项目根目录下新建一个book.json
文件,内容如下:
{
"plugins": [
"fontsettings",
"sharing",
"lunr",
"search",
"highlight",
"livereload"
]
}
然后运行命令gitbook install
安装这些插件。之后就应该没问题了。
基本文件结构:
Gitbook至少需要两个文件:
README.md
:相当于书籍简介SUMMARY.md
:这个非常重要,定义了整个目录结构和相应的文件链接SUMMARY.md
目录文件格式:
Gitbook的目录最多支持3级。
背景: OS:Raspberry Pi Rasbian Context:第一次安装Apache2,没配置任何文件,就不能reload
Reload Apache时后发生错误:
$ sudo /etc/init.d/apache2 reload
[....] Reloading apache2 configuration (via systemctl): apache2.service
Job for apache2.service failed.
See "systemctl status apache2.service" and "journalctl -xe" for details.
尝试过的方案:
sudo apt-get purge apache2 && sudo apt-get install apache2
--- 没用sudo /etc/init.d/apache2 restart
---- 可以重启但是还不能reload解决方案:
# 找到占用80端口的服务 (发现是Nginx服务器在用)
$ sudo netstat -pant |grep 80
tcp6 0 0 :::80 :::* LISTEN 769/nginx -g daemon
# 找办法关闭这个服务
$ sudo systemctl stop nginx
# 或
$ sudo service <服务名> stop
# 重启并重新加载Apache2
$ sudo /etc/init.d/apache2 restart
$ sudo /etc/init.d/apache2 reload
[ ok ] Reloading apache2 configuration (via systemctl): apache2.service.
因为有的服务器都是给自己测试用的,不喜欢用公开的80和443端口。
参考:Configure apache to listen on port other than 80
编辑这个文件/etc/apache2/ports.conf
:
sudo apt-get update
# 下载Owncloud所需依赖
sudo apt-get install -y apache2 mariadb-server libapache2-mod-php7.0 \
openssl php-imagick php7.0-common php7.0-curl php7.0-gd \
php7.0-imap php7.0-intl php7.0-json php7.0-ldap php7.0-mbstring \
php7.0-mcrypt php7.0-mysql php7.0-pgsql php-smbclient php-ssh2 \
php7.0-sqlite3 php7.0-xml php7.0-zip
# 下载并解压最新版本OwnCloud(自行前往官网找到地址)
# Download the latest version form the webpage:
wget https://download.owncloud.org/community/owncloud-10.0.8.tar.bz2
# Download the related MD5 checksum file:
wget https://download.owncloud.org/community/owncloud-10.0.8.tar.bz2.md5
# 验证文件有效性
# Verify the MD5 sum:
md5sum -c owncloud-10.0.8.tar.bz2.md5 < owncloud-10.0.8.tar.bz2
# Download the PGP signature:
wget https://download.owncloud.org/community/owncloud-10.0.8.tar.bz2.asc
wget https://owncloud.org/owncloud.asc
# Verify the PGP signature:
gpg --import owncloud.asc
gpg --verify owncloud-10.0.8.tar.bz2.asc owncloud-10.0.8.tar.bz2
# 解压文件并移动到Apache服务器目录中
# Unarchive the Owncloud package
tar -xjf owncloud-10.0.8.tar.bz2
# Copy the folder to Apache Webserver root path
sudo cp -r owncloud /var/www
# Give permission for ownCloud to read/write the folder
sudo chown -R www-data:www-data /var/www/owncloud
# 配置Apache服务器
sudo touch /etc/apache2/sites-available/owncloud.conf
sudo cat > /etc/apache2/sites-available/owncloud.conf <<EOF
Alias /owncloud "/var/www/owncloud/"
<Directory /var/www/owncloud/>
Options +FollowSymlinks
AllowOverride All
<IfModule mod_dav.c>
Dav off
</IfModule>
SetEnv HOME /var/www/owncloud
SetEnv HTTP_HOME /var/www/owncloud
</Directory>
EOF
# Then create a symlink to /etc/apache2/sites-enabled:
sudo ln -s /etc/apache2/sites-available/owncloud.conf /etc/apache2/sites-enabled/owncloud.conf
# 开启Apache服务器相关模块和服务
sudo a2enmod rewrite
sudo a2enmod headers
sudo a2enmod env
sudo a2enmod dir
sudo a2enmod mime
# Enable SSL (for https)
sudo a2enmod ssl
sudo a2ensite default-ssl
sudo service apache2 reload
# 重启Apache
sudo service apache2 restart
# 打开浏览器前往 `http://<本机IP>/owncloud` 配置服务
echo "Please proceed to your web browser and open http://<IP>/owncloud to finish the setup."
echo "========== (Enable Local External Storage, etc., Hard disk, flash disk) ==========="
echo "Please add 'files_external_allow_create_new_local' => 'true', to /var/www/owncloud/config/config.php"
echo "Refresh web browser to see the change."
# Enable Local External Storage
#sudo vim /var/www/owncloud/config/config.php
# Add this phrase into the array to enable local external storage
#'files_external_allow_create_new_local' => 'true',
echo "========== (OwnCloud Installing Finished) ==========="
安装好后,访问http://你的IP/owncloud
,就能看到这个画面,要求你设置初始用户名密码:
重制密码方法:
$ sudo -u www-data php occ user:resetpassword <用户名>
创建SSL证书:
# 创建一个文件夹 用来存储证书
$ sudo mkdir /etc/apache2/ssl
# 创建一个365天有效期的证书(然后进入交互输入相关信息)
# 包括apache.key和apache.crt文件(扩展是什么无所谓)
$ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/apache2/ssl/apache.key -out /etc/apache2/ssl/apache.crt
创建好后,就可以在apache的配置里面引用这个SSL证书了。
但是因为这个证书是自己创建而不是第三方公司的,所以不会被任何浏览器信任,每次都会出现NET::ERR_CERT_AUTHORITY_INVALID
错误:
这个是没办法解决的,只能点击proceed
继续前往。然后看到地址栏里一直会有这个提示,非常醒目:
如果这个可以忍的话,那么就继续配置。
修改/etc/apache2/ports.conf
文件,设置https等端口:
修改/etc/apache2/httpd.conf
文件,设置虚拟主机:
修改/etc/apache2/sites-available/owncloud.conf
文件,配置owncloud服务的关联:
重启apache2,
sudo /etc/init.d/apache2 reload
High Concurrency + High Available
Nginx
处理高并发非常厉害。但是如果承载Nginx本身的服务器突然出了问题,就很没法再继续了。
Keepalived
则用于建立集群,让Nginx可以同时在多个服务器上运行,一个出问题就切换到另一个,保证服务的高可用。
如果想了解基于Python的网络应用,除了知道Python语言以外,还必须要知道整条架构的技术栈。 Python的WebApp架构不是一气呵成的,而是在每个环节都要挑选一个合适的模块最终组合在一起才能发挥作用。
要理解Python的WSGI之前,先要了解什么是CGI
,了解CGI在网络应用中的作用。
CGI (Common Gateway Interface)
,常听说但不太明白。实际上一句话就可以概括:
"CGI offers a standard protocol for web servers to execute programs that execute like console applications (also called command-line interface programs) running on a server that generates web pages dynamically. " -Wiki
也就是说,CGI的作用就是:把HTTP Server接收到的请求,转换为本机上其它应用程序(如Shell命令行、Python、Java等)可以理解的命令。也就是说,让网页的互动像直接与本地程序互动一样简单。
简而言之: CGI就是一个语言转换器。将网络语言转换为具体的各种程序语言。
基于这个特性,CGI只是一个泛指。而针对不同的语言、程序,自然也就有不同的CGI来转换。 常见的CGI如下:
WSGI (Web Server Gateway Interface)
,是一种Python官方(PEP 333)制定的一个网络交互规范:客户端 -> HTTP服务器 -> WSGI -> Application(Wordpress/Flask/Django)。反过来发送给客户端也是一样。
每个语言对于网络服务器的处理可能不一样:如同Ruby的Rack。Python使用WSGI
,即一种通用的接口标准或者接口协议,实现了Python Web程序与服务器之间交互的通用性。
WSGI
是将HTTP Server的参数进行Python化
,封装为request对象传递给apllication命名的func对象并接受其传出的response参数,由于其处理了参数封装和结果解析,才有python世界web框架的泛滥,在python下,写web框架就像喝水一样简单。(参考:https://www.zhihu.com/question/19998865/answer/27033737)
有了这个东西,Flask或者Django等等的python web开发框架,就可以轻松地部署在不同的web server上了,不需要做任何特殊配置(也需要一些小小的配置调整)
"如全称代表的那样,WSGI不是服务器,不是API,不是Python模块,更不是什么框架,而是一种服务器和客户端交互的接口规范."
在客户端向服务器发出请求时,HTTP Server服务器接收请求,进行socket、tcp、udp等基层操作,全部验证通过后,再把请求内容发给WSGI。然后WSGI作为一个Python关口,把请求数据全部转换为Python程序能理解的信息,再把这些信息发送给具体的某个app应用,比如Flask,或Django。这时候,Flask等框架接收到的,就是一般的Python化的数据了,而不是网络规范的数据。也就是说,Flask等框架,完全无需考虑Protocols网络协议等东西,专心做好自己的网站就好了。
~在WSGI规范下,web组件被分成三类:client, server, and middleware.~
WSGI:全称是Web Server Gateway Interface,是一种规范,描述web server如何与web application通信的规范。要实现WSGI协议,必须同实现web server和web application,当前运行在WSGI协议之上的web框架有Bottle, Flask, Django。

参考:Web 应用 & 框架 (The Hitchhiker's Guid to Python)
可选方案有:
在这些组件里面,一般从每一个大类里面挑一个具体的库,配合使用。具体怎么搭配就看自己需求了。
常用的组合有:
实际上光Flask也是能上线运行的,但是它作为HTTP服务器的能力太有限了,所以需要搭配别的模块使用。具体来讲就是:
线程
同时运行、处理请求。从这里可以看到,其实这个组合不是必须的,拆掉哪个都能用。只是一个健全的网络App,这样处理是必要的。试想,同一时间只能接受一个人访问的网站,也算网站吗?再想,有客户会在URL网址里输入端口号去访问网站吗?
Each of these commands will run an ad hoc http static server in your current (or specified) directory, available at http://localhost:8000. Use this power wisely.
$ python -m SimpleHTTPServer 8000
$ python -m http.server 8000
Dependencies:
# Ubuntu
$ pip install Twisted
$ twistd -n web -p 8000 --path .
Or:
$ python -c 'from twisted.web.server import Site; from twisted.web.static import File; from twisted.internet import reactor; reactor.listenTCP(8000, Site(File("."))); reactor.run()'
Depends on Twisted.
Dependencies:
# Ubuntu
$ sudo apt-get install ruby
$ ruby -run -e httpd . -p 8000
$ ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => Dir.pwd).start'
Credit: Barking Iguana
Credit: nobu
$ gem install adsf # install dependency
$ adsf -p 8000
Credit: twome
No directory listings.
$ gem install sinatra # install dependency
$ ruby -rsinatra -e'set :public_folder, "."; set :port, 8000'
No directory listings.
$ cpan HTTP::Server::Brick # install dependency
$ perl -MHTTP::Server::Brick -e '$s=HTTP::Server::Brick->new(port=>8000); $s->mount("/"=>{path=>"."}); $s->start'
Credit: Anonymous Monk
$ cpan Plack # install dependency
$ plackup -MPlack::App::Directory -e 'Plack::App::Directory->new(root=>".");' -p 8000
Credit: miyagawa
$ cpan Mojolicious::Lite # install dependency
$ perl -MMojolicious::Lite -MCwd -e 'app->static->paths->[0]=getcwd; app->start' daemon -l http://*:8000
No directory listings.
$ npm install -g http-server # install dependency
$ http-server -p 8000
Note: This server does funky things with relative paths. For example, if you have a file /tests/index.html
, it will load index.html
if you go to /test
, but will treat relative paths as if they were coming from /
.
$ npm install -g node-static # install dependency
$ static -p 8000
No directory listings.
$ php -S 127.0.0.1:8000
Credit: /u/prawnsalad and MattLicense
No directory listings.
$ erl -s inets -eval 'inets:start(httpd,[{server_name,"NAME"},{document_root, "."},{server_root, "."},{port, 8000},{mime_types,[{"html","text/html"},{"htm","text/html"},{"js","text/javascript"},{"css","text/css"},{"gif","image/gif"},{"jpg","image/jpeg"},{"jpeg","image/jpeg"},{"png","image/png"}]}]).'
Credit: nivertech (with the addition of some basic mime types)
No directory listings.
$ busybox httpd -f -p 8000
Credit: lvm
$ webfsd -F -p 8000
Depends on webfs.
C:\> "C:\Program Files (x86)\IIS Express\iisexpress.exe" /path:C:\MyWeb /port:8000
Depends on IIS Express.
Credit: /u/fjantomen
No directory listings. /path
must be an absolute path.
一个Webapp Framework框架,不同于Web Server (或HTTP Server)。
Webapp框架的核心作用只有:
CGI
:实现路由,即把网络请求翻译成具体的编程语言Template
:展现业务视图,即在静态HTML中加入动态语言变成动态HTMLWebapp框架的轻重: 轻量级的框架只实现基本的路由和视图,而重量级的框架则在此基础上实现了各种各样丰富的组件和插件。 轻量级的使用简单、自由灵活、轻松扩展,重量级的具备完备的网络应用组件如数据库操作、登录、产品展示等拿来直接用,但是不够灵活。
常用Python框架:
如何理解Webapp框架在网络模型中的意义?
参考:如何理解Nginx、uWSGI和Flask之间的关系?
实际上客户访问到具体的网络服务需要走过三大组件:Client -> HTTP Server -> CGI -> Webapp Framework。 其中HTTP Server只处理基础的TCP/UDP等网络请求,得知请求的是那个具体的应用程序后,把具体内容转发给CGI翻译器,然后CGI把网络请求翻译成具体的编程语言,比如Python,然后发给对应的Webapp Framework,具有编程语言特性的Framework把请求处理好了,再回复给CGI,再回复给HTTP server,再给客户。
Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要用第三方的扩展来实现。
Flask对WSGI (路由)的实现,是采用 Werkzeug,而模板引擎(业务视图) 则使用 Jinja2。这两个是Flask框架的核心。
Flask核心就是作为一个Webapp框架的两个基础部分:
除此之外,Flask其它一切的都是由第三方插件实现: 包括:
Flask-SQLalchemy:操作数据库; Flask-migrate:管理迁移数据库; Flask-Mail:邮件; Flask-WTF:表单; Flask-Bable:提供国际化和本地化支持,翻译; Flask-script:插入脚本; Flask-Login:认证用户状态; Flask-OpenID:认证; Flask-RESTful:开发REST API的工具; Flask-Bootstrap:集成前端Twitter Bootstrap框架; Flask-Moment:本地化日期和时间; Flask-Admin:简单而可扩展的管理接口的框架
参考Flask扩展列表:http://flask.pocoo.org/extensions/
安装很简单:
$ pip install flask
建议在Virtualenv虚拟环境下安装,因为Flask需要一系列的依赖,最好给Flask生成一个专用的生产环境,并生产requirement.txt
依赖列表:
# 生产虚拟环境
$ virtualenv ./flask_env
# 启动虚拟环境
$ ./flask_env/bin/active
# 生产依赖列表
$ pip freeze > requirements.txt
一个最简单的Flask程序,只需要三步:
路由规则
,即不同路径可以实现不同的操作。app.run()
,hello-world.py
:
from flask import Flask
# 生产一个Flask APP 实例,并指向当前文件(模块)
app = Flask(__name__)
# 指定"/"根目录的路由规则
@app.route('/')
def index():
return 'Hello World'
# 开始运行app
app.run()
Flask中,路由的实现是用装饰器:@app.route("/")
这种方式来做到的。
页面显示指定内容:
@app.route('/')
def index():
return '<h1> 欢迎访问此页面 </h1>'
页面返回指定内容、状态码、headers等:
@app.route('/')
def index():
body = '<h1> 欢迎访问此页面 </h1>'
status_code = 200
headers = {'Cache-Control':'no-cache', 'Connection':'keep-alive'}
return body, status_code, headers
指定接收的请求方式
:
@app.route('/', method='GET')
# ...
给路由传参数:
@app.route('/orders/<order_id>')
def hello_itheima(order_id):
return 'The ID of order is: %d' % order_id
限制路由参数的数据类型:
@app.route('/orders/<int:order_id>')
# ...
其中int:order_id
是指定参数order_id
必须能转换成int整数,否则这个请求就会自动被拒绝。
比如用户请求http://xyz.com/orders/HAHAHAH
,这就不成功。而http://xyz.com/orders/210
这样的就成功。
中断请求:
# ...
from flask import abort
@app.route('/wrong-page')
def hello():
# 中断请求,并返回404状态码
abort(404)
处理错误请求:
# ...
@app.errorhandler(404)
def handel_404_error():
return '<h1> The page does not exist </h1>'
其中,如果路由中调用了abort(404)
中断函数,或是其它产生404错误的方法,这个app.errorhandler
都会被自动调用。
返回“渲染”过的模版(即把动态的模版渲染成静态的HTML):
#...
from flask import render_template
@app.route('/')
def index():
return render_template('index.html')
如果所有路径的路由都定义在一个文件里,会很难维护,所以必定要把各个路径的路由分拆到不同的文件里。
这种分拆很简单:
正常情况下,在index.py
主模块中,我们还是一样正常的定义路由:
#...
@app.route('/')
def index():
pass
然后我们可以把其它路由的处理函数分别放在别的文件里,比如:
register.py
中定义“普通函数”:
def reg():
pass
以及login.py
中定义一个普通函数:
def signin():
pass
然后回到主模块index.py
中,我们可以导入这些函数,并显式的将这些函数注册到路由上:
from flask import Flask
from register import reg
from login import signin
app = Flask(__name__)
app.route('/register')(reg)
app.route('/login')(signin)
@app.route('/')
def index():
pass
然后我们可以用app.url_map
获得当前定义过的所有路由:
print( app.url_map )
我们手动分割路由处理函数,然后分别导入,这样虽然也简单,但是不不好的地方是,主模块外定义的各个处理函数,本身很难看出来处理的是什么路由逻辑。
为此,Flask提供了另一种路由分割
的方法:即Blueprint
类。
而这个Blueprint类生成的对象,是在子模块中代替了之前我们所使用的Flask
类生成的app对象。
也就是说:主模块还是用app,但是子模块中用蓝图blueprint。
假设我们现在有一个子模块order.py
定义"/order"
路径的路由,那么文件中定义如下:
from flask import Blueprint
# 生成蓝图实例:参数中一个是蓝图名称,一个是主模块名称
app_orders = Bluepint('blueprint_orders', __name__)
# 将路由添加到蓝图里
@app_orders.route('/orders')
def get_orders():
pass
然后回到主模块index.py
中,把蓝图注册到主路由上:
#...
from orders import app_orders
app = Flask(__name__)
app.register_blueprint( app_orders )
#...
Flask提供一个完整请求至回应的事件流,其中包括:
@app.before_first_request
: 接受第一次请求之前执行@app.before_request
: 接受请求前,每次请求之前都执行。@app.route()
: 处理请求@app.after_request
: 请求之后执行,但前提是请求中没有出现异常
-@app.teardown_request
: 关闭请求时,即每次请求是否异常都会被执行以下是钩子的用法:
#...
@app.before_first_request
def handle_before_first_request():
pass
@app.route('/')
def index():
pass
@app....
def ...
request.current_app
是Flask特有的一种request请求处理
方式,不同于flask.request
对象的处理方式,它是能区分多个请求的。
在我们常用的flask.request
对象中,会有一个很严重的问题:即它是一个全局变量
。也就是说,如果服务器在处理并发请求时使用的是在同一个进程里的多线程,那么不同用户的请求也许会使用同一个flask.request
对象!这时候request中的请求信息就会出现混淆!
所以Flask引入了request.current_app
这个对象,即它能够根据上下文来区分不同人的请求。
这是怎么做到的呢?其实很简单,它只是把request变为一个局部变量
而已。这样一来,每次的request请求,都是各自独立的局部对象。
除了我们自己定义返回的信息外,Flask提供了一个内置的make_response
对象,便于处理返回信息。
返回全文信息:
from flask import make_response
@app.route('/')
def index():
resp = make_response('<h1> 欢迎访问此页面 </h1>')
resp.status = 200
resp.headers['Cache-Control'] = 'no-cache'
return resp
设置cookies:
from flask import make_response
@app.route('/')
def index():
resp = make_response('<h1> 此页面会设置你的cookies :) </h1>')
resp.set_cookie('uuid', '1230sfjdlsj3uu')
resp.set_cookie('name', 'Jason', max_age=360)
return resp
其中,max_age
是cookie的存活时间,以s秒为单位。不设置的话,默认是临时cookies
,即浏览器关闭后立马失效。
删除cookie:resp.delete_cookie('uuid')
。注意,这里的删除并不是立马删除浏览器中用户的cookie,而只是把max_age
设置为0,即浏览器关闭后立马失效。
Flask中有一个request对象,接收了一切对当前模块的请求数据。
使用的话,直接在@app.route
后面的函数中用def index(request)
接收来自装饰器的请求对象即可使用。
request参数类型:
常用的各种类型操作如下:
from flask import request
app = Flask(__name__)
@app.route('/', method='POST')
def index(request):
# Get uploaded file
afile = request.files.get('pic')
with open('./pic.jpg', 'w') as f:
f.write( afile.read() )
# Get a form
form = request.from # Dict类型
name = form.get('name')
age = form.get('age')
# Get cookies
uuid = request.cookies.get('uuid')
name = request.cookies.get('name')
在登录页设置session,并在index页根据session判断是否登录:
from flask import session
#...
app.config['SECRET_KEY'] = 'asdlkjflaj23jrsdjf任意字符串作为密钥kaljdsl;fkja;j'
@app.route('/login')
def login():
# 设置sessions
session['uuid'] = '123abadsf'
return '<p> 登录成功 </p>'
@app.route('/')
def index():
# 获取sessions
uuid = session['uuid']
# 判别session是否存在
if uuid:
return '<p> 之前已登录过 </p>'
else:
return '<p> 未登录,请重新登录 </p>'
其中,Flask默认情况下,会利用app.config['SECRET_KEY']
的值作为一个密钥,来加密你手动设置的session,然后把这个信息转换为名叫session
的cookie存在浏览器中。
这个是Flask特别的一点。
但是把敏感的session数据保存到谁都能访问的cookie中,即使加密了也不是很安全。 所以一般我们还是会手动把session数据存到服务器后台的数据库中,而不是存到cookie中。 每次验证再与数据库进行对比。
动态网页必须要的就是Form表单。Flask中有自带的form表单处理方法。不过我们也可以用第三方插件Flask-WTF
实现。
这里我们先只讲自带的处理方式。
假设我们有一个表单模版form.html
:
<form method="post">
用户名:<input type="text" name="username">
密码: <input type="password" name="password">
确认密码: <input type="password" name="password2">
<input type="submit" value="提交"><br>
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
</form>
当用户点击submit提交时,
整个form信息就会用POST方式提交到Flask的路由文件abc.py
中。
我们进行处理如下:
from flask import Flask
from flask import render_template
from flask import request
app.secret_key = 'abc123'
@app.route('/', methods=['GET', 'POST'])
def hello():
if request.method == 'POST':
# 获取参数, 并效验参数完整性, 如果有问题就进行flash
username = request.form.get('username')
password = request.form.get('password')
password2 = request.form.get('password2')
if not all([username, password, password2]):
flash('params error')
elif password != password2:
flash('password error')
else:
print username
return 'success'
return render_template('Congratulations.html')
一般我们在开发调试过程中,可以用Flask自带的WSGI和一个小HTTP Server来实现整个App正常运转。 但是生产环境中,这两个自带的组件就效率很低了。所以我们需要用效率更高的独立的CGI和独立的HTTP Server服务器来部署真正的生产环境
一般常见的选项有:
所以,我们一般采用Nginx + Gunicorn + Flask
来部署网络应用。
Gunicorn的使用:
# 安装
$ pip install gunicorn
# 进入Flask app的主目录
cd ./myFlask
# 用gunicorn服务器启动Flask app
$ gunicorn -w 4 -b 127.0.0.1:8080 main:app
这个时候,flask就在gunicorn的HTTP服务器上运行了,可以通过127.0.0.1:8080访问到app。
副标题:Jinja2 Template Engine
Flask和Django,以及其它很多Python框架,都默认使用Jinja2
来作为模版引擎。
在Python中,什么是模版?就是在一个静态HTML加入一些类似变量的标签,然后引擎在渲染这个HTML时候会动态的把变量填入内容,生成一个最终的HTML。
什么是模版引擎?其实就是一种能解析类似Python语言
的标记语言的解释器。
比如我们在HTML模版中输入一个<p> {{ post.title }} </p>
,显然这不是真正的HTML语法。但是当Jinja2解释器读取到{{ ...}}
后知道里面是一个变量,那么就把这个变量替换为真正的值,最后翻译出来就变成了<p> 大标题 </p>
这样的HTML内容。
Jinja2是一个模版语言,只是类似Python,比较符合Python语法,但不完全相同!
所有的模版引擎,实际上都差不多,不管是基于VBS语言的ASP模版,还是基于PHP语言的PHP模版,都不是与原本语言一摸一样,而只是做到尽量一样而已。
注意:Jinja2
模版语言,是不区分缩进的,和纯python不同。实际上所有模版语言都不区分缩紧。
常用标记:
{# 这是注释 #}
{{ post.title }}
,或字典元素{{your_dict['key']}}
,或列表{{your_list[0]}}
{% 开始 %} HTML标签 {% 结束 %}
示例:
{% if user %}
{{ user }}
{% else %}
hello!
{% for index in indexs %}
{{ index }}
{% endfor %}
一个filter过滤器的本质就是一个function函数。使用格式为:变量名 | 函数
。
它做到的就是,把变量传给函数,然后再把函数返回值作为这个代码块的值。
如:
<!-- 带参数的 -->
{{变量 | 函数名(*args)}}
<!-- 不带参数可以省略括号 -->
{{变量 | 函数名}}
链式调用(管道式): 和命令行的pipline管道一样,可以一次调用多个函数(过滤器),如:
{{ "hello world" | reverse | upper }}
文本块调用(将中间的所有文字都作为变量内容传入到过滤器中):
{% filter upper %}
一大堆文字
{% endfilter %}
字符串操作:
safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>
capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>
lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
upper:把值转成大写
<p>{{ 'hello' | upper }}</p>
title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>
reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>
truncate: 字符串截断
<p>{{ 'hello every one' | truncate(9)}}</p>
列表操作:
first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
Jinja2是允许自定义函数的,这样在模版中可以重复利用这个自定义函数。Jinja2称之为Macro
宏。
定义方法:
{% macro 函数名(参数) %}
具体的HTML内容
{% endmacro %}
<!-- 使用 -->
{{ 函数名(参数) }}
<!-- 或作为过滤器 -->
{{ 变量 | 函数名(参数) }}
关于Jinja2自定义函数的context
上下文和环境变量的问题:
Jinja2的自定义函数“宏”,本身是没法像filter过滤器函数一样使用上下文和环境变量的。
不过可以加上@contextfilter
装饰器达到同样的效果。
导入另一个文件的自定义函数“宏”:
假设在macro.html
文件中我们定义了一个函数func()
。
那么现在我们可以在另一个文件reference.html
中像python导入模块一样导入它:
{% import 'macro.html' as module %}
{{ module.func() }}
Include是我们常用的操作,即定义一个框架模版(父模版),然后一个一个指定性的把子模版引入进来。
框架模版frame.html
如下:
{% include 'header.html' %}
{% include 'body.html' %}
{% include 'footer.html' %}
我们可以在一个父模版中定义一个block
代码块,然后在另一个子模版中“继承”这个父模版,并重写这个block
代码块。
不过一般模版中的父模版,都只是留出一个block
空位,里面不写东西,特意等子模版来实现。
假设现在有一个父模版parent.html
:
{% block HEADER %}
页头部分的HTML内容。
{% endblock HEADER %}
{% block BODY %}
正文部分的HTML内容。
{% endblock BODY %}
{% block FOOTER %}
页脚部分的HTML内容。
{% endblock FOOTER %}
其中定义了三个block,页头、正文和页脚。
然后我们就可以定义一个模版child.html
来继承父模版,并且只重写BODY部分:
{% extends 'parent.html' %}
{% block BODY %}
由子页面重写改写的的HTML内容,替换父页面的BODY。。。
{% endblock BODY %}
扩展完成后,我们最终得到的结果是:
{% block HEADER %}
页头部分的HTML内容。
{% endblock HEADER %}
{% block BODY %}
由子页面重写改写的的HTML内容,替换父页面的BODY。。。
{% endblock BODY %}
{% block FOOTER %}
页脚部分的HTML内容。
{% endblock FOOTER %}
在Flask应用Jinja2模版时,在模版中可以直接调用Flask app中的一些公用变量和方法。
引用Flask的request
对象:
<p> {{ request.url }} </p>
<p> {{ request.form.get('name') }} </p>
引用Flask的url_for(...)
方法:
<!-- 它会返回我们定义的路由`app.route('/index')`所对应的URL -->
<p> {{ url_for('index') }} </p>
<!-- 它会返回我们定义的路由`app.route('/post/{post_id}')`所对应的URL -->
<p> {{ url_for('post', post_id='127') }} </p>
在模版中,我们可以引用get_flashed_messages()
方法,获取Flask路由传来的闪现信息
:
{% for msg in get_flashed_messages() %}
<p> {{ msg }} </p>
{% endfor %}
这种闪现信息是从Flask路由中传来的,只要在路由中发一个flash('hello')
信息,相当于弹了一个alert()
。然后我们可以在Jinja2的模版中用get_flashed_messages()
获得flash过来的信息列表。
MVC
是一种编写代码的逻辑上的分割模型,即把代码分为三大部分:M、V、C,每部分明确的负责一个层面。这样的作用是什么?那就是为了日后更方便的代码维护、代码迁移和代码重构等。
传统的MVC(Model-View-Control)模型适用于大多数的桌面App和网络App的程序编写。
即Model模型
(负责数据库) -> View视图
(负责将数据显示为最终界面) -> Controller控制器
(负责将用户请求转化为界面或数据库的改动)。
Django的代码结构是典型的MVC框架。只是具体实施上,它把视图View作为一个Router路由来负责不同URL映射到不同的处理方法,然后用Template模版来作为动态显示,所以也叫MVT模型。(其实这么叫非常不准确,所以提到MVT的人不多)
注意:网路上对MVC和MVT的说法、图解都各有不一,因为很多人对这些叫法和结构都产生了混淆。所以不能太依赖各种非官方的文章和图片。
了解Django的这种“代码结构”有什么作用? 因为Django是一个以及成熟开发的网络App,我们要做的只是改一改具体业务信息即可。正因为他是已经完整开发的App,所以我们要清楚他开发的结构是什么,才知道哪种需求到哪里去改。
Object-Relations-Mappings
,实际上是Object -> Mappings -> Relations
这种方向,即:
把业务抽象为程序中的Object对象
,然后通过一种Mappings方法
,映射为Relational数据库
的表格中去。
简而言之,ORM实际上是一种操作数据库的模型
,或是一套操作数据库的方法
。
Django中提供了一套完善的ORM模型,即一个工具包,让你轻松把自己创建的业务对象和数据库里的表格对应起来,随便操作。
建议在虚拟环境中,且为django项目独立创建:
$ pip install django
$ django-admin startproject ./MyDjangoProject
创建好Django项目后,文件夹中会出现如下目录结构:
./MyDjangoProject
manage.py
:整个项目的管理文件MyDjangoProject
:整个项目文件
__init__.py
setting.py
:项目配置文件urls.py
:网址的路由设置,不同路径分配不同的处理函数wsgi.py
:作为Web Server,并负责和Django框架进行交互注意:Django中,一个项目只是一个Admin管理员,不包括任何的具体实现。真正的业务代码实现,也就是具体的MVC实现,是要在单独的各个模块中的。
Django中的一个项目只是代表一个大框架,不同于Flask创建一个项目即一个完整的app。
Django中的一个app只是大框架下的一个子模块。Django中的每个app都具备完全的MVC结构代码。
实际工作中,在我们设计好业务模块
后,就可以在Django中把模块分开: 一个模块生成一个子目录,即一个app
应用。 每个app子文件夹都有同样的一套文件,实现了典型MVC代码分割。
在一个Django项目中,创建一个子app应用的命令为:
$ python ./manage.py startapp MyApp01
创建好子app的MyApp01后,Django的目录中就出现了一个子目录MyApp01
,结构如下:
./MyApp01
admin.py
:网址后台Dashboard相关__init__.py
:migrations/
__init__.py
models.py
:MVC中的M,负责和数据库交互tests.py
:测试代码views.py
:MVC中的V,负责页面的模版建立好各个模块后,我们需要在项目主配置settings.py
中把每个模块进行注册,才能集合为一个项目。
在settings.py
中注册的方法如下:
启动服务器:
$ python ./manage.py runserver IP:端口
然后就会启动一个指定IP的服务器:
项目架构:
实际上这个“疑难杂症”系列的开启,就是源于今天近一个小时解决Chrome无法访问Github.io的问题。 由于现在在国内,所以装了各种Shadowsocks和Chrome切换代理插件,甚至还修改了本地的Hosts文件。所以一旦遇到无法访问网页,真是要伸手到很多地方去开启、关闭、各种调试。不过最终终于在chrome的插件管理里面找到了问题。
由于之前安装了AdBlock
插件,删除它
实际上我装了以后一直没有使用这个插件,因为会被很多网站检测到并提示这个做法不道德。所以我就长期将这个插件放在不启动模式。但是没想到即便不启动,它仍然有屏蔽一些网站的功能。导致我无法访问github.io的各种个人主页。
删除adblock插件后没多久就又无法访问了,所以再想别的问题。不断尝试后发现,只要把所有的url地址都强制改为https://
安全链接,就能访问。
类别:Web Dev Tools
作用:显示当前网站的技术栈组成
分类显示清晰好看,速度快(与网页一起加载)。但是长期占用200M以上内存。
占用内存小,但是需要临时检测,所以要等待出结果。界面好看。
临时加载,但是信息非常非常全面。非弹出窗口,而是在网页中加载一个竖条,失去焦点后不会消失,很方便。比较慢,但是信息全,分类方便看。
速度慢,css样式还经常加载不出来。还显示好多不太用得到的东西和连接,把内容弄的很长让你去翻页看。
鼠标放在哪,就显示哪的css样式。按ESC取消。
Reports on how the webpage loaded. 用时长,检测单个网页,然后单独弹出一个网页显示报告结果。显示清晰,结果分类详细 展示很容易看。信息很有价值。
超详细检测当前网站的类似网站、被引用情况、SEO信息、用户分类统计等。超详细超有料!
YSlow
- 网页加载检测报告
YSlow analyzes web pages and suggests ways to improve their performance based on a set of rules for high performance web pages. 网页加载检测报告。非常详细,但是报告排版不是很容易看懂。比Lighthouse快
通过加载本地其事先加载好的资源,并拦截其他第三方的资源请求来加快网页加载速度,该插件还能在加快网页加载速度的同时还能减少一些跟踪脚本的跟踪功能以使得你的网络环境更加安全。
重定向/屏蔽 URL,修改/删除 headers. gooreplacer 最初为解决国内无法访问 Google 资源(Ajax、API等)导致页面加载速度巨慢而生,新版在此基础上,增加了更多实用功能,可以方便用户屏蔽某些请求,修改 HTTP 请求/响应 的 headers。
The most popular Chrome extension, with over 40 million users! Blocks ads all over the web.
ABP 不管是什么网页都会插入 14000 多条元素隐藏规则,所以占用内存很大.
uBlock Origin对比ABP有性能上的优势,其最为突然的性能优势就是其占用极低的内存和 CPU。有分析说adp主要靠的是屏蔽,而ublock Origin主要靠的是阻断,ABP的工作需要在所有网页插入屏蔽脚本和css,而uBlock Origin直接阻止需要屏蔽的内容进入当前网页。一般的拦截请求的规则大家都差不多,关键的是元素隐藏规则,uBlock 把它叫做修饰规则 cosmetic filters,ABP 不管是什么网页都会插入 14000 多条元素隐藏规则,所以占用内存很大,uBlock 插入的很少,因为他是在网页开始加载以后才判断需要用到哪些元素隐藏规则。所以才在性能上面要更加优越。
AdBlock 对比 uBlock Origin 参考使用方法文章
参考文章: uBlock Origin:不仅可以过滤广告还可以创建过滤规则屏蔽任何你不想见的元素 (1)关闭按钮,用于关闭uBlock按钮,蓝色开启/灰色关闭 (2)元素吸管,屏蔽元素按钮(用过ABP的都知道) (3)网络请求日志(个人理解:网络请求记录监控中心):可以按照网络资源查看/屏蔽的控制系统 网络请求日志与开发工具的网络 不同之处在于,不单单可以查看网络资源,还可以屏蔽不想要的脚本等等之流 (4)禁止网页弹窗按钮,开启后,该网页永久无法弹窗 (5)严格屏蔽按钮,开启后,将不再对当前网页本身进行屏蔽,比如可以用于网络运营商的网络劫持,规则(uBlock设置->自定义规则列表):
参考:uBlock Origin中文使用手册,告诉你uBlock Origin怎么用!
打开后台设置选项: 对于中文规则:
注意:启用越多的过滤规则就会产生越高的内存占用。 然而,即使再添加,uBlock₀ 的内存占用依然比其他常见的过滤工具要低的多。
Ghostery
- Privacy Ad Blocker
Ghostery’s built-in ad blocker removes advertisements from a webpage to eliminate clutter so you can focus on the content you want. Protect your privacy Ghostery allows you to view and block trackers on websites you browse to control who collects your data. Enhanced Anti Tracking also anonymizes your data to further protect your privacy. Browse faster Ghostery’s Smart Blocking feature speeds up page loads and optimizes page performance by automatically blocking and unblocking trackers to meet page quality criteria. Customize your display Ghostery offers multiple displays and insights dashboards so you can see the information that’s relevant to you.
主要是看一些常用网站对内存和CPU的负担
跳转至该网页:MIT OCW Linear Algebra 18.06sc
明明就是一个静态网页,加载时半分钟都是CPU占用100%,加载完毕后300M以上的内存占用。
语法检查在写邮件和写笔记时,非常好用,问题是他们默认等都是UK English
,
但是当你写Realize
, analyze
时就会被认为是拼写错误。
实际上是UK English 和US English的差别。
要纠正这一点很简单,只要在浏览器设置里面找到Language
栏,语言里面加入美式英语即可。
需要刷新网页才能看到效果。
Flask是基于Python的Web后台服务器框架,相对于Django来讲属于非常灵巧的轻量级框架。目前市面上应用程度很广,值得学习一下。
pip install virtualenv
mkdir /d/workspace/myFlask
cd /d/workspace/myFlask
virtualenv --no-site-packages venv
source venv/Scripts/activate
# Now is already in virtual enviroment
pip install Flask
# 其他都一样 只有运行不同
venv\Scripts\activate
touch hello.py
mkdir static
mkdir templates
在hello.py中输入以下内容并保存(最简单Flask)
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
python hello.py
在template
文件夹中新建模板users.html,并随便写几句话,在hello.py中加入如下语句:
#记住在前面需要引用渲染函数
from flask import render_template
@app.route('/users/')
def show_users():
return render_template('users.html')
Spider
-> item
-> Pipline
其中:
Spider
只负责向目标网站,获得response,传给itemitem
只负责从response中整理数据,变成item字典对象,传给piplinePipline
只负责把item字典对象转换为ORM,保存到数据库Logging:
LOG_LEVEL = 'INFO' # DEBUG/INFO/ERROR/CRITICAL
LOG_FORMAT = '%(levelname)s: %(message)s'
禁止cookies, 防止因为cookies不合格而被发现:
COOKIES_ENABLED = False
Robot协议遵守(推荐: 不遵守):
ROBOTSTXT_OBEY = False
随机User-Agent:
随机Proxy:
为了适合各种不同的爬取需求,Scrapy提供了对应各种情况的不同爬虫基础类
。
其中各自的特点是:
Scrapy Spider
: 所有爬虫的基础类。一般用于自定义爬虫来继承,定制性强。Crawl Spider
:通用型爬虫。适合抓取页面所有链接,可以定制Rule
筛选链接XML Feed Spider
:XML数据爬虫。自带XML解析CSV Feed Spider
:CSV数据爬虫。自带分隔符判断等Sitemap Spider
:Sitemap爬虫Spider类中的parse()
函数,本质上是一个Generator生成器。然后Scrapy引擎会不断的调用这个生成器,把获得的response
传送给下一步骤的Pipline.
parse()
中很常见多个yield
,或一个return
,或什么都没有。这代表了你可以根据自己选择,让它不断递归挖掘信息
,或一次性返回一个结果,或者什么都不返回,直接输出写入文件。
另外,parse()
的返回值类型也不是固定的,目前我们可以返回这两种类型:
scrapy.item
对象:如果直接返回item,那么这个item会直接传送到pipline进行下一步处理。scrapy.Request
对象:如果返回的是Request对象,那么引擎会再次发送Request请求,递归回本函数。我们常见在parse()
中写两个yield语句,返回两种不同类型的对象,如下:
def parse(self, response):
#do something
#item[key] = value
yield item
yield scrapy.Request(url, callback=self.parse)
这个不好理解。因为一般return的逻辑是统一返回一种东西。但其实只要了解yield就知道,引擎调用parse()
,获得一个对象,根据它的类型做不同处理。处理完了再调用parse()
,看这次又给我什么对象,我再处理。然后再调用。
把脑袋中的return
逻辑忘掉,想想yield
逻辑,就容易理解了。
如果能够理解yield的逻辑,那么我们就可以在parse()
函数中玩出花了:
我们可以for循环yield
,可以递归yield
,可以一个一个手动yield。总之不管yield"吐出“什么东西,引擎都会接住,然后处理吐出的这个东西。处理完了再让你接着yield。
scrapy.Item
对象,在爬虫中可有可无。如果你有需求把获得的数据转换为ORM进行数据库保存的话,这个很方便。但是没有,也不会影响爬虫运行。
关于item对象的名字,也是随便起,没有规则。因为最后是在
spider
类中被手动调用生成实例的,引擎不会参与。
如果需要ORM处理的话,就可以继承scrapy.Item
对象。内容极其简单:只要定义几个类属性(字段)即可,连字段类型都不用填。
import scrapy
class MyItem(scrapy.Item):
username = scrapy.Field()
description = scrapy.Field()
Pipeline
不需要继承scrapy的什么基础类,而是直接写即可。
但是爬虫引擎怎么发现这个类呢?——通过在settings.py
中注册自定义pipeline
的名称,然后引擎会自动调用这个类的某几个固定的函数名
,传入相应的参数。
注意:pipeline
不是只有在爬虫的request请求后才会被调用,而是根据自己的需要在spider开始前、结束后都可以被调用。
# pipelines.py
class GoodPipeline(object):
def open_spider(self, spider):
print('[PIPELINE] open:', spider)
def process_item(self, item, spider):
print('[PIPELINE] process:', item, spider)
return item
def close_spider(self, spider):
print('[PIPELINE] close:', spider)
然后在settings.py
中注册这个pipeline:
ITEM_PIPELINES = {
'MyProject.pipelines.MyProjectPipeline': 300,
'MyProject.pipelines.GoodPipeline': 400,
}
其中后面的300, 400
这些是优先级的表示,数字越小越优先执行这个pipeline。
从下面这个执行结果,我们可以看到Pipline中定义函数的执行顺序:
上面我们看到在pipeline对象中我们写了process_item()
函数,实际上还有其它固定名称的函数可以写。
这些固定函数如下:
process_item(self, item, spider)
open_spider(self, spider)
close_spider(self, spider)
from_crawler(cls, crawler)
最佳运行环境是Python2。
入门Nginx,主要是学配置文件。 理解Nginx,才需要懂背后逻辑。
大概的结构是:
http {
server {
listen 80
location / {
#....
}
}
}
可以看到这个整体结构:最外面是Protocol
,即传输协议,可以是http/https/pop3等;然后是这个协议下的server
服务应用,一个server
只能对应一个port
端口,即一个本机唯一的网络进程;这个服务应用下,能够对应多个location
,进行灵活的地址映射。
在Server中:
一个Server
其实就对应一个port
端口入口,相当于本机一个唯一的网络进程。
一个Server
可以有多个location
位置,如根路径/
,或子路径/api/v3/get
。
一个Server
可以把入口port
”的不同location
映射“到其它多个port
上,即反向代理 。
一个Server
的设置可以完全独立于其它Server
不同,如chartset
, reversed proxies
一个配置文件,可以有多个Server
。
对于多个应用,一个应用配置一个port
是肯定的,所以也就是要配置多个对应的Server
,如80给Flask用,8080给PHP用。
"The Middlware classes are called twice during the
Request/Response Life Cycle
. For that reason, theorder
you define the Middlwares in the MIDDLEWARE_CLASSES configuration is important."
Refer to: How to Create a Custom Django Middleware
Django built-in middlewares (installed by default):
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Refer to: The Procfile - Heroku Dev Center
A Procfile
is a file remembers that the commands you usually type by hands to deploy your web application like Django, Flask and such WebApp projects.
It's indeed the file to implement the concept Deploy as code
that tracks the important deployment commands.
A Procfile
can look like this:
web: gunicorn -b "0.0.0.0:$PORT" -w 4 myapp:app
worker: python worker.py --priority high,med,low
worker_low: python worker.py --priority med,low
And with the Procfile management tool Honcho
, all you need to do to start a service is to type:
$ honcho start
A python clone of Foreman. For managing Procfile-based applications
Refer to: https://honcho.readthedocs.io/en/latest/ Refer to: https://github.com/nickstenning/honcho
Install Honcho
:
$ pip install honcho
Official site: https://github.com/django-wiki/django-wiki Docker: https://github.com/riotkit-org/docker-django-wiki
Run on docker:
docker run --name djangowiki quay.io/riotkit/django-wiki:0.4.5
但是页面太丑,文档太少,社区太小。决定还是不要深挖了。换MediaWiki吧
Refer to: How to Setup MediaWiki with Docker
First try:
docker run --name some-mediawiki -p 8080:80 -d mediawiki
open http://localhost:8080
docker cp LocalSettings.php mediawiki:/var/www/html
open http://localhost:8080
More specific settings:
# docker-compose.yaml
# Access via "http://localhost:8080"
# (or "http://$(docker-machine ip):8080" if using docker-machine)
version: '3'
services:
mediawiki:
image: mediawiki
# restart: always
ports:
- 8080:80
# links:
# - database
volumes:
- ./images:/var/www/html/images
- ./data:/var/www/data
- ./LocalSettings.php:/var/www/html/LocalSettings.php
environment:
MEDIAWIKI_SERVER: http://localhost:8080
MEDIAWIKI_SITENAME: MyWiki
MEDIAWIKI_LANGUAGE_CODE: en
MEDIAWIKI_SECRET_KEY: mysecret
MEDIAWIKI_DB_TYPE: sqlite
MEDIAWIKI_DB_NAME: wikidb
MEDIAWIKI_ENABLE_UPLOADS: 1
MEDIAWIKI_EXTENSION_VISUAL_EDITOR_ENABLED: 1
MEDIAWIKI_DEFAULT_SKIN: vector
MEDIAWIKI_DEBUG: 1
docker-compose up
必备插件:VisualEditor编辑器:
cd extensions
# 必须要配合当前的mediawiki的版本:1.34
git clone -b REL1_34 https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor.git
WYSIWYG编辑器:
cd wiki/extensions/
git clone https://github.com/Mediawiki-wysiwyg/WYSIWYG-CKeditor.git WYSIWYG-src
ln -s WYSIWYG-src/WYSIWYG WYSIWYG
ln -s WYSIWYG-src/SemanticForms SemanticForms
mv WikiEditor WikiEditor-org
ln -s WYSIWYG-src/WikiEditor WikiEditor
因为各种插件的安装依赖实在太难找了,所以直接docker解决。
参考:https://github.com/pastakhov/compose-mediawiki-ubuntu 这个Docker是维护的最好最全的一份。
没有网络问题的话,下面几句就能完成安装运行:
git clone https://github.com/pastakhov/compose-mediawiki-ubuntu.git
cd compose-mediawiki-ubuntu
docker-compose up
如果遇到网络问题,比如git下载,那么就需要手动在网络方便的地方提前下载好:
#download.sh
mkdir required && cd required
export MW_VERSION=REL1_29
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/core.git
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/mediawiki/skins/Vector
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/mediawiki/skins/Modern
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/mediawiki/skins/MonoBook
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/mediawiki/skins/CologneBlue
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/ConfirmEdit
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Gadgets
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Nuke
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/ParserFunctions
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Renameuser
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/WikiEditor
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Cite
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/ImageMap
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/InputBox
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Interwiki
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/LocalisationUpdate
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/PdfHandler
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Poem
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/SpamBlacklist
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/TitleBlacklist
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/CiteThisPage
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/SyntaxHighlight_GeSHi
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Echo
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Thanks
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/CheckUser
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Flow
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Babel
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/cldr
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/CleanChanges
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/UniversalLanguageSelector
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/VisualEditor \
&& cd VisualEditor && git submodule update --init && cd ..
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/CirrusSearch
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/Elastica
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/MultimediaViewer
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/MobileFrontend
git clone --depth 1 -b $MW_VERSION https://gerrit.wikimedia.org/r/p/mediawiki/extensions/ElectronPdfService
然后把Dockerfile一下:
# 所有git clone的地方,都替代成类似这种:
ADD required/VisualEditor $MW_HOME/extensions/VisualEditor
RUN set -x \
&& cd $MW_HOME && cd /extensions/VisualEditor \
&& git submodule update --init
参考:https://showzeng.itscoder.com/nginx/2016/10/03/use-nginx-to-deploy-static-pages-easily.html 参考:https://blog.csdn.net/lizhiyuan_eagle/article/details/90639448 参考:https://juejin.im/post/5d7b72b7e51d453b386a63bc
http {
server {
listen 8080;
server_name localhost;
location / {
root /var/www/html;
index index.html index.html;
}
location /sub {
alias /path/to/some-other-name; # 注意这里二级目录一定要用alias而不是root,否则404
index index.html index.html;
}
}
}
如果只为了分享,而不需要另一方写入文件,那么Nginx的文件服务比SMB或Webdav靠谱更多。
可以直接点开视频看,也可以直接点开图片。
http {
server {
listen 8080;
server_name localhost;
location / {
root /var/www/html;
index index.html index.html;
}
location /share {
alias /path/to/shared-folder;
autoindex on; # Turn on file-server
autoindex_exact_size off; # Turn off to show human readable size
autoindex_localtime on;
}
}
}
要安装免费的第三方CA认证的SSL证书,需要:
Refer to: https://letsencrypt.org/getting-started/ Refer to Cerbot for ACME client: https://certbot.eff.org/lets-encrypt
Refer to: https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx
Nginx + Ubuntu 1604
sudo apt-get update
sudo apt-get install software-properties-common -y
sudo add-apt-repository universe -y
sudo apt-get update
sudo apt-get install certbot -y
# 需要手动按回车确定:
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get install python-certbot-nginx -y
# 生成证书
sudo certbot certonly --nginx
但是Nginx如果不支持需要的插件,有可能会出现错误。
cd ~
git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install
# 添加cronjob,定期更新证书
echo '0 0 * * * "/home/ubuntu/acme.sh"/acme.sh --cron --home "/home/ubuntu/acme.sh" > /dev/null' |crontab
crontab -l
未完待续..
copy_current_request_context
doesn't work in thread as wellfrom flask import Flask, g as flask_g
app = Flask(__name__)
@app.route('/hello')
def hello():
flask_g.glo = 'not set'
f()
data = getattr(flask_g, 'glo')
print(data)
return ''
def f():
flask_g.glo = 'changed'
app.test_client().get('/hello', headers={'myhead': 'not set'})
The result is changed
.
This f()
has to be in the request context.
import gevent
from flask import Flask, has_request_context
app = Flask(__name__)
@app.route('/hello')
def hello():
gevent.joinall([gevent.spawn(has_context)])
return ''
def has_context():
print('Has context:', has_request_context())
resp = app.test_client().get('/hello', headers={'myhead': 'not set'})
The result is False
. Same thing goes with Thread
and Process
.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
How to run:
$ uvicorn main:app --reload
It's based the Pydantic
library.
Refer to: https://pydantic-docs.helpmanual.io/usage/models/
GET method path parameters:
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str = None):
return {"item_id": item_id, "q": q}
Basic Request model:
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
@app.post("/items/")
async def create_item(item: Item):
return item
Basic Response models:
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item
Nested Models:
class PackageEnum(str, Enum):
package_a = 'store_product_level'
package_b = 'usage_product_level_base'
class SpanEnum(str, Enum):
daily = 'daily'
weekly = 'weekly'
monthly = 'monthly'
class ParamItem(BaseModel):
package: PackageEnum
span: SpanEnum
target_date: str
class DetailItem(BaseModel):
task_name: str
params: ParamItem
class PostBody(BaseModel):
user_id: str
tasks: List[DetailItem] = []
@app.post('/p')
def handle_post(body: PostBody):
return body.json()
Refer to Bigger Applications - Multiple Files: https://fastapi.tiangolo.com/tutorial/bigger-applications
project/
- application/
- app.py
- models/
- request_models.py
- middlewares/
- routers
- myrouter.py
The Starlette middleware deals with both Request / Response at the same dispatch()
function, which matches the before_request
and after_request
functions of Flask middleware.
Refer to: https://fastapi.tiangolo.com/tutorial/middleware/ Refer to: https://www.starlette.io/middleware/#basehttpmiddleware
#application/middlewares/demo.py
from logging import getLogger
from starlette.middleware.base import BaseHTTPMiddleware
logger = getLogger(__name__)
class DemoMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
logger.critical('[MID] Recieved request in DemoMiddleware...')
response = await call_next(request)
logger.critical('[MID] Got respoonse in DemoMiddleware...')
response.headers['X-my-demo-header'] = 'Example'
return response
#application/app.py
from fastapi import FastAPI
from application.routers import myrouter
from application.middlewares.log import DemoMiddleware
def create_app(*args, **kwargs):
app = FastAPI(debug=True, title='aa-atq-service')
# Middlewares
app.add_middleware(DemoMiddleware)
# Routers
app.include_router(myrouter.router)
return app
app = create_app()
import json
@app.post('/endpoint')
def endpoint(body: PostBody):
payload = body.dict()
assert json.loads(body.json()) == payload
@app.post('/endpoint')
def endpoint(request: Request):
headers = request.headers
print(headers.get('Content-Type'))
Installment Methods:
Image: https://hub.docker.com/r/plexinc/pms-docker/
Docker-compose:
# Access from: http://localhost:32400/web
---
version: '3'
services:
plex:
container_name: plex
image: plexinc/pms-docker:beta
restart: unless-stopped
environment:
- TZ=America/Denver
- PLEX_CLAIM=claim-###############
- PLEX_UID=1000 # $ id -u `whoami`
- PLEX_GUID=1000 # $ id -g `whoami`
network_mode: bridge
hostname: PlexDockerBeta
ports:
- "32400:32400" # Main Access
- "32469:32469"
- "32401:32401"
- "3005:3005"
- "8324:8324"
- "1900:1900/udp"
- "32410:32410/udp"
- "32412:32412/udp"
- "32413:32413/udp"
- "32414:32414/udp"
volumes:
- $PWD/config/plex:/config
- $PWD/transcode:/transcode
- $PWD/media:/data
# - $PWD/tv:v
# - $PWD/movies:/movies
记录网络应用、网站建设等相关笔记。
涉及内容