solomonxie / blog-in-the-issues

A personalised tech-blog, notebook, diary, presentation and introduction.
https://solomonxie.github.io
67 stars 12 forks source link

WebApp 网络应用架构及搭建 #32

Open solomonxie opened 6 years ago

solomonxie commented 6 years ago

记录网络应用、网站建设等相关笔记。

区分:这里只涉及TCP/IP协议族的最顶层:Application Layer, 即应用层。较底层的TCP、UDP、IP等都在另一篇中记录。

涉及内容

solomonxie commented 6 years ago

Markdown中插入数学公式

以下方法:

Google Charts API 制作数学公式图

都知道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

formula

Codecogs 制作数学公式

相比Google来说,Codecogs采用了一样的url引用方式,一样的语法,且也必须是url编码。但是Codecogs提供了一个网页编辑器,可以直接在上面可视化编辑公式,然后直接拷贝图片链接即可,所见即所得,要快捷的多了。 当然如果在意服务器稳定性还是想用Google的话,那还可以把图片链接前面直接改成Google api的地址即可。

image

生成好图片后,直接右键点击生成的图片获取链接即可。

solomonxie commented 6 years ago

Wordpress初始安装

Wordpress实在是太好用,就连鄙视PHP的人也不能说Wordpress不好。作为纯傻瓜式Web应用框架来说,Wordpress实在是让我这个小白从零开始做了一套完整的企业前后台网站。不能不在这里记录一些经验以供使用。

Wordpress初始配置

一般是通过ssh连接远程服务器,在命令行里操作。

安装LNMP环境 (Linux+Nginx+Mysql+PHP)

#安装环境
sudo apt-get install php5-fpm nginx mysql-server php5-mysql
# 启动Nginx服务
service nginx start

安装Wordpress

# 下载wordpress
wget http://wordpress.org/latest.zip
# 解压wordpress
apt-get install unzip
unzip latest.zip
# 设置权限
chmod 777 -R /var/www/html/网站文件夹名

设置Mysql

# 登录mysql并设置密码
mysql -u root -p
##--创建数据库--
create database 数据库名;
solomonxie commented 6 years ago

❖ 利用「Github Pages」快速建立静态网站

Github Pages对于建立静态网站来说真的是超级方便,概念方便,配置方便。 只要你不超出HTML+Javascript+CSS的范围,一切都好说。 如果为了漂亮,可以使用Bootstrap等各种技术加强页面显示,只要是静态的,一切都好说。

个人主页vs项目主页

参考:单个GitHub帐号下添加多个GitHub Pages的相关问题

Github Pages有两种建站方案,一种叫个人主页,一种叫项目主页:

注意一般即使上传好了网页,也不会及时显示出来,有时可能会等几个小时Github才会显示最新的页面。

自定义域名的配置

一般solomonxie.github.io这种域名虽然已经很简单了,但还是挂着github的名字且有点长,始终摆脱不了供应商的影子。如果做为个人网站的话,这一点的确会影响些形象和印象的独立性。 所以有必要把这个域名映射到自己申请的外部域名上去。

以下为域名映射的操作步骤:

solomonxie commented 6 years ago

❖ Jekyll 动态地建立静态博客网站 (Get Started)

提前声明:Jekyll并不简单,必须要正确的看待它。把它和PHP,JSP和Django等放在一起讨论会减少很多失落感。它的学习曲线几乎相当于Wordpress,工作流程和结构也几乎一样。

Jekyll与Wordpress最大不同的就是,没有数据库。但是体验上来说也算不上什么大差别。 彻底摒弃数据库,这算是一种Jekyll式的新思路。 因为你需要的只是定期更新一些Markdown格式的文章,然后让它显示成网页,并放在一起成为网站而已。没必要大动干戈的设计数据库什么的。

简单的说,Jekyll是一个基于Ruby语言的静态博客网站制作工具,它可以把Markdown转换成HTML网页。

不过对于一个HTML网页来说,它得有标题、样式、日期什么的,甚至一些根据文章的不同而动态改变的内容等。这些就不仅是把Markdown转换成HTML而已了。很多内容需要你在Markdown文件里面就写明指定。

另注:Jekyll虽然和Github Pages搭配免费,但其实是完全独立的产品。可以在任何地方使用,像Wordpress一样。

安装Jekyll

安装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

目录里面内容如下: image 这里面是完整的一个网站,可以直接运行浏览。 然后你就可以根据自己的主页、其它网页什么的,在这个基础上修改了。

Jekyll new时发送错误: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链接察看网站效果。

image

允许公网访问

如果jekyll部署在了公网上的服务器上,那么很轻松就可以公开给所有人访问了。 语法如下:

$ jekyll serve --detach  --host 0.0.0.0

# 或
$ jekyll serve --force_polling -H 0.0.0.0 -P 4000

然后就会显示如下: image

也就是说公网运行jekyll的话,程序就转到后台了,需要退出的话需要手动关闭进程。

然后根据网站设计时候指定的端口,相应的在服务器防火墙上开放这个端口,比如4000。 然后用http://服务器IP:4000这样的就能访问了。 如果要不带端口号访问,就在_config.yml中把端口号设计为80。(但是经常有冲突,需要解决)

Jekyll Workflow 工作流程

使用Jekyll,主要难就在一开始,需要设计网页样式,设置全站的规则等等。 但是一旦这些基本设置都完成了,以后更新就只需要专注的写Markdown文件即可。

Jekyll自定义网站

Jekyll new命令新建一个网站结构后,文件夹里面有很多文件。这些文件结构都是什么作用,是我们必须要学习的。

Jekyll文件夹结构

注意:

Front-Matter 文件头信息

文件头信息在这里被叫做front-matter,或yml-header,它是写在每个Markdown文件头部的设置信息。主要是指明这篇文章标题、日期、使用的模板、样式、标签、分类等,这样Jekyll就可以根据这些设置把markdown文件转换成你想要的最终HTML网页了。 image

头信息的常用参数如下:

“真正的”拿到Jekyll生成的静态网站

Jekyll的最终目标和整个存在意义都是生成静态网站。 但是, 默认情况下,所谓生成出来的静态HTML页面,你也不能直接打开看到效果!必须要运行jekyll serve才行,或者把它放到Github的Repo里。 那还叫什么静态网站?! 真正的静态网站不是生成HTML就行了,而是让你双击打开HTML就能在浏览器看到效果。

避开这个有点矛盾的逻辑不说了,我们有比较方便的外部工具来做到这点。 那就是最常用的wget下载命令。 wget可以把网页或整个网站下载下来,并且能自动转换各种文件里的路径。 命令如下:

$ wget -r --convert-links <URL>

所以当你运行Jekyll serve成功编译生成_site目录后,就可以用wget下载本地的这个网站了。

Jekyll的体验

目前体验极其糟糕:

solomonxie commented 6 years ago

❖ Jekyll 第三方模版安装运行:从入门到放弃

实际上,Jekyll安装主题是非常反人类的——它一点也不比自己写模版简单,学习成本真是高。 安装主题不是把人家做好的template直接复制过来就能用了。 每个模版设置的变量设置名、依赖的gem包都不一样,还经常需要在本地安装所有依赖包,安装jekyll插件等。如果不懂Ruby gem的话,还真是不简单。

到了这里,一般人真的会问自己应不应该再继续下去。因为明明简单的东西,不知道是不是还值得了。

我相信所有坚持学习jekyll的人,都有自己非学不可的理由吧。

包管理器的理解

Jekyll是用Ruby语言构建的,且每个主题都会有超多的Ruby依赖包。在这里需要先理解一些基本概念才能进行下去。

简单说,gem主要管理整个系统的Ruby包,下载安装卸载之类。而Bundler只负责管理每个项目的Ruby包依赖。

一般安装方法

先讲讲一般通用的模板安装方法:

至此,一般简单的模版都可以搞定了。如果超出任何以上提及内容,我们就要到"特殊安装方法"一节来分析了。

特殊安装方法

一般安装方法解决不了的,基本上算是特殊安装方法了。

经过我尝试了下载和安装几十个下载的主题后,发现如果碰见一个连bundle install命令都不用,直接jekyll serve就打开服务的,那简直是像中大奖一样的。 每个主题的安装都不太一样,且遇到的错误都完全不同。通用性极其小。

要想真正安装好一个主题,必须掌握基本的Debug能力,命令行信息的理解能力,如果精通Ruby那么就再好不过了。

基本上我不打算在这里浪费时间把这些情况列出来讨论,只是想把坑分享出来,提醒你不要跳。

如果不是100%确定真的想用这个主题,就不要浪费时间去调试和修改gem环境了,不值得。

我的经验是:安装越麻烦的,模版本身其实反而更丑更差劲👎。

涉及Node.js技术的模版安装方法

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里建个静态网页而已。

常见问题

运行Jekyll serve 总报错: ERROR `/sw.js' not found

image

根据这个Stackoverflow的回答sw.js是Service worker的意思,是自动生成的。 基本上不会造成什么影响,但是主要出现这个错误,jekyll就没法同步更新。

根据我的实际体验,这不是主题的问题,而是jekyll的问题:对每个主题都报有这个错误。

solomonxie commented 6 years ago

将Jekyll Themes模版部署到Github Pages [DRAFT]

基本步骤:

image

solomonxie commented 6 years ago

❖ 利用Sphinx+Github+Readthedocs+Rst快速创建规范文档 [DRAFT]

注意:这个服务的文档不能用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]:
solomonxie commented 6 years ago

❖ Liquid语法学习 (Jekyll模版引擎)

副标题:Liquid Template Engine

学习制作Jekyll模版,其实主要是学习Liquid语法。

参考:Liquid官方文档。

就像PHP、ASP、Python等一切网络动态语言一样,Liquid也相当于一种独立的动态语言,没什么大差别,基本功能都有。 说白了就是动态生成HTML,可以输出变量,操作数组,调用外部数据,设置IF ELSE判断,FOR循环等,这些都能达到。

开始讲语法前,大概说明一下运行流程:

常用变量及属性

参考:Jekyll 语法简单笔记

site对象

site对象是全站都能调用的变量,全部都在_config.yml文件中定义。 常用变量如下:

page对象

categories对象

site.categories列表中循环得到的是一个一个的category,其中包括这些属性:

tag对象

site.tags列表中循环得到的是一个一个的tag,其中包括这些属性:

paginator对象

列表读取(各种归档页面用)

循环读取Posts

读取全站所有的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中指定分类,要不然读不出来。

循环读取categories

读取全站所有的分类:

{% 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

读取全站所有的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 %}

读取某category下所有文章并按tag分组读取

{% 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 %}

Post读取

需要在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 分组和where_exp条件筛选

官方的group_by做到了复杂查询的功能,比如查找某个category下的全部文章并按tag分组显示。 相对自己写for/if实现来说,虽然官方提供了这个功能,但是你仔细阅读文档就会发现,这个group_by必须配合单独的静态的额外的文档才能实现。 也就是说,你必须手动写个mygroup.doc文件,一个一个指定每篇文章的分组、分类、顺序等。 那实在太麻烦了。

参考官方:Navigation

solomonxie commented 6 years ago
solomonxie commented 6 years ago
solomonxie commented 6 years ago
solomonxie commented 6 years ago

利用「Gitbook」快速构建专业文档、个人博客

不像Readthedocs那么复杂,Gitbook所需的文件和设置极其少,而且原生支持Markdown和Github仓库自动同步。

一般本地无需安装,只要在Github中存入相应的Markdown文件就能自动生成了。 不过为了随时测试和预览,有必要在本地也弄一套。

参考:GitBook 简明教程

安装(不要安装旧版的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安装这些插件。之后就应该没问题了。

基本文件结构: snip20181002_16

Gitbook至少需要两个文件:

SUMMARY.md目录文件格式: Gitbook的目录最多支持3级。

Part I

Part II


solomonxie commented 5 years ago

Apache 2 reload 失败

背景: 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.

尝试过的方案:

解决方案:

# 找到占用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.
solomonxie commented 5 years ago

Apache2 修改监听端口

因为有的服务器都是给自己测试用的,不喜欢用公开的80和443端口。

参考:Configure apache to listen on port other than 80

编辑这个文件/etc/apache2/ports.conf:

solomonxie commented 5 years ago

Ubuntu服务器安装OwnCloud私有云盘 [DRAFT]

安装步骤(不要全部复制,部分手动)

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,就能看到这个画面,要求你设置初始用户名密码:

image

重制密码方法:

$ sudo -u www-data php occ user:resetpassword <用户名>

image

开启Https支持(SSL)

solomonxie commented 5 years ago

Owncloud 开启Https(SSL)支持(Apache2)

参考:为ownCloud配置SSL连接

创建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 

image

创建好后,就可以在apache的配置里面引用这个SSL证书了。 但是因为这个证书是自己创建而不是第三方公司的,所以不会被任何浏览器信任,每次都会出现NET::ERR_CERT_AUTHORITY_INVALID错误:

image

这个是没办法解决的,只能点击proceed继续前往。然后看到地址栏里一直会有这个提示,非常醒目: image

如果这个可以忍的话,那么就继续配置。

修改/etc/apache2/ports.conf文件,设置https等端口:

修改/etc/apache2/httpd.conf文件,设置虚拟主机:

修改/etc/apache2/sites-available/owncloud.conf文件,配置owncloud服务的关联:

重启apache2, sudo /etc/init.d/apache2 reload

solomonxie commented 5 years ago

Nginx + Keepalived (高并发 + 高可用)

High Concurrency + High Available

Nginx处理高并发非常厉害。但是如果承载Nginx本身的服务器突然出了问题,就很没法再继续了。 Keepalived则用于建立集群,让Nginx可以同时在多个服务器上运行,一个出问题就切换到另一个,保证服务的高可用

solomonxie commented 5 years ago

❖ 一文了解Python网络应用的技术架构 [DRAFT]

如果想了解基于Python的网络应用,除了知道Python语言以外,还必须要知道整条架构的技术栈。 Python的WebApp架构不是一气呵成的,而是在每个环节都要挑选一个合适的模块最终组合在一起才能发挥作用。

理解CGI

要理解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: 针对Python的翻译机制

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网络协议等东西,专心做好自己的网站就好了。

image

WSGI 组件

~在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。

  1. WSGI server:负责从客户端接收请求,将request转发给application,并将application返回的response返回给客户端,比如uWSGI和Gunicorn都是实现了WSGI server协议的服务器。
  2. WSGI application:接收由server转发的request,处理请求,并将处理结果返回给server,比如Flask和Django都是实现了WSGI application协议的web框架。
  3. 注:Flask和Django框架都有自己实现的简单的WSGI server,但一般只用于调试,生产环境下还是最好用其他WSGI server。
  4. 反向代理:指的是用代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端。与正向代理不同,用户不知道真实的服务端,反向代理服务器会帮我们把请求转发到提供真实计算的服务器那里去。 整体架构 下图可以很直观地说明uWSGI(Server)、客户端(浏览器)、Flask应用(application)之间的关系。

image

网络应用技术栈的组合搭配

参考:Web 应用 & 框架 (The Hitchhiker's Guid to Python)

可选方案有:

在这些组件里面,一般从每一个大类里面挑一个具体的库,配合使用。具体怎么搭配就看自己需求了。

常用的组合有:

实际上光Flask也是能上线运行的,但是它作为HTTP服务器的能力太有限了,所以需要搭配别的模块使用。具体来讲就是:

从这里可以看到,其实这个组合不是必须的,拆掉哪个都能用。只是一个健全的网络App,这样处理是必要的。试想,同一时间只能接受一个人访问的网站,也算网站吗?再想,有客户会在URL网址里输入端口号去访问网站吗?

solomonxie commented 5 years ago

❖ Linux最简单文件服务器汇总 HTTP Server/File Server

参考:willurd/web-servers.md

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.

Discussion on reddit.

Python 2.x

$ python -m SimpleHTTPServer 8000

Python 3.x

$ python -m http.server 8000

Twisted (Python)

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.

Ruby 1.9.2+

Dependencies:

# Ubuntu
$ sudo apt-get install ruby
$ ruby -run -e httpd . -p 8000

Ruby

$ ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => Dir.pwd).start'

Credit: Barking Iguana

Credit: nobu

adsf

$ gem install adsf   # install dependency
$ adsf -p 8000

Credit: twome

No directory listings.

Sinatra (Ruby)

$ gem install sinatra   # install dependency
$ ruby -rsinatra -e'set :public_folder, "."; set :port, 8000'

No directory listings.

Perl

$ 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

Plack (Perl)

$ cpan Plack   # install dependency
$ plackup -MPlack::App::Directory -e 'Plack::App::Directory->new(root=>".");' -p 8000

Credit: miyagawa

Mojolicious (Perl)

$ cpan Mojolicious::Lite   # install dependency
$ perl -MMojolicious::Lite -MCwd -e 'app->static->paths->[0]=getcwd; app->start' daemon -l http://*:8000

No directory listings.

http-server (Node.js)

$ 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 /.

node-static (Node.js)

$ npm install -g node-static   # install dependency
$ static -p 8000

No directory listings.

PHP (>= 5.4)

$ php -S 127.0.0.1:8000

Credit: /u/prawnsalad and MattLicense

No directory listings.

Erlang

$ 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

$ busybox httpd -f -p 8000

Credit: lvm

webfs

$ webfsd -F -p 8000

Depends on webfs.

IIS Express

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.

solomonxie commented 5 years ago

❖ Web Application 网络应用框架理解

一个Webapp Framework框架,不同于Web Server (或HTTP Server)。

Webapp框架的核心作用只有:

Webapp框架的轻重: 轻量级的框架只实现基本的路由和视图,而重量级的框架则在此基础上实现了各种各样丰富的组件和插件。 轻量级的使用简单、自由灵活、轻松扩展,重量级的具备完备的网络应用组件如数据库操作、登录、产品展示等拿来直接用,但是不够灵活。

常用Python框架:

如何理解Webapp框架在网络模型中的意义?

参考:如何理解Nginx、uWSGI和Flask之间的关系?

image

实际上客户访问到具体的网络服务需要走过三大组件:Client -> HTTP Server -> CGI -> Webapp Framework。 其中HTTP Server只处理基础的TCP/UDP等网络请求,得知请求的是那个具体的应用程序后,把具体内容转发给CGI翻译器,然后CGI把网络请求翻译成具体的编程语言,比如Python,然后发给对应的Webapp Framework,具有编程语言特性的Framework把请求处理好了,再回复给CGI,再回复给HTTP server,再给客户。

solomonxie commented 5 years ago

❖ 一篇文章入门Flask

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/

Flask安装

安装很简单:

$ pip install flask

建议在Virtualenv虚拟环境下安装,因为Flask需要一系列的依赖,最好给Flask生成一个专用的生产环境,并生产requirement.txt依赖列表:

# 生产虚拟环境
$ virtualenv ./flask_env
# 启动虚拟环境
$ ./flask_env/bin/active
# 生产依赖列表
$ pip freeze > requirements.txt

Hello World

一个最简单的Flask程序,只需要三步:

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("/")这种方式来做到的。

Flask路由规则

页面显示指定内容:

@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这样的就成功。 image

中断请求:

# ...

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')

Flask 路由分割

如果所有路径的路由都定义在一个文件里,会很难维护,所以必定要把各个路径的路由分拆到不同的文件里。

这种分拆很简单:

正常情况下,在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 )

image

Flask Blueprint 蓝图

我们手动分割路由处理函数,然后分别导入,这样虽然也简单,但是不不好的地方是,主模块外定义的各个处理函数,本身很难看出来处理的是什么路由逻辑。

为此,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 )

#...

Hooks 钩子事件

Flask提供一个完整请求至回应的事件流,其中包括:

以下是钩子的用法:

#...

@app.before_first_request
def handle_before_first_request():
    pass

@app.route('/')
def index():
    pass

@app....
def ...

Flask上下文请求对象 flask.current_app

request.current_app 是Flask特有的一种request请求处理方式,不同于flask.request对象的处理方式,它是能区分多个请求的。

在我们常用的flask.request对象中,会有一个很严重的问题:即它是一个全局变量。也就是说,如果服务器在处理并发请求时使用的是在同一个进程里的多线程,那么不同用户的请求也许会使用同一个flask.request对象!这时候request中的请求信息就会出现混淆!

所以Flask引入了request.current_app这个对象,即它能够根据上下文来区分不同人的请求。 这是怎么做到的呢?其实很简单,它只是把request变为一个局部变量而已。这样一来,每次的request请求,都是各自独立的局部对象。

返回响应信息 flask.make_response

除了我们自己定义返回的信息外,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

Flask中有一个request对象,接收了一切对当前模块的请求数据。 使用的话,直接在@app.route后面的函数中用def index(request)接收来自装饰器的请求对象即可使用。

request参数类型:

image

常用的各种类型操作如下:

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')

会话处理 flask.session

在登录页设置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中。 每次验证再与数据库进行对比。

表单处理 request.form

动态网页必须要的就是Form表单。Flask中有自带的form表单处理方法。不过我们也可以用第三方插件Flask-WTF实现。

这里我们先只讲自带的处理方式。

Flask自带表单处理

假设我们有一个表单模版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的HTTP Server

一般我们在开发调试过程中,可以用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。

solomonxie commented 5 years ago

❖ Jinja2语法学习 (Flask/Django模版引擎)

副标题: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语言基础

注意:Jinja2模版语言,是不区分缩进的,和纯python不同。实际上所有模版语言都不区分缩紧。

常用标记:

示例:

{% if user %}
    {{ user }}
{% else %}
    hello!
    {% for index in indexs %}
        {{ index }} 
{% endfor %}

Jinja2 Filter 过滤器 (即函数)

一个filter过滤器的本质就是一个function函数。使用格式为:变量名 | 函数。 它做到的就是,把变量传给函数,然后再把函数返回值作为这个代码块的值。

如:

<!-- 带参数的 -->
{{变量 | 函数名(*args)}}

<!-- 不带参数可以省略括号 -->
{{变量 | 函数名}}

链式调用(管道式): 和命令行的pipline管道一样,可以一次调用多个函数(过滤器),如:

{{ "hello world" | reverse | upper }}

文本块调用(将中间的所有文字都作为变量内容传入到过滤器中):

{% filter upper %}
    一大堆文字
{% endfilter %}

Jinja2常用内置函数(过滤器)

字符串操作:

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 Macro 宏 (自定义函数)

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 模版引用

Include是我们常用的操作,即定义一个框架模版(父模版),然后一个一个指定性的把子模版引入进来。

框架模版frame.html如下:

{% include 'header.html' %}

{% include 'body.html' %}

{% include 'footer.html' %}

Extend 模版继承

我们可以在一个父模版中定义一个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 %}

Jinja2模版引用Flask路由中的内容

在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过来的信息列表。

solomonxie commented 5 years ago

一篇文章入门Django框架 [DRAFT]

MVC/MVT 框架

MVC是一种编写代码的逻辑上的分割模型,即把代码分为三大部分:M、V、C,每部分明确的负责一个层面。这样的作用是什么?那就是为了日后更方便的代码维护、代码迁移和代码重构等。

参考Wiki:Model–view–controller

传统的MVC(Model-View-Control)模型适用于大多数的桌面App和网络App的程序编写。 即Model模型(负责数据库) -> View视图(负责将数据显示为最终界面) -> Controller控制器(负责将用户请求转化为界面或数据库的改动)。

image

Django的代码结构是典型的MVC框架。只是具体实施上,它把视图View作为一个Router路由来负责不同URL映射到不同的处理方法,然后用Template模版来作为动态显示,所以也叫MVT模型。(其实这么叫非常不准确,所以提到MVT的人不多)

image

注意:网路上对MVC和MVT的说法、图解都各有不一,因为很多人对这些叫法和结构都产生了混淆。所以不能太依赖各种非官方的文章和图片。

了解Django的这种“代码结构”有什么作用? 因为Django是一个以及成熟开发的网络App,我们要做的只是改一改具体业务信息即可。正因为他是已经完整开发的App,所以我们要清楚他开发的结构是什么,才知道哪种需求到哪里去改。

ORM模型

Object-Relations-Mappings,实际上是Object -> Mappings -> Relations这种方向,即:

把业务抽象为程序中的Object对象,然后通过一种Mappings方法,映射为Relational数据库的表格中去。

简而言之,ORM实际上是一种操作数据库的模型,或是一套操作数据库的方法

Django中提供了一套完善的ORM模型,即一个工具包,让你轻松把自己创建的业务对象和数据库里的表格对应起来,随便操作。

安装

建议在虚拟环境中,且为django项目独立创建:

$ pip install django

创建Django项目

$ django-admin startproject ./MyDjangoProject

创建好Django项目后,文件夹中会出现如下目录结构:

注意:Django中,一个项目只是一个Admin管理员,不包括任何的具体实现。真正的业务代码实现,也就是具体的MVC实现,是要在单独的各个模块中的。

Django的模块分割

Django中的一个项目只是代表一个大框架,不同于Flask创建一个项目即一个完整的app。

Django中的一个app只是大框架下的一个子模块。Django中的每个app都具备完全的MVC结构代码。

image

实际工作中,在我们设计好业务模块后,就可以在Django中把模块分开: 一个模块生成一个子目录,即一个app应用。 每个app子文件夹都有同样的一套文件,实现了典型MVC代码分割。

在一个Django项目中,创建一个子app应用的命令为:

$ python ./manage.py startapp MyApp01

创建好子app的MyApp01后,Django的目录中就出现了一个子目录MyApp01,结构如下:

建立好各个模块后,我们需要在项目主配置settings.py中把每个模块进行注册,才能集合为一个项目。

settings.py中注册的方法如下:

image

启动服务器:

$ python ./manage.py runserver IP:端口

然后就会启动一个指定IP的服务器:

image

solomonxie commented 5 years ago

❖ WebApp 网络应用项目建立的基本流程 [DRAFT]

项目立项和定位

需求分析 Requirements Analysis

原型建立 Prototype

开发流程

image

项目架构 Project Architecture

项目架构:

数据库设计 Database Design

模块代码实现和单元测试 Modules Implementation & Unit Test

前端设计 Frontend Design

前后端代码整合 Frontend & Backend Integration

集成测试 Integration Tests

发布 Publish

solomonxie commented 5 years ago

Chrome无法访问某特定网页 但是Safari和其它浏览器能正常访问

实际上这个“疑难杂症”系列的开启,就是源于今天近一个小时解决Chrome无法访问Github.io的问题。 由于现在在国内,所以装了各种Shadowsocks和Chrome切换代理插件,甚至还修改了本地的Hosts文件。所以一旦遇到无法访问网页,真是要伸手到很多地方去开启、关闭、各种调试。不过最终终于在chrome的插件管理里面找到了问题。

尝试方法

由于之前安装了AdBlock插件,删除它 实际上我装了以后一直没有使用这个插件,因为会被很多网站检测到并提示这个做法不道德。所以我就长期将这个插件放在不启动模式。但是没想到即便不启动,它仍然有屏蔽一些网站的功能。导致我无法访问github.io的各种个人主页。

解决方案后记

删除adblock插件后没多久就又无法访问了,所以再想别的问题。不断尝试后发现,只要把所有的url地址都强制改为https://安全链接,就能访问。

solomonxie commented 5 years ago

❖ Chrome开发者插件合集:网站技术栈检测

类别:Web Dev Tools 作用:显示当前网站的技术栈组成

Wappalyzer

Wappalyzer

分类显示清晰好看,速度快(与网页一起加载)。但是长期占用200M以上内存。

image

WhatRuns

WhatRuns

占用内存小,但是需要临时检测,所以要等待出结果。界面好看。

image

SimilarTech Prospecting

SimilarTech Prospecting

临时加载,但是信息非常非常全面。非弹出窗口,而是在网页中加载一个竖条,失去焦点后不会消失,很方便。比较慢,但是信息全,分类方便看。

image

BuiltWith Technology Profiler

BuiltWith Technology Profiler

速度慢,css样式还经常加载不出来。还显示好多不太用得到的东西和连接,把内容弄的很长让你去翻页看。

image

CSSViewer

CSSViewer

鼠标放在哪,就显示哪的css样式。按ESC取消。

image

Lighthouse

Lighthouse

Reports on how the webpage loaded. 用时长,检测单个网页,然后单独弹出一个网页显示报告结果。显示清晰,结果分类详细 展示很容易看。信息很有价值。

image

SimilarWeb

SimilarWeb

超详细检测当前网站的类似网站、被引用情况、SEO信息、用户分类统计等。超详细超有料!

image

YSlow

YSlow - 网页加载检测报告

YSlow analyzes web pages and suggests ways to improve their performance based on a set of rules for high performance web pages. 网页加载检测报告。非常详细,但是报告排版不是很容易看懂。比Lighthouse快

image

solomonxie commented 5 years ago

❖ Chrome开发者插件合集:网页加载优化

Decentraleyes

Decentraleyes

通过加载本地其事先加载好的资源,并拦截其他第三方的资源请求来加快网页加载速度,该插件还能在加快网页加载速度的同时还能减少一些跟踪脚本的跟踪功能以使得你的网络环境更加安全。

参考文章

image

Gooreplacer

Gooreplacer

重定向/屏蔽 URL,修改/删除 headers. gooreplacer 最初为解决国内无法访问 Google 资源(Ajax、API等)导致页面加载速度巨慢而生,新版在此基础上,增加了更多实用功能,可以方便用户屏蔽某些请求,修改 HTTP 请求/响应 的 headers。

中文官方介绍 使用语法指南

image

AdBlock

AdBlock

The most popular Chrome extension, with over 40 million users! Blocks ads all over the web.

ABP 不管是什么网页都会插入 14000 多条元素隐藏规则,所以占用内存很大.

image

uBlock Origin

uBlock Origin

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设置->自定义规则列表): image

uBlock Origin 常用设置

参考:uBlock Origin中文使用手册,告诉你uBlock Origin怎么用!

打开后台设置选项: 对于中文规则:

注意:启用越多的过滤规则就会产生越高的内存占用。 然而,即使再添加,uBlock₀ 的内存占用依然比其他常见的过滤工具要低的多。

Ghostery

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.

image

solomonxie commented 5 years ago

常用网站重量比对

主要是看一些常用网站对内存和CPU的负担

image image

视频类(视频播放页)

solomonxie commented 5 years ago

迷之网页占用资源

MIT OCW

跳转至该网页:MIT OCW Linear Algebra 18.06sc

明明就是一个静态网页,加载时半分钟都是CPU占用100%,加载完毕后300M以上的内存占用。

image

Kami PDF editor for Chrome

image

solomonxie commented 5 years ago

Chrome/Opera等语法检查 Spell checker

语法检查在写邮件和写笔记时,非常好用,问题是他们默认等都是UK English, 但是当你写Realize, analyze时就会被认为是拼写错误。 实际上是UK English 和US English的差别。

要纠正这一点很简单,只要在浏览器设置里面找到Language栏,语言里面加入美式英语即可。

需要刷新网页才能看到效果。

solomonxie commented 5 years ago

❖ FLask 初接触

Flask是基于Python的Web后台服务器框架,相对于Django来讲属于非常灵巧的轻量级框架。目前市面上应用程度很广,值得学习一下。

Installation

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

Deployment

touch hello.py
mkdir static
mkdir templates

Hello World

在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') 
solomonxie commented 5 years ago
solomonxie commented 5 years ago

❖ Scrapy 入门 [DRAFT]

理解Scrapy流程

Spider -> item -> Pipline

其中:

Settings 通用设定

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:

Spider爬虫

爬虫类型

为了适合各种不同的爬取需求,Scrapy提供了对应各种情况的不同爬虫基础类

参考官方文档Scrapy:Spiders

image

其中各自的特点是:

Parse()函数的本质及多重yield

Spider类中的parse()函数,本质上是一个Generator生成器。然后Scrapy引擎会不断的调用这个生成器,把获得的response传送给下一步骤的Pipline.

parse()中很常见多个yield,或一个return,或什么都没有。这代表了你可以根据自己选择,让它不断递归挖掘信息,或一次性返回一个结果,或者什么都不返回,直接输出写入文件。

另外,parse()的返回值类型也不是固定的,目前我们可以返回这两种类型:

我们常见在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。

Item对象

scrapy.Item对象,在爬虫中可有可无。如果你有需求把获得的数据转换为ORM进行数据库保存的话,这个很方便。但是没有,也不会影响爬虫运行。

关于item对象的名字,也是随便起,没有规则。因为最后是在spider类中被手动调用生成实例的,引擎不会参与。

如果需要ORM处理的话,就可以继承scrapy.Item对象。内容极其简单:只要定义几个类属性(字段)即可,连字段类型都不用填。

import scrapy

class MyItem(scrapy.Item):

    username = scrapy.Field()
    description = scrapy.Field()

Pipeline对象

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中定义函数的执行顺序:

image

上面我们看到在pipeline对象中我们写了process_item()函数,实际上还有其它固定名称的函数可以写。 这些固定函数如下:

solomonxie commented 5 years ago

❖ 轻松搭建开源IP代理池之:不需要再造轮子了 [DRAFT]

IPProxyPool

最佳运行环境是Python2。

solomonxie commented 5 years ago

❖ Nginx入门 [DRAFT]

理解Nginx配置

入门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用。

solomonxie commented 5 years ago

Django Middleware [DRAFT]

"The Middlware classes are called twice during the Request/Response Life Cycle. For that reason, the order 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',
]
solomonxie commented 5 years ago

Procfile: Deploy as Code (Continuous Delivery) [DRAFT]

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

Honcho: manage Procfile-based applications

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
solomonxie commented 4 years ago

用Django-wiki搭建维基百科服务器 [DRAFT]

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吧

solomonxie commented 4 years ago

MediaWiki自己搭建维基百科服务器 [DRAFT]

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直接安装所有必备插件

因为各种插件的安装依赖实在太难找了,所以直接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
solomonxie commented 4 years ago

Nginx 部署静态网页

参考: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;
        }
    }
}
solomonxie commented 4 years ago

Nginx 部署文件服务器

如果只为了分享,而不需要另一方写入文件,那么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;
        }
    }
}

image

solomonxie commented 4 years ago

为Nginx网站安装SSL免费证书 (Let's Encrypt)

要安装免费的第三方CA认证的SSL证书,需要:

Refer to: https://letsencrypt.org/getting-started/ Refer to Cerbot for ACME client: https://certbot.eff.org/lets-encrypt

安装Cerbot - ACME协议的客户端

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如果不支持需要的插件,有可能会出现错误。

安装acmesh - 纯bash的ACME协议客户端

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

未完待续..

solomonxie commented 4 years ago

Flask中的Global全局变量:g [DRAFT]

How to use

from 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.

Lose context inside Gevent worker

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.

solomonxie commented 4 years ago

FastAPI First Touch

Hello World

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

How to run:

$ uvicorn main:app --reload

Basic Validation: Request / Response Models

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()

Bigger App: Project Layout with APIRouter

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

Middleware

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()

Handling Post Request Body

import json

@app.post('/endpoint')
def endpoint(body: PostBody):
    payload = body.dict()
    assert json.loads(body.json()) == payload

Directly Using Request Object

@app.post('/endpoint')
def endpoint(request: Request):
    headers = request.headers
    print(headers.get('Content-Type'))

Directly Using Response Object

solomonxie commented 4 years ago

Plex Media Server All-in-one [DRAFT]

Installment Methods:

Docker Setup (PC)

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

Docker Setup for RaspberryPi (ARM)

Image: https://hub.docker.com/r/jaymoulin/plex/

solomonxie commented 4 years ago