baliang

baliang 查看完整档案

北京编辑洛阳理工学院  |  自动化 编辑青松智慧(北京)科技有限公司  |  工程师 编辑填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

baliang 关注了用户 · 2020-08-10

n͛i͛g͛h͛t͛i͛r͛e͛ @nightire

正在更新 Elixir 语言的系列文章:http://www.very-geek.com

关注 2799

baliang 收藏了文章 · 2020-03-18

Python 装饰器执行顺序迷思

探究多个装饰器执行顺序

装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。

疑问

大部分涉及多个装饰器装饰的函数调用顺序时都会说明它们是自上而下的,比如下面这个例子:

def decorator_a(func):
    print 'Get in decorator_a'
    def inner_a(*args, **kwargs):
        print 'Get in inner_a'
        return func(*args, **kwargs)
    return inner_a

def decorator_b(func):
    print 'Get in decorator_b'
    def inner_b(*args, **kwargs):
        print 'Get in inner_b'
        return func(*args, **kwargs)
    return inner_b

@decorator_b
@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

f(1)

上面代码先定义里两个函数: decotator_a, decotator_b, 这两个函数实现的功能是,接收一个函数作为参数然后返回创建的另一个函数,在这个创建的函数里调用接收的函数(文字比代码绕人)。最后定义的函数 f 采用上面定义的 decotator_a, decotator_b 作为装饰函数。在当我们以1为参数调用装饰后的函数 f 后, decotator_a, decotator_b 的顺序是什么呢(这里为了表示函数执行的先后顺序,采用打印输出的方式来查看函数的执行顺序)?

如果不假思索根据自下而上的原则来判断地话,先执行 decorator_a 再执行 decorator_b , 那么会先输出 Get in decotator_a, Get in inner_a 再输出 Get in decotator_b , Get in inner_b 。然而事实并非如此。

实际上运行的结果如下:

Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

函数和函数调用的区别

为什么是先执行 inner_b 再执行 inner_a 呢?为了彻底看清上面的问题,得先分清两个概念:函数和函数调用。上面的例子中 f 称之为函数, f(1) 称之为函数调用,后者是对前者传入参数进行求值的结果。在Python中函数也是一个对象,所以 f 是指代一个函数对象,它的值是函数本身, f(1) 是对函数的调用,它的值是调用的结果,这里的定义下 f(1) 的值2。同样地,拿上面的 decorator_a 函数来说,它返回的是个函数对象 inner_a ,这个函数对象是它内部定义的。在 inner_a 里调用了函数 func ,将 func 的调用结果作为值返回。

装饰器函数在被装饰函数定义好后立即执行

其次得理清的一个问题是,当装饰器装饰一个函数时,究竟发生了什么。现在简化我们的例子,假设是下面这样的:

def decorator_a(func):
    print 'Get in decorator_a'
    def inner_a(*args, **kwargs):
        print 'Get in inner_a'
        return func(*args, **kwargs)
    return inner_a

@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

正如很多介绍装饰器的文章里所说:

@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

# 相当于
def f(x):
    print 'Get in f'
    return x * 2

f = decorator_a(f)

所以,当解释器执行这段代码时, decorator_a 已经调用了,它以函数 f 作为参数, 返回它内部生成的一个函数,所以此后 f 指代的是 decorater_a 里面返回的 inner_a 。所以当以后调用 f 时,实际上相当于调用 inner_a ,传给 f 的参数会传给 inner_a , 在调用 inner_a 时会把接收到的参数传给 inner_a 里的 funcf ,最后返回的是 f 调用的值,所以在最外面看起来就像直接再调用 f 一样。

疑问的解释

当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了 decorator_adecorator_b ,这是会输出对应的 Get in decorator_aGet in decorator_b 。 这时候 f 已经相当于 decorator_b 里的 inner_b 。但因为 f 并没有被调用,所以 inner_b 并没有调用,依次类推 inner_b 内部的 inner_a 也没有调用,所以 Get in inner_aGet in inner_b 也不会被输出。

@decorator_b
@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

然后最后一行当我们对 f 传入参数1进行调用时, inner_b 被调用了,它会先打印 Get in inner_b ,然后在 inner_b 内部调用了 inner_a 所以会再打印 Get in inner_a, 然后再 inner_a 内部调用的原来的 f, 并且将结果作为最终的返回。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。

当我们在上面的例子最后一行 f 的调用去掉,放到repl里演示,也能很自然地看出顺序问题:

➜  test git:(master) ✗ python
Python 2.7.11 (default, Jan 22 2016, 08:29:18)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test13
Get in decorator_a
Get in decorator_b
>>> test13.f(1)
Get in inner_b
Get in inner_a
Get in f
2
>>> test13.f(2)
Get in inner_b
Get in inner_a
Get in f
4
>>>

在实际应用的场景中,当我们采用上面的方式写了两个装饰方法比如先验证有没有登录 @login_required , 再验证权限够不够时 @permision_allowed 时,我们采用下面的顺序来装饰函数:

@login_required
@permision_allowed
def f()
  # Do something
  return

参考资料

  • 我的大脑和好奇心

查看原文

baliang 赞了文章 · 2020-03-18

Python 装饰器执行顺序迷思

探究多个装饰器执行顺序

装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。

疑问

大部分涉及多个装饰器装饰的函数调用顺序时都会说明它们是自上而下的,比如下面这个例子:

def decorator_a(func):
    print 'Get in decorator_a'
    def inner_a(*args, **kwargs):
        print 'Get in inner_a'
        return func(*args, **kwargs)
    return inner_a

def decorator_b(func):
    print 'Get in decorator_b'
    def inner_b(*args, **kwargs):
        print 'Get in inner_b'
        return func(*args, **kwargs)
    return inner_b

@decorator_b
@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

f(1)

上面代码先定义里两个函数: decotator_a, decotator_b, 这两个函数实现的功能是,接收一个函数作为参数然后返回创建的另一个函数,在这个创建的函数里调用接收的函数(文字比代码绕人)。最后定义的函数 f 采用上面定义的 decotator_a, decotator_b 作为装饰函数。在当我们以1为参数调用装饰后的函数 f 后, decotator_a, decotator_b 的顺序是什么呢(这里为了表示函数执行的先后顺序,采用打印输出的方式来查看函数的执行顺序)?

如果不假思索根据自下而上的原则来判断地话,先执行 decorator_a 再执行 decorator_b , 那么会先输出 Get in decotator_a, Get in inner_a 再输出 Get in decotator_b , Get in inner_b 。然而事实并非如此。

实际上运行的结果如下:

Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

函数和函数调用的区别

为什么是先执行 inner_b 再执行 inner_a 呢?为了彻底看清上面的问题,得先分清两个概念:函数和函数调用。上面的例子中 f 称之为函数, f(1) 称之为函数调用,后者是对前者传入参数进行求值的结果。在Python中函数也是一个对象,所以 f 是指代一个函数对象,它的值是函数本身, f(1) 是对函数的调用,它的值是调用的结果,这里的定义下 f(1) 的值2。同样地,拿上面的 decorator_a 函数来说,它返回的是个函数对象 inner_a ,这个函数对象是它内部定义的。在 inner_a 里调用了函数 func ,将 func 的调用结果作为值返回。

装饰器函数在被装饰函数定义好后立即执行

其次得理清的一个问题是,当装饰器装饰一个函数时,究竟发生了什么。现在简化我们的例子,假设是下面这样的:

def decorator_a(func):
    print 'Get in decorator_a'
    def inner_a(*args, **kwargs):
        print 'Get in inner_a'
        return func(*args, **kwargs)
    return inner_a

@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

正如很多介绍装饰器的文章里所说:

@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

# 相当于
def f(x):
    print 'Get in f'
    return x * 2

f = decorator_a(f)

所以,当解释器执行这段代码时, decorator_a 已经调用了,它以函数 f 作为参数, 返回它内部生成的一个函数,所以此后 f 指代的是 decorater_a 里面返回的 inner_a 。所以当以后调用 f 时,实际上相当于调用 inner_a ,传给 f 的参数会传给 inner_a , 在调用 inner_a 时会把接收到的参数传给 inner_a 里的 funcf ,最后返回的是 f 调用的值,所以在最外面看起来就像直接再调用 f 一样。

疑问的解释

当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了 decorator_adecorator_b ,这是会输出对应的 Get in decorator_aGet in decorator_b 。 这时候 f 已经相当于 decorator_b 里的 inner_b 。但因为 f 并没有被调用,所以 inner_b 并没有调用,依次类推 inner_b 内部的 inner_a 也没有调用,所以 Get in inner_aGet in inner_b 也不会被输出。

@decorator_b
@decorator_a
def f(x):
    print 'Get in f'
    return x * 2

然后最后一行当我们对 f 传入参数1进行调用时, inner_b 被调用了,它会先打印 Get in inner_b ,然后在 inner_b 内部调用了 inner_a 所以会再打印 Get in inner_a, 然后再 inner_a 内部调用的原来的 f, 并且将结果作为最终的返回。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。

当我们在上面的例子最后一行 f 的调用去掉,放到repl里演示,也能很自然地看出顺序问题:

➜  test git:(master) ✗ python
Python 2.7.11 (default, Jan 22 2016, 08:29:18)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test13
Get in decorator_a
Get in decorator_b
>>> test13.f(1)
Get in inner_b
Get in inner_a
Get in f
2
>>> test13.f(2)
Get in inner_b
Get in inner_a
Get in f
4
>>>

在实际应用的场景中,当我们采用上面的方式写了两个装饰方法比如先验证有没有登录 @login_required , 再验证权限够不够时 @permision_allowed 时,我们采用下面的顺序来装饰函数:

@login_required
@permision_allowed
def f()
  # Do something
  return

参考资料

  • 我的大脑和好奇心

查看原文

赞 17 收藏 17 评论 11

baliang 赞了文章 · 2020-03-10

XML文档中特殊字符的处理方法

XML中有五个特殊的字符:

& < > " '

当XML的节点和属性值需要包含这些特殊字符时,需要做转义处理,否则XML解析时会报错,一般有两种处理方法:

  • 使用<![CDATA]]>标签进行转义

<password><![CDATA[abc&123]]></password>
  • 使用转义序列

<server password="abc&amp;123"></server>

转义序列对应关系如下:

&&amp;
<&lt;
>&gt;
"&quot;
'&apos;
查看原文

赞 1 收藏 0 评论 0

baliang 赞了文章 · 2019-08-23

Gitlab CI yaml官方配置文件翻译

通过 .gitlab-ci.yml配置任务

git仓库:https://github.com/Fennay/git...

此文档用于描述.gitlab-ci.yml语法,.gitlab-ci.yml文件被用来管理项目的runner 任务。
如果想要快速的了解GitLab CI ,可查看快速引导

.gitlab-ci.yml

从7.12版本开始,GitLab CI使用YAML文件(.gitlab-ci.yml)来管理项目配置。该文件存放于项目仓库的根目录,它定义该项目如何构建。

开始构建之前YAML文件定义了一系列带有约束说明的任务。这些任务都是以任务名开始并且至少要包含script部分:

job1:
  script: "execute-script-for-job1"
  
job2:
  script: "execute-script-for-job2"

上面这个例子就是一个最简单且带有两个独立任务的CI配置,每个任务分别执行不同的命令。

script可以直接执行系统命令(例如:./configure;make;make install)或者是直接执行脚本(test.sh)。

任务是由Runners接管并且由服务器中runner执行。更重要的是,每一个任务的执行过程都是独立运行的。

用下面这个例子来说明YAML语法还有更多复杂的任务:

image: ruby:2.1
services:
  - postgres

before_script:
  - bundle install

after_script:
  - rm secrets

stages:
  - build
  - test
  - deploy

job1:
  stage: build
  script:
    - execute-script-for-job1
  only:
    - master
  tags:
    - docker

下面列出保留字段,这些保留字段不能被定义为job名称:

关键字是否必须描述
image用于docker镜像,查看docker文档
services用于docker服务,查看docker文档
stages定义构建阶段
typesstages 的别名(已废除)
before_script定义在每个job之前运行的命令
after_script定义在每个job之后运行的命令
variable定义构建变量
cache定义一组文件列表,可在后续运行中使用

image和services

这两个关键字允许使用一个自定义的Docker镜像和一系列的服务,并且可以用于整个job周期。详细配置文档请查看a separate document

before_script

before_script用来定义所有job之前运行的命令,包括deploy(部署) jobs,但是在修复artifacts之后。它可以是一个数组或者是多行字符串。

after_script

GitLab 8.7 开始引入,并且要求Gitlab Runner v1.2

after_script用来定义所有job之后运行的命令。它必须是一个数组或者是多行字符串

stages

stages用来定义可以被job调用的stages。stages的规范允许有灵活的多级pipelines。

stages中的元素顺序决定了对应job的执行顺序:

1. 相同stage的job可以平行执行。
2. 下一个stage的job会在前一个stage的job成功后开始执行。

接下仔细看看这个例子,它包含了3个stage:

stages:
 - build
 - test
 - deploy
  1. 首先,所有build的jobs都是并行执行的。
  2. 所有build的jobs执行成功后,test的jobs才会开始并行执行。
  3. 所有test的jobs执行成功,deploy的jobs才会开始并行执行。
  4. 所有的deploy的jobs执行成功,commit才会标记为success
  5. 任何一个前置的jobs失败了,commit会标记为failed并且下一个stages的jobs都不会执行。

这有两个特殊的例子值得一提:

  1. 如果.gitlab-ci.yml中没有定义stages,那么job's stages 会默认定义为 buildtestdeploy
  2. 如果一个job没有指定stage,那么这个任务会分配到test stage。

types

已废除,将会在10.0中移除。用stages替代。

stages同义

variables

GitLab Runner V0.5.0. 开始引入

GItLab CI 允许在.gitlab-ci.yml文件中添加变量,并在job环境中起作用。因为这些配置是存储在git仓库中,所以最好是存储项目的非敏感配置,例如:

variables:
  DATABASE_URL:"postgres://postgres@postgres/my_database"

这些变量可以被后续的命令和脚本使用。服务容器也可以使用YAML中定义的变量,因此我们可以很好的调控服务容器。变量也可以定义成job level

除了用户自定义的变量外,Runner也可以定义它自己的变量。CI_COMMIT_REG_NAME就是一个很好的例子,它的值表示用于构建项目的分支或tag名称。除了在.gitlab-ci.yml中设置变量外,还有可以通过GitLab的界面上设置私有变量。

更多关于variables

cache

Gitlab Runner v0.7.0 开始引入。

cache用来指定需要在job之间缓存的文件或目录。只能使用该项目工作空间内的路径。

从GitLab 9.0开始,pipelines和job就默认开启了缓存

如果cache定义在jobs的作用域之外,那么它就是全局缓存,所有jobs都可以使用该缓存。

缓存binaries.config中的所有文件:

rspec:
  script: test
  cache:
    paths:
    - binaries/
    - .config

缓存git中没有被跟踪的文件:

rspec:
  script: test
  cache:
    untracked: true

缓存binaries下没有被git跟踪的文件:

rspec:
  script: test
  cache:
    untracked: true
    paths:
    - binaries/

job中优先级高于全局的。下面这个rspecjob中将只会缓存binaries/下的文件:

cache:
  paths:
  - my/files

rspec:
  script: test
  cache:
    key: rspec
    paths:
    - binaries/

注意,缓存是在jobs之前进行共享的。如果你不同的jobs缓存不同的文件路径,必须设置不同的cache:key,否则缓存内容将被重写。

缓存只是尽力而为之,所以别期望缓存会一直存在。查看更多详细内容,请查阅GitLab Runner。

缓存key

GitLab Runner v1.0.0 开始引入。

key指令允许我们定义缓存的作用域(亲和性),可以是所有jobs的单个缓存,也可以是每个job,也可以是每个分支或者是任何你认为合适的地方。

它也可以让你很好的调整缓存,允许你设置不同jobs的缓存,甚至是不同分支的缓存。

cache:key可以使用任何的预定义变量

默认key是默认设置的这个项目缓存,因此默认情况下,每个pipelines和jobs中可以共享一切,从GitLab 9.0开始。

配置示例

缓存每个job:

cache:
  key: "$CI_JOB_NAME"
  untracked: true

缓存每个分支:

cache:
  key: "$CI_COMMIT_REF_NAME"
  untracked: true

缓存每个job且每个分支:

cache:
  key: "$CI_JOB_NAME/$CI_COMMIT_REF_NAME"
  untracked: true

缓存每个分支且每个stage:

cache:
  key: "$CI_JOB_STAGE/$CI_COMMIT_REF_NAME"
  untracked: true

如果使用的Windows Batch(windows批处理)来跑脚本需要用%替代$

cache:
  key: "%CI_JOB_STAGE%/%CI_COMMIT_REF_NAME%"
  untracked: true

Jobs

.gitlab-ci.yml允许指定无限量jobs。每个jobs必须有一个唯一的名字,而且不能是上面提到的关键字。job由一列参数来定义jobs的行为。

job_name:
  script:
    - rake spec
    - coverage
  stage: test
  only:
    - master
  except:
    - develop
  tags:
    - ruby
    - postgres
  allow_failure: true
KeywordRequiredDescription
scriptyesRunner执行的命令或脚本
imageno所使用的docker镜像,查阅使用docker镜像
servicesno所使用的docker服务,查阅使用docker镜像
stageno定义job stage(默认:test
typenostage的别名(已弃用)
variablesno定义job级别的变量
onlyno定义一列git分支,并为其创建job
exceptno定义一列git分支,不创建job
tagsno定义一列tags,用来指定选择哪个Runner(同时Runner也要设置tags)
allow_failureno允许job失败。失败的job不影响commit状态
whenno定义何时开始job。可以是on_successon_failurealways或者manual
dependenciesno定义job依赖关系,这样他们就可以互相传递artifacts
cacheno定义应在后续运行之间缓存的文件列表
before_scriptno重写一组在作业前执行的命令
after_scriptno重写一组在作业后执行的命令
environmentno定义此作业完成部署的环境名称
coverageno定义给定作业的代码覆盖率设置

script

script是Runner执行的yaml脚本。举个例子:

job:
  script: "bundle exec rspec"

该参数也可以用数组包含多个命令:

job:
  script:
    - uname -a
    - bundle exec rspec

有时候,script命令需要被单引号或者是双引号包裹起来。举个例子,当命令中包含冒号(:)时,script需要被包在双引号中,这样YAML解析器才可以正确解析为一个字符串而不是一个键值对(key:value)。使用这些特殊字符的时候一定要注意::,{,},[,],,,&,*,#,?,|,-,<,>,=,!

stage

stage允许一组jobs进入不同的stages。jobs在相同的stage时会parallel同时进行。查阅stages更多的用法请查看stages

only and except

only和except是两个参数用分支策略来限制jobs构建:

  1. only定义哪些分支和标签的git项目将会被job执行。
  2. except定义哪些分支和标签的git项目将不会被job执行。

下面是refs策略的使用规则:

  • onlyexcept可同时使用。如果onlyexcept在一个job配置中同时存在,则以only为准,跳过except(从下面示例中得出)。
  • onlyexcept可以使用正则表达式。
  • onlyexcept允许使用特殊的关键字:branchestagstriggers
  • onlyexcept允许使用指定仓库地址但不是forks的仓库(查看示例3)。

在下面这个例子中,job将只会运行以issue-开始的refs(分支),然而except中设置将被跳过。

job:
  # use regexp
  only:
    - /^issue-.*$/
  # use special keyword
  except:
    - branches

在下面这个例子中,job将只会执行有tags的refs,或者通过API触发器明确地请求构建。

job:
  # use special keywords
  only:
    - tags
    - triggers

仓库路径只能用于父级仓库执行jobs,而不是forks:

job:
  only:
    - branches@gitlab-org/gitlab-ce
  except:
    - master@gitlab-org/gitlab-ce

上面这个例子将会为所有的分支执行job,但master分支除外。

Job variables

在job中是可以使用关键字variables来定义job变量。它的运行原理跟global-level是一样的,但是它允许设置特殊的job变量。

当设置了job级别的关键字variables,它会覆盖全局YAML和预定义中的job变量。想要关闭全局变量可以在job中设置一个空数组:

job_name:
  variables: []

Job变量的优先级关系可查看variables文档说明。

tags

tags可以从允许运行此项目的所有Runners中选择特定的Runners来执行jobs。

在注册Runner的过程中,我们可以设置Runner的标签,比如rubypostgresdevelopment

tags可通过tags来指定特殊的Runners来运行jobs:

job:
  tags:
    - ruby
    - postgres

上面这个示例中,需要确保构建此job的Runner必须定义了rubypostgres这两个tags。

allow_failure

allow_failure可以用于当你想设置一个job失败的之后并不影响后续的CI组件的时候。失败的jobs不会影响到commit状态。

当开启了允许job失败,所有的intents和purposes里的pipeline都是成功/绿色,但是也会有一个"CI build passed with warnings"信息显示在merge request或commit或job page。这被允许失败的作业使用,但是如果失败表示其他地方应采取其他(手动)步骤。

下面的这个例子中,job1job2将会并列进行,如果job1失败了,它也不会影响进行中的下一个stage,因为这里有设置了allow_failure: true

job1:
  stage: test
  script:
  - execute_script_that_will_fail
  allow_failure: true

job2:
  stage: test
  script:
  - execute_script_that_will_succeed

job3:
  stage: deploy
  script:
  - deploy_to_staging

when

when is used to implement jobs that are run in case of failure or despite the failure.

when可以设置以下值:

  1. on_success - 只有前面stages的所有工作成功时才执行。 这是默认值。
  2. on_failure - 当前面stages中任意一个jobs失败后执行。
  3. always - 无论前面stages中jobs状态如何都执行。
  4. `manual ` - 手动执行(GitLab8.10增加)。更多请查看手动操作

举个例子:

stages:
- build
- cleanup_build
- test
- deploy
- cleanup

build_job:
  stage: build
  script:
  - make build

cleanup_build_job:
  stage: cleanup_build
  script:
  - cleanup build when failed
  when: on_failure

test_job:
  stage: test
  script:
  - make test

deploy_job:
  stage: deploy
  script:
  - make deploy
  when: manual

cleanup_job:
  stage: cleanup
  script:
  - cleanup after jobs
  when: always

脚本说明:

  1. 只有当build_job失败的时候才会执行`cleanup_build_job
  2. 不管前一个job执行失败还是成功都会执行`cleanup_job
  3. 可以从GitLab界面中手动执行deploy_jobs

Manual actions

GitLab 8.10 开始引入手动执行。GitLab 9.0 开始引入手动停止。GitLab 9.2 开始引入保护手动操作。

手动操作指令是不自动执行的特殊类型的job;它们必须要人为启动。手动操作指令可以从pipeline,build,environment和deployment视图中启动。

部署到生产环境是手动操作指令的一个很好示例。

了解更多请查看environments documentation

手动操作指令可以是可选的或阻塞。在定义了手动执行的那个stage中,手动操作指令将会停止pipline中的自动执行指令。当有人通过点击play按钮来执行需要手动执行的job时,可以来恢复pipeline的执行。

当pipeline被阻塞时,即使是pipeline是成功状态也不会merge。被阻塞的pipelines也有一个特殊的状态,叫manual

手动操作指令默认是不阻塞的。如果你想要手动操作指令产生阻塞,首先需要在job的配置文件.gitlab-ci.yml中添加allow_failure:false

可选的手动操作指令默认设置allow_failure:true

可选动作的状态不影响整个pipeline的状态。

手动操作指令被认为是写操作,所以当前用户触发操作时,必须拥有操作保护分支的权限。换句话说,为了触发一个手动操作指令到pipeline中正在运行的指定分支,当前用户必须拥有推送到这分支的权限。

enviroment

注意:

environment用于定义job部署到特殊的环境中。如果指定了environment,并且没有该名称下的环境,则会自动创建新环境。

在最简单的格式中,环境关键字可以定义为:

deploy to production:
  stage: deploy
  script: git push production HEAD:master
  environment:
    name: production

在上面这个例子中,deploy to profuctionjob将会执行部署到production环境的操作。

environment:name

注意

  • GitLab 8.11 开始引入。
  • 在GitLab8.11之前,环境名称定义为environment:production。现在推荐的做法是定义为name关键字。

environment名称可以包含:

  • 英文字母(letters)
  • 数字(digits)
  • 空格(spaces)
  • -
  • _
  • /
  • $
  • {
  • }

常用的名称有qa,staging,和production,当然你可以在你的工作流中使用任意名字。

除了在environment关键字右边紧跟name定义方法外,也是可以为环境名称单独设定一个值。例如,用name关键字在environment下面设置:

deploy to production:
  stage: deploy
  script: git push production HEAD:master
  environment:
    name: production

environment:url

注意:

  • GitLab 8.11 开始引用。
  • 在GitLab 8.11之前,URL只能在GitLab's UI中添加。现在推荐的定义方法是在.gitlab-ci.yml

这是设置一个可选值,它会显示在按钮中,点击它可以带你到设置的URL页面。

在下面这个例子中,如果job都成功完成了,在environment/deployments页面中将会创建一个合并请求的按钮,它将指向https://prod.example.com

deploy to production:
  stage: deploy
  script: git push production HEAD:master
  environment:
    name: production
    url: https://prod.example.com

environment:on_stop

注意:

  • GitLab 8.13中开始引入
  • 从GitLab 8.14开始,当在environment中定义了一个stop操作,GitLab将会在相关联的分支本删除时自动触发一个stop操作。

关闭(停止)environments可以通过在environment下定义关键字on_stop来实现。它定义了一个不同的job,用于关闭environment。

请查看environment:action模块中例子。

environment:action

Gitlab 8.13 开始引入

actionon_stop联合使用,定义在job中,用来关闭environment。

举个例子:

review_app:
  stage: deploy
  script: make deploy-app
  environment:
    name: review
    on_stop: stop_review_app

stop_review_app:
  stage: deploy
  script: make delete-app
  when: manual
  environment:
    name: review
    action: stop

上面这个例子中,我们定义了review_appjob来部署到review环境中,同时我们也定义了一个新stop_review_appjob在on_stop中。一旦review_appjob执行完成并且成功,它将触发定义在when中的stop_review_appjob。在这种情况下,我们设置为manual,需要通过GitLab's web界面来允许manual action

stop_review_appjob需要定义下面这些关键字:

  • when - 说明
  • environment:name
  • environment:action
  • stage需要和review_app相同,以便分支删除被删除的时候自动执行停止。

dynamic environment

注意:

  • GitLab 8.12开始引入,并且要求GitLab Runner 1.6 。
  • GitLab 8.15开始引入$CI_ENVIRONMENT_SLUG

environment也可以是代表配置项,其中包含nameurl。这些参数可以使用任何的CI variables(包括预定义、安全变量和.gitlab-ci.yml中的变量)。

举个例子:

deploy as review app:
  stage: deploy
  script: make deploy
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: https://$CI_ENVIRONMENT_SLUG.example.com/

$CI_COMMIT_REF_NAME 被Runner设置为environment variable时,deply as review appjob将会被指定部署到动态创建revuew/$CI_COMMIT_REF_NAME的环境中。$CI_ENVIRONMENT_SLUG变量是基于环境名称的,最终组合成完整的URLs。在这种情况下,如果deploy as review appjob是运行在名称为pow的分支下,那么可以通过URLhttps"//review-pw.example.com/来访问这个环境。

这当然意味着托管应用程序的底层服务器已经正确配置。

常见的做法是为分支创建动态环境,并讲它们作为Review Apps。可以通过https://gitlab.com/gitlab-exa... Apps的简单示例。

artifacts

注意:

  • 非Windows平台从GitLab Runner v0.7.0中引入。
  • Windows平台从GitLab Runner V1.0.0中引入。
  • 在GItLab 9.2之前,在artifacts之后存储缓存。
  • 在GItLab 9.2之后,在artifacts之前存储缓存。
  • 目前并不是所有的executors都支持。
  • 默认情况下,job artifacts 只正对成功的jobs收集。

artifacts用于指定成功后应附加到job的文件和目录的列表。只能使用项目工作间内的文件或目录路径。如果想要在不通的job之间传递artifacts,请查阅依赖关系。以下是一些例子:

发送binaries.config中的所有文件:

artifacts:
  paths:
  - binaries/
  - .config

发送所有没有被Git跟踪的文件:

artifacts:
  untracked: true

发送没有被Git跟踪和binaries中的所有文件:

artifacts:
  untracked: true
  paths:
  - binaries/

定义一个空的dependencies可以禁用artifact传递:

job:
  stage: build
  script: make build
  dependencies: []

有时候只需要为标签为releases创建artifacts,以避免将临时构建的artifacts传递到生产服务器中。

只为tags创建artifacts(default-job将不会创建artifacts):

default-job:
  script:
    - mvn test -U
  except:
    - tags

release-job:
  script:
    - mvn package -U
  artifacts:
    paths:
    - target/*.war
  only:
    - tags

在job成功完成后artifacts将会发送到GitLab中,同时也会在GitLab UI中提供下载。

artifacts:name

GitLab 8.6 和 Runner v1.1.0 引入。

name允许定义创建的artifacts存档的名称。这样一来,我们可以为每个存档提供一个唯一的名称,当需要从GitLab中下载是才不会混乱。artifacts:name可以使用任何的预定义变量(predefined variables)。它的默认名称为artifacts,当下载是就变成了artifacts.zip


配置示例

通过使用当前job的名字作为存档名称:

job:
  artifacts:
    name: "$CI_JOB_NAME"

使用当前分支名称或者是tag作为存到名称,只存档没有被Git跟踪的文件:

job:
   artifacts:
     name: "$CI_COMMIT_REF_NAME"
     untracked: true

使用当前job名称和当前分支名称或者是tag作为存档名称,只存档没有被Git跟踪的文件:

job:
  artifacts:
    name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
    untracked: true

使用当前stage和分支名称作为存档名称:

job:
  artifacts:
    name: "${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}"
    untracked: true

如果是使用Windows批处理来运行yaml脚本,需要用%替换$

job:
  artifacts:
    name: "%CI_JOB_STAGE%_%CI_COMMIT_REF_NAME%"
    untracked: true

artifacts:when

GitLab 8.9和GitLab Runner v1.3.0 引入。

在job失败的时候,artifacts:when用来上传artifacts或者忽略失败。

artifacts:when可以设置一下值:

  1. on_success - 当job成功的时候上传artifacts。默认值。
  2. on_failure - 当job失败的时候上传artifacts。
  3. always - 不论job失败还是成功都上传artifacts。

示例配置

只在job失败的时候上传artifacts。

job:
  artifacts:
    when: on_failure

artifacts:expire_in

GitLab 8.9 和 GitLab Runner v1.3.0 引入。

artifacts:expire_in用于过期后删除邮件上传的artifacts。默认情况下,artifacts都是在GitLab中永久保存。expire_in允许设置设置artifacts的存储时间,从它们被上传存储到GitLab开始计算。

可以通过job页面的Keep来修改有效期。

过期后,artifacts会被通过一个默认每小时执行一次的定时job删除,所以在过期后无法访问artifacts。

expire_in是一个时间区间。下面可设置的值:

  • '3 mins 4 sec'
  • '2 hrs 20 min'
  • '2h20min'
  • '6 mos 1 day'
  • '47 yrs 6 mos and 4d'
  • '3 weeks and 2 days'

示例配置

设置artifacts的有效期为一个星期:

job:
  artifacts:
    expire_in: 1 week

dependencies

GitLab 8.6 和 GitLab RUnner v1.1.1引入。

这个功能应该与artifacts一起使用,并允许定义在不同jobs之间传递artifacts。

注意:所有之前的stages都是默认设置通过。

如果要使用此功能,应该在上下文的job中定义dependencies,并且列出之前都已经通过的jobs和可下载的artifacts。你只能在当前执行的stages前定义jobs。你如果在当前stages或者后续的stages中定义了jobs,它将会报错。可以通过定义一个空数组是当前job跳过下载artifacts。


在接下来的例子中,我们定义两个带artifacts的jobs,build:osxbuild:linux。当test:osx开始执行的时候,build:osx的artifacts就会开始下载并且会在build的stages下执行。同样的会发生在test:linux,从build:linux中下载artifacts。

因为stages的优先级关系,deployjob将会下载之前jobs的所有artifacts:

build:osx:
  stage: build
  script: make build:osx
  artifacts:
    paths:
    - binaries/

build:linux:
  stage: build
  script: make build:linux
  artifacts:
    paths:
    - binaries/

test:osx:
  stage: test
  script: make test:osx
  dependencies:
  - build:osx

test:linux:
  stage: test
  script: make test:linux
  dependencies:
  - build:linux

deploy:
  stage: deploy
  script: make deploy

before_script 和 after_script

它可能会覆盖全局定义的before_scriptafter_script

before_script:
- global before script

job:
  before_script:
  - execute this instead of global before script
  script:
  - my command
  after_script:
  - execute this after my script

coverage

注意:

GitLab 8.17 引入

coverage允许你配置代码覆盖率将会从该job中提取输出。

在这里正则表达式是唯一有效的值。因此,字符串的前后必须使用/包含来表明一个正确的正则表达式规则。特殊字符串需要转义。

一个简单的例子:

job1:
  coverage: '/Code coverage: \d+\.\d+/'

Git Strategy

GitLab 8.9中以试验性功能引入。将来的版本中可能改变或完全移除。GIT_STRATEGY要求GitLab Runner v1.7+。

你可以通过设置GIT_STRATEGY用于获取最新的代码,可以再全局variables或者是在单个job的variables模块中设置。如果没有设置,将从项目中使用默认值。

可以设置的值有:clonefetch,和none

clone是最慢的选项。它会从头开始克隆整个仓库,包含每一个job,以确保项目工作区是最原始的。

variables:
  GIT_STRATEGY: clone

当它重新使用项目工作区是,fetch是更快(如果不存在则返回克隆)。git clean用于撤销上一个job做的任何改变,git fetch用于获取上一个job到现在的的commit。

variables:
  GIT_STRATEGY: fetch

none也是重新使用项目工作区,但是它会跳过所有的Git操作(包括GitLab Runner前的克隆脚本,如果存在的话)。它主要用在操作job的artifacts(例如:deploy)。Git数据仓库肯定是存在的,但是他肯定不是最新的,所以你只能依赖于从项目工作区的缓存或者是artifacts带来的文件。

variables:
  GIT_STRATEGY: none

Git Checout

GitLab Runner 9.3 引入。

GIT_STRATEGY设置为clonefetch时,可以使用GIT_CHECKOUT变量来指定是否应该运行git checkout。如果没有指定,它默认为true。就像GIT_STRATEGY一样,它可以设置在全局variables或者是单个job的variables中设置。

如果设置为false,Runner就会:

  • fetch - 更新仓库并在当前版本中保留工作副本,
  • clone - 克隆仓库并在默认分支中保留工作副本。

Having this setting set to true will mean that for both clone and fetch strategies the Runner will checkout the working copy to a revision related to the CI pipeline:

【如果设置这个为true将意味着clonefetch策略都会让Runner执行项目工作区更新到最新:】

variables:
  GIT_STRATEGY: clone
  GIT_CHECKOUT: false
script:
  - git checkout master
  - git merge $CI_BUILD_REF_NAME

Git Submodule Strategy

需要GitLab Runner v1.10+。

GIT_SUBMODULE_STRATEGY变量用于在构建之前拉取代码时,Git子模块是否或者如何被引入。就像GIT_STRATEGY一样,它可在全局variables或者是单个job的variables模块中设置。

它的可用值有:nonenormalrecursive

  • none意味着在拉取项目代码时,子模块将不会被引入。这个是默认值,与v1.10之前相同的。
  • normal意味着在只有顶级子模块会被引入。它相当于:
git submodule sync
git submodule update --init

recursive意味着所有的子模块(包括子模块的子模块)都会被引入,他相当于:

git submodule sync --recursive
git submodule update --init --recursive

注意:如果想要此功能正常工作,子模块必须配置(在.gitmodules)下面中任意一个:

  • 可访问的公共仓库http(s)地址,
  • 在同一个GitLab服务器上有一个可访问到另外的仓库的真实地址。更多查看Git 子模块文档

Job stages attempts

GitLab引入,要求GItLab Runner v1.9+。

正在执行的job将会按照你设置尝试次数依次执行下面的stages:

变量描述
GET_SOURCES_ATTEMPTS获取job源的尝试次数
ARTIFACT_DOWNLOAD_ATTEMPTS下载artifacts的尝试次数
RESTORE_CACHE_ATTEMPTS重建缓存的尝试次数

默认是一次尝试。

例如:

variables:
  GET_SOURCES_ATTEMPTS: 3

你可以在全局variables模块中设置,也可以在单个job的variables模块中设置。

Shallow cloning

GitLab 8.9 以实验性功能引入。在将来的版本中有可能改变或者完全移除。

你可以通过GIT_DEPTH来指定抓取或克隆的深度。它可浅层的克隆仓库,这可以显著加速具有大量提交和旧的大型二进制文件的仓库的克隆。这个设置的值会传递给git fetchgit clone

注意:如果设置depth=1,并且有一个jobs队列或者是重试jobs,则jobs可能会失败。

由于Git抓取和克隆是基于一个REF,例如分支的名称,所以Runner不能指定克隆一个commit SHA。如果队列中有多个jobs,或者您正在重试旧的job,则需要测试的提交应该在克隆的Git历史记录中存在。设置GIT_DEPTH太小的值可能会导致无法运行哪些旧的commits。在job日志中可以查看unresolved reference。你应该考虑设置GIT_DEPTH为一个更大的值。

GIT_DEPTH只设置了部分存在的记录时,哪些依赖于git describe的jobs也许不能正确的工作。

只抓取或克隆最后的3次commits:

variables:
  GIT_DEPTH: "3"

Hidden keys

GitLab 8.6 和 GitLab Runner v1.1.1引入。

Key 是以.开始的,GitLab CI 将不会处理它。你可以使用这个功能来忽略jobs,或者用Special YAML features 转换隐藏键为模版。

在下面这个例子中,.key_name将会被忽略:

.key_name:
  script:
    - rake spec

Hidden keys 可以是像普通CI jobs一样的哈希值,但你也可以利用special YAMLfeatures来使用不同类型的结构。

Special YAML features

使用special YAML features 像anchors(&),aliases(*)和map merging(<<),这将使您可以大大降低.gitlab-ci.yml的复杂性。

查看更多YAML features

Anchors

GitLab 8.6 和 GitLab Runner v1.1.1引入。

YAML有个方便的功能称为"锚",它可以让你轻松的在文档中复制内容。Anchors可用于复制/继承属性,并且是使用hidden keys来提供模版的完美示例。

下面这个例子使用了anchors和map merging。它将会创建两个jobs,test1test2,该jobs将集成.job_template的参数,每个job都自定义脚本:

.job_template: &job_definition  # Hidden key that defines an anchor named 'job_definition'
  image: ruby:2.1
  services:
    - postgres
    - redis

test1:
  <<: *job_definition           # Merge the contents of the 'job_definition' alias
  script:
    - test1 project

test2:
  <<: *job_definition           # Merge the contents of the 'job_definition' alias
  script:
    - test2 project

&在anchor的名称(job_definition)前设置,<<表示"merge the given hash into the current one",*包括命名的anchor(job_definition)。扩展版本如下:

.job_template:
  image: ruby:2.1
  services:
    - postgres
    - redis

test1:
  image: ruby:2.1
  services:
    - postgres
    - redis
  script:
    - test1 project

test2:
  image: ruby:2.1
  services:
    - postgres
    - redis
  script:
    - test2 project

让我们来看另外一个例子。这一次我们将用anchors来定义两个服务。两个服务会创建两个job,test:postgrestest:mysql,他们会在.job_template中共享定义的script指令,以及分别在.postgres_services.mysql_services中定义的service指令:

.job_template: &job_definition
  script:
    - test project

.postgres_services:
  services: &postgres_definition
    - postgres
    - ruby

.mysql_services:
  services: &mysql_definition
    - mysql
    - ruby

test:postgres:
  <<: *job_definition
  services: *postgres_definition

test:mysql:
  <<: *job_definition
  services: *mysql_definition

扩展版本如下:

.job_template:
  script:
    - test project

.postgres_services:
  services:
    - postgres
    - ruby

.mysql_services:
  services:
    - mysql
    - ruby

test:postgres:
  script:
    - test project
  services:
    - postgres
    - ruby

test:mysql:
  script:
    - test project
  services:
    - mysql
    - ruby

你可以看到hidden keys被方便的用作模版。

Triggers

Triggers 可用于强制使用API调用重建特定分支,tag或commits。

在triggers文档中查看更多。

pages

pages是一个特殊的job,用于将静态的内容上传到GitLab,可用于为您的网站提供服务。它有特殊的语法,因此必须满足以下两个要求:

  1. 任何静态内容必须放在public/目录下
  2. artifacts必须定义在public/目录下

下面的这个例子是将所有文件从项目根目录移动到public/目录。.public工作流是cp,并且它不会循环复制public/本身。

pages:
  stage: deploy
  script:
  - mkdir .public
  - cp -r * .public
  - mv .public public
  artifacts:
    paths:
    - public
  only:
  - master

更多内容请查看GitLab Pages用户文档

Validate the .gitlab-ci.yml

GitLab CI的每个实例都有一个名为Lint的嵌入式调试工具。 你可以在gitlab实例的/ci/lint下找到该链接。

Skipping jobs

如果你的commit信息中包含[ci skip]或者[skip ci],不论大小写,那么这个commit将会创建但是jobs也会跳过。

Examples

访问examples README来查看各种语言的GitLab CI用法示例。

官方文档地址:https://docs.gitlab.com/ce/ci...

查看原文

赞 70 收藏 54 评论 0

baliang 收藏了文章 · 2019-08-02

搞懂nginx的rewrite模块

之前在配置nginx时,总是遇到rewrite指令的last和break标识的问题,看到的资料大都是last 基本上都用这个 Flag,break 中止 Rewirte,不在继续匹配。看完之后还是有点懵,后来看了下rewrite模块的文档,终于搞懂了,这个模块内容也不是太多,索性整个把这个模块都好好整理下吧

ngx_http_rewrite_module 模块用来使用正则表达式(PCRE)改变请求的URI,返回重定向,并有条件地选择配置。

指令执行顺序

  1. 首先顺序执行server块中的rewrite模块指令,得到rewrite后的请求URI
  2. 然后循环执行如下指令

    > 如果没有遇到中断循环标志,此循环最多执行10次,但是我们可以使用break指令来中断rewrite后的新一轮的循环
    

(1). 依据rewrite后的请求URI,匹配定义的 location 块

(2). 顺序执行匹配到的 location 中的rewrite模块指令

指令

break

Context: server, location, if

停止执行 ngx_http_rewrite_module 的指令集,但是其他模块指令是不受影响的
例子说明

server {
    listen 8080;
    # 此处 break 会停止执行 server 块的 return 指令(return 指令属于rewrite模块)
    # 如果把它注释掉 则所有请求进来都返回 ok
    break;
    return 200 "ok";
    location = /testbreak {
        break;
        return 200 $request_uri;
        proxy_pass http://127.0.0.1:8080/other;
    }
    location / {
        return 200 $request_uri;
    }
}

# 发送请求如下
# curl 127.0.0.1:8080/testbreak
# /other

# 可以看到 返回 `/other` 而不是 `/testbreak`,说明 `proxy_pass` 指令还是被执行了
# 也就是说 其他模块的指令是不会被 break 中断执行的
# (proxy_pass是ngx_http_proxy_module的指令)

if

Context: server, location

依据指定的条件决定是否执行 if 块语句中的内容

if 中的几种 判断条件

  1. 一个变量名,如果变量 $variable 的值为空字符串或者字符串"0",则为false
  2. 变量与一个字符串的比较 相等为(=) 不相等为(!=) 注意此处不要把相等当做赋值语句啊
  3. 变量与一个正则表达式的模式匹配 操作符可以是(~ 区分大小写的正则匹配, ~*不区分大小写的正则匹配, !~!~*,前面两者的非)
  4. 检测文件是否存在 使用 -f(存在) 和 !-f(不存在)
  5. 检测路径是否存在 使用 -d(存在) 和 !-d(不存在) 后面判断可以是字符串也可是变量
  6. 检测文件、路径、或者链接文件是否存在 使用 -e(存在) 和 !-e(不存在) 后面判断可以是字符串也可是变量
  7. 检测文件是否为可执行文件 使用 -x(可执行) 和 !-x(不可执行) 后面判断可以是字符串也可是变量

注意 上面 第1,2,3条被判断的必须是 变量, 4, 5, 6, 7则可以是变量也可是字符串

set $variable "0"; 
if ($variable) {
    # 不会执行,因为 "0" 为 false
    break;            
}

# 使用变量与正则表达式匹配 没有问题
if ( $http_host ~ "^star\.igrow\.cn$" ) {
    break;            
}

# 字符串与正则表达式匹配 报错
if ( "star" ~ "^star\.igrow\.cn$" ) {
    break;            
}
# 检查文件类的 字符串与变量均可
if ( !-f "/data.log" ) {
    break;            
}

if ( !-f $filename ) {
    break;            
}

return

Context: server, location, if

return code [text];
return code URL;
return URL;

停止处理并将指定的code码返回给客户端。 非标准code码 444 关闭连接而不发送响应报头。

0.8.42版本开始, return 语句可以指定重定向 url (状态码可以为如下几种 301,302,303,307),
也可以为其他状态码指定响应的文本内容,并且重定向的url和响应的文本可以包含变量

有一种特殊情况,就是重定向的url可以指定为此服务器本地的urI,这样的话,nginx会依据请求的协议$schemeserver_name_in_redirectport_in_redirect自动生成完整的 url (此处要说明的是server_name_in_redirectport_in_redirect 指令是表示是否将server块中的 server_namelisten 的端口 作为redirect用 )

# return code [text]; 返回 ok 给客户端
location = /ok {
    return 200 "ok";
}

# return code URL; 临时重定向到 百度
location = /redirect {
    return 302 http://www.baidu.com;
}

# return URL; 和上面一样 默认也是临时重定向
location = /redirect {
    return http://www.baidu.com;
}

rewrite

Context: server, location, if

rewrite regex replacement [flag];

rewrite 指令是使用指定的正则表达式regex来匹配请求的urI,如果匹配成功,则使用replacement更改URIrewrite指令按照它们在配置文件中出现的顺序执行。可以使用flag标志来终止指令的进一步处理。如果替换字符串replacementhttp://https://$ scheme开头,则停止处理后续内容,并直接重定向返回给客户端。

第一种情况 重写的字符串 带http://

location / {
    # 当匹配 正则表达式 /test1/(.*)时 请求将被临时重定向到 http://www.$1.com
    # 相当于 flag 写为 redirect
    rewrite /test1/(.*) http://www.$1.com;
    return 200 "ok";
}
# 在浏览器中输入 127.0.0.1:8080/test1/baidu 
# 则临时重定向到 www.baidu.com
# 后面的 return 指令将没有机会执行了

第二种情况 重写的字符串 不带http://

location / {
    rewrite /test1/(.*) www.$1.com;
    return 200 "ok";
}
# 发送请求如下
# curl 127.0.0.1:8080/test1/baidu
# ok

# 此处没有带http:// 所以只是简单的重写。请求的 uri 由 /test1/baidu 重写为 www.baidu.com
# 因为会顺序执行 rewrite 指令 所以 下一步执行 return 指令 响应了 ok 

rewrite 的四个 flag

  1. last
    停止处理当前的ngx_http_rewrite_module的指令集,并开始搜索与更改后的URI相匹配的location;
  2. break
    停止处理当前的ngx_http_rewrite_module指令集,就像上面说的break指令一样;
  3. redirect
    返回302临时重定向。
  4. permanent
    返回301永久重定向。
# 没有rewrite 后面没有任何 flag 时就顺序执行 
# 当 location 中没有 rewrite 模块指令可被执行时 就重写发起新一轮location匹配
location / {
    # 顺序执行如下两条rewrite指令 
    rewrite ^/test1 /test2;
    rewrite ^/test2 /test3;  # 此处发起新一轮location匹配 uri为/test3
}

location = /test2 {
    return 200 "/test2";
}  

location = /test3 {
    return 200 "/test3";
}
# 发送如下请求
# curl 127.0.0.1:8080/test1
# /test3
last 与 break 的区别

last 和 break一样 它们都会终止此 location 中其他它rewrite模块指令的执行,
但是 last 立即发起新一轮的 location 匹配 而 break 则不会

location / {
    rewrite ^/test1 /test2;
    rewrite ^/test2 /test3 last;  # 此处发起新一轮location匹配 uri为/test3
    rewrite ^/test3 /test4;
    proxy_pass http://www.baidu.com;
}

location = /test2 {
    return 200 "/test2";
}  

location = /test3 {
    return 200 "/test3";
}
location = /test4 {
    return 200 "/test4";
}
# 发送如下请求
# curl 127.0.0.1:8080/test1
# /test3 

当如果将上面的 location / 改成如下代码
location / {
    rewrite ^/test1 /test2;
    # 此处 不会 发起新一轮location匹配;当是会终止执行后续rewrite模块指令 重写后的uri为 /more/index.html
    rewrite ^/test2 /more/index.html break;  
    rewrite /more/index\.html /test4; # 这条指令会被忽略

    # 因为 proxy_pass 不是rewrite模块的指令 所以它不会被 break终止
    proxy_pass https://www.baidu.com;
}
# 发送如下请求
# 浏览器输入 127.0.0.1:8080/test1 
# 代理到 百度产品大全页面 https://www.baidu.com/more/index.html;
友情提醒下

此处提一下 在上面的代码中即使将 proxy_pass 放在 带有 breakrewrite上面它也是会执行的,这就要扯到nginx的执行流程了。大家有兴趣可以了解下。

rewrite 后的请求参数

如果替换字符串replacement包含新的请求参数,则在它们之后附加先前的请求参数。如果你不想要之前的参数,则在替换字符串 replacement 的末尾放置一个问号,避免附加它们。

# 由于最后加了个 ?,原来的请求参数将不会被追加到rewrite之后的url后面 
rewrite ^/users/(.*)$ /show?user=$1? last;

rewrite_log

Context: http, server, location, if

开启或者关闭 rewrite模块指令执行的日志,如果开启,则重写将记录下notice 等级的日志到nginxerror_log 中,默认为关闭 off

Syntax:    rewrite_log on | off;

set

Context: server, location, if

设置指定变量的值。变量的值可以包含文本,变量或者是它们的组合形式。

location / {
    set $var1 "host is ";
    set $var2 $host;
    set $var3 " uri is $request_uri";
    return 200 "response ok $var1$var2$var3";
}
# 发送如下请求
# curl 127.0.0.1:8080/test
# response ok host is 127.0.0.1 uri is /test

uninitialized_variable_warn

Context: http, server, location, if

控制是否记录 有关未初始化变量的警告。默认开启

查看原文

baliang 赞了文章 · 2019-08-02

搞懂nginx的rewrite模块

之前在配置nginx时,总是遇到rewrite指令的last和break标识的问题,看到的资料大都是last 基本上都用这个 Flag,break 中止 Rewirte,不在继续匹配。看完之后还是有点懵,后来看了下rewrite模块的文档,终于搞懂了,这个模块内容也不是太多,索性整个把这个模块都好好整理下吧

ngx_http_rewrite_module 模块用来使用正则表达式(PCRE)改变请求的URI,返回重定向,并有条件地选择配置。

指令执行顺序

  1. 首先顺序执行server块中的rewrite模块指令,得到rewrite后的请求URI
  2. 然后循环执行如下指令

    > 如果没有遇到中断循环标志,此循环最多执行10次,但是我们可以使用break指令来中断rewrite后的新一轮的循环
    

(1). 依据rewrite后的请求URI,匹配定义的 location 块

(2). 顺序执行匹配到的 location 中的rewrite模块指令

指令

break

Context: server, location, if

停止执行 ngx_http_rewrite_module 的指令集,但是其他模块指令是不受影响的
例子说明

server {
    listen 8080;
    # 此处 break 会停止执行 server 块的 return 指令(return 指令属于rewrite模块)
    # 如果把它注释掉 则所有请求进来都返回 ok
    break;
    return 200 "ok";
    location = /testbreak {
        break;
        return 200 $request_uri;
        proxy_pass http://127.0.0.1:8080/other;
    }
    location / {
        return 200 $request_uri;
    }
}

# 发送请求如下
# curl 127.0.0.1:8080/testbreak
# /other

# 可以看到 返回 `/other` 而不是 `/testbreak`,说明 `proxy_pass` 指令还是被执行了
# 也就是说 其他模块的指令是不会被 break 中断执行的
# (proxy_pass是ngx_http_proxy_module的指令)

if

Context: server, location

依据指定的条件决定是否执行 if 块语句中的内容

if 中的几种 判断条件

  1. 一个变量名,如果变量 $variable 的值为空字符串或者字符串"0",则为false
  2. 变量与一个字符串的比较 相等为(=) 不相等为(!=) 注意此处不要把相等当做赋值语句啊
  3. 变量与一个正则表达式的模式匹配 操作符可以是(~ 区分大小写的正则匹配, ~*不区分大小写的正则匹配, !~!~*,前面两者的非)
  4. 检测文件是否存在 使用 -f(存在) 和 !-f(不存在)
  5. 检测路径是否存在 使用 -d(存在) 和 !-d(不存在) 后面判断可以是字符串也可是变量
  6. 检测文件、路径、或者链接文件是否存在 使用 -e(存在) 和 !-e(不存在) 后面判断可以是字符串也可是变量
  7. 检测文件是否为可执行文件 使用 -x(可执行) 和 !-x(不可执行) 后面判断可以是字符串也可是变量

注意 上面 第1,2,3条被判断的必须是 变量, 4, 5, 6, 7则可以是变量也可是字符串

set $variable "0"; 
if ($variable) {
    # 不会执行,因为 "0" 为 false
    break;            
}

# 使用变量与正则表达式匹配 没有问题
if ( $http_host ~ "^star\.igrow\.cn$" ) {
    break;            
}

# 字符串与正则表达式匹配 报错
if ( "star" ~ "^star\.igrow\.cn$" ) {
    break;            
}
# 检查文件类的 字符串与变量均可
if ( !-f "/data.log" ) {
    break;            
}

if ( !-f $filename ) {
    break;            
}

return

Context: server, location, if

return code [text];
return code URL;
return URL;

停止处理并将指定的code码返回给客户端。 非标准code码 444 关闭连接而不发送响应报头。

0.8.42版本开始, return 语句可以指定重定向 url (状态码可以为如下几种 301,302,303,307),
也可以为其他状态码指定响应的文本内容,并且重定向的url和响应的文本可以包含变量

有一种特殊情况,就是重定向的url可以指定为此服务器本地的urI,这样的话,nginx会依据请求的协议$schemeserver_name_in_redirectport_in_redirect自动生成完整的 url (此处要说明的是server_name_in_redirectport_in_redirect 指令是表示是否将server块中的 server_namelisten 的端口 作为redirect用 )

# return code [text]; 返回 ok 给客户端
location = /ok {
    return 200 "ok";
}

# return code URL; 临时重定向到 百度
location = /redirect {
    return 302 http://www.baidu.com;
}

# return URL; 和上面一样 默认也是临时重定向
location = /redirect {
    return http://www.baidu.com;
}

rewrite

Context: server, location, if

rewrite regex replacement [flag];

rewrite 指令是使用指定的正则表达式regex来匹配请求的urI,如果匹配成功,则使用replacement更改URIrewrite指令按照它们在配置文件中出现的顺序执行。可以使用flag标志来终止指令的进一步处理。如果替换字符串replacementhttp://https://$ scheme开头,则停止处理后续内容,并直接重定向返回给客户端。

第一种情况 重写的字符串 带http://

location / {
    # 当匹配 正则表达式 /test1/(.*)时 请求将被临时重定向到 http://www.$1.com
    # 相当于 flag 写为 redirect
    rewrite /test1/(.*) http://www.$1.com;
    return 200 "ok";
}
# 在浏览器中输入 127.0.0.1:8080/test1/baidu 
# 则临时重定向到 www.baidu.com
# 后面的 return 指令将没有机会执行了

第二种情况 重写的字符串 不带http://

location / {
    rewrite /test1/(.*) www.$1.com;
    return 200 "ok";
}
# 发送请求如下
# curl 127.0.0.1:8080/test1/baidu
# ok

# 此处没有带http:// 所以只是简单的重写。请求的 uri 由 /test1/baidu 重写为 www.baidu.com
# 因为会顺序执行 rewrite 指令 所以 下一步执行 return 指令 响应了 ok 

rewrite 的四个 flag

  1. last
    停止处理当前的ngx_http_rewrite_module的指令集,并开始搜索与更改后的URI相匹配的location;
  2. break
    停止处理当前的ngx_http_rewrite_module指令集,就像上面说的break指令一样;
  3. redirect
    返回302临时重定向。
  4. permanent
    返回301永久重定向。
# 没有rewrite 后面没有任何 flag 时就顺序执行 
# 当 location 中没有 rewrite 模块指令可被执行时 就重写发起新一轮location匹配
location / {
    # 顺序执行如下两条rewrite指令 
    rewrite ^/test1 /test2;
    rewrite ^/test2 /test3;  # 此处发起新一轮location匹配 uri为/test3
}

location = /test2 {
    return 200 "/test2";
}  

location = /test3 {
    return 200 "/test3";
}
# 发送如下请求
# curl 127.0.0.1:8080/test1
# /test3
last 与 break 的区别

last 和 break一样 它们都会终止此 location 中其他它rewrite模块指令的执行,
但是 last 立即发起新一轮的 location 匹配 而 break 则不会

location / {
    rewrite ^/test1 /test2;
    rewrite ^/test2 /test3 last;  # 此处发起新一轮location匹配 uri为/test3
    rewrite ^/test3 /test4;
    proxy_pass http://www.baidu.com;
}

location = /test2 {
    return 200 "/test2";
}  

location = /test3 {
    return 200 "/test3";
}
location = /test4 {
    return 200 "/test4";
}
# 发送如下请求
# curl 127.0.0.1:8080/test1
# /test3 

当如果将上面的 location / 改成如下代码
location / {
    rewrite ^/test1 /test2;
    # 此处 不会 发起新一轮location匹配;当是会终止执行后续rewrite模块指令 重写后的uri为 /more/index.html
    rewrite ^/test2 /more/index.html break;  
    rewrite /more/index\.html /test4; # 这条指令会被忽略

    # 因为 proxy_pass 不是rewrite模块的指令 所以它不会被 break终止
    proxy_pass https://www.baidu.com;
}
# 发送如下请求
# 浏览器输入 127.0.0.1:8080/test1 
# 代理到 百度产品大全页面 https://www.baidu.com/more/index.html;
友情提醒下

此处提一下 在上面的代码中即使将 proxy_pass 放在 带有 breakrewrite上面它也是会执行的,这就要扯到nginx的执行流程了。大家有兴趣可以了解下。

rewrite 后的请求参数

如果替换字符串replacement包含新的请求参数,则在它们之后附加先前的请求参数。如果你不想要之前的参数,则在替换字符串 replacement 的末尾放置一个问号,避免附加它们。

# 由于最后加了个 ?,原来的请求参数将不会被追加到rewrite之后的url后面 
rewrite ^/users/(.*)$ /show?user=$1? last;

rewrite_log

Context: http, server, location, if

开启或者关闭 rewrite模块指令执行的日志,如果开启,则重写将记录下notice 等级的日志到nginxerror_log 中,默认为关闭 off

Syntax:    rewrite_log on | off;

set

Context: server, location, if

设置指定变量的值。变量的值可以包含文本,变量或者是它们的组合形式。

location / {
    set $var1 "host is ";
    set $var2 $host;
    set $var3 " uri is $request_uri";
    return 200 "response ok $var1$var2$var3";
}
# 发送如下请求
# curl 127.0.0.1:8080/test
# response ok host is 127.0.0.1 uri is /test

uninitialized_variable_warn

Context: http, server, location, if

控制是否记录 有关未初始化变量的警告。默认开启

查看原文

赞 43 收藏 83 评论 15

baliang 回答了问题 · 2019-01-12

解决Mac上MySQL报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'

查看日志 /usr/local/var/mysql/localhost.err 或者 /usr/local/var/mysql/主机名.err

关注 26 回答 14

baliang 回答了问题 · 2019-01-10

NotFound 和 BadRequest 那个更合适

可以使用NotFound,表示获取资源不存在,但是具体的返回码可以使用自定义的更有意义的标识,而不是使用http状态码。

关注 3 回答 2

baliang 回答了问题 · 2019-01-08

celery找不到命令

celery默认安装到/usr/local/python3/bin/celery路径,此路径不再$PATH中,所以shell找不到此路径,做一个连接就可以 ln -s /usr/local/python3/bin/celery /usr/bin/celery

关注 2 回答 1

认证与成就

  • 获得 3 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-07-26
个人主页被 462 人浏览