给鸡蛋再找个篮子——使用Gitea搭建轻量级Git服务器

前言

由于我之前提到过,想要用树莓派自己搭建一个Git的服务,于是我就去找了一下各种Git的服务端,最后就发现了这个使用go开发的Git服务端——Gitea。

Gitea的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用Go作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了x86,amd64,还包括 ARM 和 PowerPC。

因为是用Go开发,Gitea的主要程序就是一个原生的二进制可执行文件,而且Gitea可以使用SQLite当做数据库,这样它的资源占用比Gitlab这种Java开发需要虚拟机的要少的多,甚至他在官网上就写着用树莓派就能运行,这不是巧了吗。

  • 最低的系统硬件要求为一个廉价的树莓派
  • 如果用于团队项目,建议使用 2 核 CPU 及 1GB 内存

而且由于是Go开发,所以环境搭建就容易的很多了。

搭建过程

首先先去官网下载平台对应的二进制文件。

https://dl.gitea.io/gitea

直到这篇文章撰写时,最新的版本为1.16.8,而我是准备运行在树莓派上的,他的cpu架构是arm6,所以最后我下载的文件为gitea-1.16.8-linux-arm-6,下载后更名为gitea,然后传入服务器中。

将二进制文件移动到/usr/local/bin/gitea,并使用chmod +x添加运行权限。

这个操作也可以在服务器环境通过下面的命令执行,记得换成你对应平台的文件的地址。

1wget -O gitea https://dl.gitea.io/gitea/1.16.8/gitea-1.16.8-linux-arm-6
2chmod +x gitea
3sudo mv gitea /usr/local/bin/gitea

由于我们搭建的是Git服务器,所以需要提前安装好git,并且git版本要大于20,这里不同的系统可以用不同的包管理器来安装,这里贴出官方给出的不同系统下的安装方式。

Debian/Ubuntu

For the latest stable version for your release of Debian/Ubuntu

# apt-get install git For Ubuntu, this PPA provides the latest stable upstream Git version

# add-apt-repository ppa:git-core/ppa # apt update; apt install git

Fedora

# yum install git (up to Fedora 21)

# dnf install git (Fedora 22 and later)

Gentoo

# emerge --ask --verbose dev-vcs/git

Arch Linux

# pacman -S git

openSUSE

# zypper install git

Mageia

# urpmi git

Nix/NixOS

# nix-env -i git

FreeBSD

# pkg install git

Solaris 9/10/11 (OpenCSW)

# pkgutil -i git

Solaris 11 Express

# pkg install developer/versioning/git

OpenBSD

# pkg_add git

Alpine

$ apk add git

Red Hat Enterprise Linux, Oracle Linux, CentOS, Scientific Linux, et al. RHEL and derivatives typically ship older versions of git. You can download a tarball and build from source, or use a 3rd-party repository such as the IUS Community Project to obtain a more recent version of git.

Slitaz

$ tazpkg get-install git

安装好git之后,可以使用git --version检查一下版本是否大于20。

接下来的操作,官方的中文文档里有缺失,我一开始也踩了坑,然后照着英文版的文档做才成功搭建。

首先创建一个用户Gitea运行的用户,Gitea是不允许使用root用户运行的。

1adduser \
2   --system \
3   --shell /bin/bash \
4   --gecos 'Git Version Control' \
5   --group \
6   --disabled-password \
7   --home /home/git \
8   git

然后创建需要使用的目录和文件。

 1mkdir -p /var/lib/gitea/{custom,data,log}
 2chown -R git:git /var/lib/gitea/
 3chmod -R 750 /var/lib/gitea/
 4mkdir /etc/gitea
 5chown root:git /etc/gitea
 6chmod 770 /etc/gitea
 7chown root:git /etc/gitea
 8chmod 770 /etc/gitea
 9touch /etc/gitea/app.ini
10chown root:git /etc/gitea/app.ini
11chmod 770 /etc/gitea/app.ini

接下来,将服务文件gitea.service复制到/etc/systemd/system/gitea.service,这里如果懒也可以直接复制我下面的精简版(如果需要使用第三方数据库,请自行查看配置文件,并取消对应数据库的注释)。

 1[Unit]
 2Description=Gitea (Git with a cup of tea)
 3After=syslog.target
 4After=network.target
 5
 6[Service]
 7
 8RestartSec=2s
 9Type=simple
10User=git
11Group=git
12WorkingDirectory=/var/lib/gitea/
13
14ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
15Restart=always
16Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
17
18[Install]
19WantedBy=multi-user.target

接下来就是设置开机自启和启动服务了。

1sudo systemctl enable gitea
2sudo systemctl start gitea

然后服务应该就会在3000端口运行了(使用服务运行默认是在3000端口,如果需要修改端口,要在服务配置的命令中加上--install-port 端口号),从浏览器访问IP:3000来打开安装页面。

安装页面有中文,很简单,对着选就行了,我这里数据库选SQLite

在这里可以修改端口,等下安装好之后,重启就是新的端口了。如果SSH端口不填的话就是关闭SSH功能,我的机器因为准备做内网穿透,所以不准备用SSH了,我就关了。

个性化

我们的服务配置里面,把Gitee的资产文件都放在了/var/lib/gitea目录中,这个目录下面有三个子目录customdatalog,分别存放着自定义内容,数据,以及日志。而我们想要做个性化,就需要修改custom里面的文件。

关于custom目录,里面的public目录里的内容,会在服务启动后,被路由到网站的根目录下面。也就是说如果你有一些自定义的图片,样式,脚本什么的,都应该放在这里面。

安装好Gitee服务后,打开主页,我们看到的是默认的样式,虽然Gitee比较简洁,但是也是提供了一些类似主题修改的个性化功能的。

添加主题

首先先说主题的修改,Gitee的主题读取的策略是首先需要你在配置文件里面添加可选择的主题列表,之后再去custom/public/css目录中,读取theme-主题名.css的样式文件。所以如果你下载了新的主题或者自定义了一些CSS样式,那么你需要这样做。

我们假设主题的名字叫做test首先将你的CSS样式重命名为theme-test.css,然后将他放入custom/public/css目录中。

之后打开配置文件/etc/gitea/app.ini,在[ui]字段下面(如果没有请手动添加),修改THEMES字段,加入test主题,如下。

1...
2[ui]
3# 这里gitea是默认主题,arc-green是默认暗黑主题
4THEMES = gitea,arc-green,test 
5...

保存重启服务之后,在用户登陆后的主题设置里面,就可以看到test的主题可供选择了。

修改默认主题

如果你想要用户在未登录,或者是新用户默认情况下就设置为某个主题,你需要这么做。

首先按照上文所说,将你的主题加入到可选择列表中,例如上文中的test主题。然后修改配置文件的[ui]字段,在下面加入DEFAULT_THEME,值等于你想选择的默认主题,如下。

1...
2[ui]
3THEMES = gitea,arc-green,test
4# 这里将默认主题设置为了test
5DEFAULT_THEME = test 
6...

保存重启服务之后,打开首页就可以看到在未登录的时候,主题设置成了test

修改页面样式

在默认的首页样式中,我们可以看到除了我们的网站名字以外,还有一些默认的文字和图案内容。比如一款极易搭建的自助 Git 服务易安装,跨平台,轻量级,开源化等等。想要修改这些内容,你需要这样做。

首先需要下载Gitea的源代码。

https://dl.gitea.io/gitea/1.16.8/gitea-src-1.16.8.tar.gz

下载完成解压之后,我们可以在里面找到templates,这里面放着的就是全部页面的默认模板。我们可以将这个目录复制到custom目录下,然后修改这里面的模板,这样在服务启动后,Gitea就会优先读取custom目录下我们修改过的模板。当然,实际上你只需要保留你修改过的模板文件在custom目录里就好。

我们这次是要修改首页的内容,我们打开custom/templates/home.tmpl,修改一下里面的内容。

 1{{template "base/head" .}}
 2<div class="page-content home">
 3	<div class="ui stackable middle very relaxed page grid">
 4		<div class="sixteen wide center aligned centered column">
 5			<div>
 6				<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg"/>
 7			</div>
 8			<div class="hero">
 9				<h1 class="ui icon header title">
10					{{AppName}}
11				</h1>
12				<h2>这里就是页面的副标题</h2>
13			</div>
14		</div>
15	</div>
16	<!-- 注释掉不想要的内容
17    <div class="ui stackable middle very relaxed page grid">
18		<div class="eight wide center column">
19			<h1 class="hero ui icon header">
20				{{svg "octicon-flame"}} {{.i18n.Tr "startpage.install"}}
21			</h1>
22			<p class="large">
23				{{.i18n.Tr "startpage.install_desc" | Str2html}}
24			</p>
25		</div>
26		<div class="eight wide center column">
27			<h1 class="hero ui icon header">
28				{{svg "octicon-device-desktop"}} {{.i18n.Tr "startpage.platform"}}
29			</h1>
30			<p class="large">
31				{{.i18n.Tr "startpage.platform_desc" | Str2html}}
32			</p>
33		</div>
34	</div>
35	<div class="ui stackable middle very relaxed page grid">
36		<div class="eight wide center column">
37			<h1 class="hero ui icon header">
38				{{svg "octicon-rocket"}} {{.i18n.Tr "startpage.lightweight"}}
39			</h1>
40			<p class="large">
41				{{.i18n.Tr "startpage.lightweight_desc" | Str2html}}
42			</p>
43		</div>
44		<div class="eight wide center column">
45			<h1 class="hero ui icon header">
46				{{svg "octicon-code"}} {{.i18n.Tr "startpage.license"}}
47			</h1>
48			<p class="large">
49				{{.i18n.Tr "startpage.license_desc" | Str2html}}
50			</p>
51		</div>
52	</div>
53  -->
54</div>
55{{template "base/footer" .}}

修改完成后,保存重启,就可以看到页面的样式已经改变啦。

结语

虽然Gitea很多配置还要修改文件+重启服务,可能没有那么方便,但是他较小的资源占用和性能需求,拿来当做个人使用的服务的话还是比较适合的。

前几天Gitee称因为需要审查,迫于无奈下架了大量的开原仓库的事件引发了轩然大波,作为一个普普通通的小开发者,我对这个事件也没有什么评价。我所能做的就是再多搭建一个平台,用来保存我那些“可能也没什么价值”的源码罢了。毕竟都说不要把鸡蛋装在一个篮子里,而现如今最大的篮子就是Github,但是谁又能保证这个篮子不会破呢?

搭建私人低功耗服务器——如何将树莓派的系统装进USB设备

前言

最近搞了几个比较大的项目,github的免费套餐有点吃不消,所以就一直想着弄个别的git服务器。但是由于最近我正在给服务做《断舍离》,所以现在我之前买的公网服务器都被我鲨了。这时候我就想到自己手里不是还有一个低功耗Linux设备——树莓派吗。于是我就想到用树莓派挂载一个USB的移动硬盘,来当做自己的私有服务器。

我的树莓派是初代树莓派B+款,2014年生产,距今已经8年了。当年这东西我花了300多块钱买的,因为当时一个体积这么小但是却可以运行完整的Linux系统的设备,也是非常的稀有。他的性能很差,cpu主频只有700mhz,但是功耗还是比较低的,用一个5v2a的电源就能轻松带起。

之所以要把西开通塞进USB设备,是因为正常树莓派的系统是装到TF卡里的,但是TF卡的读写又慢,而且长期的读写很容易导致内存卡损坏。所以我就想着用USB挂一个移动硬盘来当做系统盘,虽然USB2.0的读写不高,但是至少可以防止存储挂掉。同时早期的树莓派并不直接带有USB引导功能,所以我在搭建的过程中也踩了不少坑。

前期准备

本文内容仅在树莓派1B+版本进行测试使用,其他平台请自行测试

  • 一张容量>2G的TF卡(因为系统容量是1.9G,容量不足烧录不进去)
  • 一个USB存储设备(推荐用USB的固态硬盘,相对来说更稳定一点)
  • 一台电脑

操作过程

首先在官网下载安装官方的系统烧录工具。

https://www.raspberrypi.com/software/

然后插上你准备好的TF卡,打开烧录工具,先选择擦除烧录一次,之后再选择Raspberry Pi OS (Legacy)(注意Debian版本Debian version: 10 (buster))进行烧录。

经测试Debian version: 11 (bullseye)的版本无法正常引导,不清楚是我这里设备的问题还是操作错误,总之我只确定buster是可用的

由于TF卡只有2G所以我选择了Lite版,也就是没有桌面环境的最小化系统。TF卡容量大的可以自行选择

烧录好之后,因为烧录工具会自动卸载设备,所以需要将TF卡从电脑拔出,然后再插入。这时可以看到有一个名叫boot的256m大的分区。在分区内创建文件命名为SSH,然后将TF卡从电脑中卸载,插入树莓派,启动开机。这里创建文件是为了默认启动SSH服务,使你可以在开机后通过SSH远程控制计算机。

当树莓派启动后,并切可以通过SSH连入系统后,使用sudo shutdown -h now将树莓派关机,然后拔出TF卡。将TF卡连接到电脑上,然后将USB存储设备也连接到电脑上。

使用磁盘管理工具对磁盘进行克隆,我这里使用的是傲梅分区助手,如果你有其他的类似软件也可以。将TF卡的磁盘直接克隆到USB存储设备上,这时可以看到TF卡和USB存储设备会各有一个256m大小的名字为boot的分区,以及一个大小为2G的分区(USB存储设备应该会有未使用空间)。

将两个设备都从电脑中卸载,然后TF卡和USB存储设备都插入树莓派,启动开机。开机后使用SSH连接到树莓派。

执行如下命令

1sudo blkid

这时终端中应该输出了类似如下的内容

1/dev/mmcblk0p1: LABEL_FATBOOT="boot" LABEL="boot" UUID="8551-82D5" TYPE="vfat" PARTUUID="f7403228-01"
2/dev/mmcblk0p2: LABEL="rootfs" UUID="aea57068-3f8a-462e-842c-b210b2d9c7ea" TYPE="ext4" PARTUUID="f7403228-02"
3/dev/sda1: LABEL_FATBOOT="boot" LABEL="boot" UUID="8551-82D5" TYPE="vfat" PARTUUID="087a1744-01"
4/dev/sda2: LABEL="rootfs" UUID="aea57068-3f8a-462e-842c-b210b2d9c7ea" TYPE="ext4" PARTUUID="087a1744-02"
5/dev/mmcblk0: PTUUID="f7403228" PTTYPE="dos"

这里显示的是你的磁盘设备的UUID,/dev/mmcblk0p1/dev/mmcblk0p2就是你TF卡的两个分区,而/dev/sda1/dev/sda2就是你的USB存储设备的两个分区。我们要记录一下/dev/sda2分区后面显示的PARTUUID,这里我的UUID是087a1744-02

然后使用nano打开/boot/cmdline.txt文件

1sudo nano /boot/cmdline.txt

这里编辑的是引导命令,内容应该类似下面

1console=serial0,115200 console=tty1 root=PARTUUID=f7403228-02 rootfstype=ext4 fsck.repair=yes rootwait

可以看到这里有一个root=PARTUUID=f7403228-02,根据上面的输出我们知道这个PARTUUID是指向了TF卡的第二个分区,我们修改这个值,将f7403228-02修改为USB存储设备对应的UUID,这里我需要修改为087a1744-02,修改结果如下。

1console=serial0,115200 console=tty1 root=PARTUUID=087a1744-02 rootfstype=ext4 fsck.repair=yes rootwait

然后ctrl + x保存文件。

之后使用nano打开/etc/fstab文件

1sudo nano /etc/fstab

这里编辑的是磁盘挂载文件,内容应该类似下面

1proc                    /proc           proc    defaults          0       0
2PARTUUID=f7403228-01    /boot           vfat    defaults          0       2
3PARTUUID=f7403228-02    /               ext4    defaults,noatime  0       1
4# a swapfile is not a swap partition, no line here
5#   use  dphys-swapfile swap[on|off]  for that

也将其中的f7403228-02替换成对应的UUID,这里我替换成087a1744-02,修改结果如下。

1proc                    /proc           proc    defaults          0       0
2PARTUUID=f7403228-01    /boot           vfat    defaults          0       2
3PARTUUID=087a1744-02    /               ext4    defaults,noatime  0       1
4# a swapfile is not a swap partition, no line here
5#   use  dphys-swapfile swap[on|off]  for that

然后ctrl + x保存文件。

然后sudo reboot重启,就完成了从USB存储设备启动的设置了。

但是这是USB存储设备只使用了2G的空间,这肯定是不够用的。所以我们需要扩展一下存储空间。

进入raspi-config

1sudo raspi-config

选择Advanced Options然后回车,选择第一项Expand Filesystems Ensures that all of the SD card storage is available,回车确认,回车重启系统。

系统重启后磁盘的容量就是正常的容量了,可以运行df -h检查,输出内容如下。

1Filesystem      Size  Used Avail Use% Mounted on
2/dev/root       221G  1.5G  210G   1% /
3devtmpfs        183M     0  183M   0% /dev
4tmpfs           216M     0  216M   0% /dev/shm
5tmpfs           216M   14M  202M   7% /run
6tmpfs           5.0M  4.0K  5.0M   1% /run/lock
7tmpfs           216M     0  216M   0% /sys/fs/cgroup
8/dev/mmcblk0p1  253M   49M  204M  20% /boot
9tmpfs            43M     0   43M   0% /run/user/1000

可以看到我的/dev/root的容量已经是221G了。

之后可以再把TF卡的第二分区和USB存储设备的第一分区删除掉,不过因为我比较懒而且怕出问题,所以就还是放着没有管。

画外音

最开始我想的是使用机械硬盘启动,结果折腾来折腾去,突然这块机械硬盘就决定原地去世,于是我只能掏出来之前的一块拆机固态换到了硬盘盒里面,但是固态确实要比机械盘稳定。机械盘有可能会因为供电问题启动不起来,但是用固态就一帆风顺了。心疼逝去的硬盘

解决恼人的Win10输入法问题——TSFTool

前言

相信如果是经常使用电脑的小伙伴,尤其是从98,2000,XP,Win7一路用过来的老用户,一定对现在版本的Win10默认输入法有很多意见,比如快捷键冲突不能使用ctrl+shift切换输入法为了玩Xbox修改了区域语言结果默认输入法不是英语等等,我之前也是饱受这些事情困扰,直到发现了今天的主角——TSFTool。

软件介绍

TSFTool 是 IMETool 的继承者,是运行在 Windows10/Windows Server 2019 操作系统下的输入法设置工具,支持 TSF 输入法框架,并部分兼容旧的 IMM 输入法框架。

简单点说就是一个用于修改系统输入法设置的工具,可以绕过系统设置的限制。

作者:Silence

发布地址:TSFTool 输入法设置工具

分流下载地址:TSFTool 0.9.2.0.zip

使用方式

设置默认输入法为美式键盘

禁用的输入法列表中,选择美式键盘,点击右侧启用,之后在启用的输入法列表中选择美式键盘,点击右侧上移,移动到最顶部,然后在启用的输入法列表中右键点击美式键盘,选择设置为默认输入法

禁用微软拼音

启用的输入法列表中右键点击微软拼音,选择禁用

关于Nginx的一些骚操作

前几天给服务器重装了个系统,用来反向代理一些接口,结果学会了几个骚操作,分享给大家。

反向代理使用了多个证书的网站

一上来属于是一个坑,我们都知道同一个IP可以被很多个域名指向,通过不同的域名来访问同一个IP的服务器,则是通过header里的host来判断是访问到哪个站点。但是这里面会有一种情况,就是这个站点是具有tls加密也就是https加密,同时这个站点配置了多个证书,这时候如果你直接代理的话,则会报证书错误,必须在location里添加proxy_ssl_server_name on,例如下面这样。

1proxy_set_header Host "www.example.com";
2proxy_ssl_server_name on;

这样源站的服务器才能知道你是为了哪一个域名和证书而来,才回给你发放对应的证书。 注:其实这个就是SNI功能,某些网站的访问就是在这一部被屏蔽掉的。

前端跨域时的处理

前端跨域限制是浏览器的一种安全措施,叫做同源策略(Sameoriginpolicy),属于是浏览器的一种很基础的安全策略。在同源策略限制下,网页的JS会不允许访问协议、域名或者端口不同的内容和资源。但是有时候我们前端的代码可能会需要访问不同域名或者端口的接口,所以我们会使用Nginx来处理跨域问题。 一般情况下,我们在网站配置的location下面,增加下面两条,就可以允许服务器被跨域访问了。

1add_header Access-Control-Allow-Origin * always;
2add_header Access-Control-Allow-Headers * always;

但是在做反向代理的时候,如果源站配置过同源限制,则需要再加上下面这两条。

1proxy_hide_header Access-Control-Allow-Origin;
2proxy_hide_header Access-Control-Allow-Headers;

这两条的意思是丢弃掉源站的同源配置,同时你增加了新的配置,这样才能正确配置。 但是其实还有一个问题,就是浏览器会发送methodoptions的请求,用来判断是否可以接收跨域请求。 那既然我们已经允许了全部的跨域请求,我们就可以给options请求直接返回一个200告诉他可以。

1if ($request_method = "OPTIONS") {
2	return 200;
3}

由此一来我们就可以随便调用这个反向代理的接口了。

反向代理Url使用变量

反向代理时,有可能源网址并不是固定的,而是随着某些nginx的变量改变的,这种时候,就要在proxy_pass中拼接一个变量,像下面这样。

1set $name 'tom';
2proxy_pass https://www.example.com/$name;

但是如果直接这样使用可能会报错,需要在server字段下,添加dns配置,如下。

1resolver 8.8.8.8;

这样才能在proxy_pass中拼接变量。

使用lua获取post请求的body

这是这次学到的最骚的一个操作,一般来说nginx是不能直接获取到post请求中的内容的,但是大家都知道lua是一种非常容易嵌入各种各样环境的语言,nginx中也是有嵌入lua可以使用的,所以我们可以通过lua来获取postbody

首先如果想获取body的话,需要先在server字段中,添加如下配置。

1lua_need_request_body on;

这条配置代表了声明lua需要读取requestbody

之后我们在location下,添加如下配置。

1set $name '';
2rewrite_by_lua_block {
3	local post = ngx.req.get_post_args()
4	ngx.var.name = post.name
5}
6proxy_pass https://www.example.com/$name;

首先先使用原生nginx语句,定义一个变量name,之后添加rewrite_by_lua_block代码段,这里因为我是需要在proxy_pass之前处理数据,所以使用了rewrite_by_lua_block,还有其他的节点可以使用,下面再说。 然后内部就是lua语句了,定义一个post变量,赋值为ngx.req.get_post_args()的返回值,这里就获取到了body的数据了,之后ngx.var.name这个变量就等于是之前我们使用set定义的变量name,所以这里就是给变量name赋值post.name,也就是我们请求上送过来的body中的name字段。 最后再把name变量拼接到proxy_pass后面。 于是就实现了,可以通过post请求中的name字段,来修改反向代理的地址了。

扩展内容——关于在nginx中使用lua

nginx提供了几个lua代码的处理阶段,具体如下表。

处理阶段 可用范围
init_by_lua http
set_by_lua server, server if, location, location if
rewrite_by_lua http, server, location, location if
access_by_lua http, server, location, location if
content_by_lua location, location if
header_filter_by_lua http, server, location, location if
body_filter_by_lua http, server, location, location if
log_by_lua http, server, location, location if
timer *

init_by_lua

在nginx重新加载配置文件时,运行里面lua脚本,常用于全局变量的申请。 例如lua_shared_dict共享内存的申请,只有当nginx重起后,共享内存数据才清空,这常用于统计。

set_by_lua

设置一个变量,常用与计算一个逻辑,然后返回结果 该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

rewrite_by_lua

在access阶段前运行,主要用于rewrite

access_by_lua

主要用于访问控制,能收集到大部分变量,类似status需要在log阶段才有。 这条指令运行于nginx access阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。

content_by_lua

阶段是所有请求处理阶段中最为重要的一个,运行在这个阶段的配置指令一般都肩负着生成内容(content)并输出HTTP响应。

header_filter_by_lua

一般只用于设置Cookie和Headers等 该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

body_filter_by_lua

一般会在一次请求中被调用多次, 因为这是实现基于 HTTP 1.1 chunked 编码的所谓“流式输出”的。 该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

log_by_lua

该阶段总是运行在请求结束的时候,用于请求的后续操作,如在共享内存中进行统计数据,如果要高精确的数据统计,应该使用body_filter_by_lua。 该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

timer

可参考官方文档:http://wiki.nginx.org/HttpLuaModule

在lua代码中,可以使用ngx.var.变量名获取ngxin的原生变量,例如ngx.var.name等价于nginx中的$name,但是注意的一点是,这个变量必须预先在nginx中声明。

可以在不同的请求阶段后面增加_block,则可以使用代码块形式嵌入lua脚本,如下。

1set $name '';
2rewrite_by_lua "
3	ngx.var.name = 'tom'
4"
5rewrite_by_lua_block {
6	ngx.var.name = "tom"
7}
Web前端开发面试题整理

导语

研究面试题是一件很有意义的事情,面试题里很多的知识点或许并不完全在工作中会用到,但是经常会涉及到一些底层知识,这些知识如果可以理解领悟的话,对程序设计还是会有不少的帮助的,这里就把之前见到的一些面试问题整理一下放在这里,然后我也可以一边分享一边学习。 如果有小伙伴发现有哪些问题我的答案有误,欢迎指正

ES6 相关

ES6 的新特性有哪些

const 和 let 变量声明

在 ES6 之前,变量声明都是采用的var,而var有一个特型叫做作用域提升,就是你无论在哪里声明这个变量,在运行时都会把这个声明提升到代码最前面执行,然后这样就会导致逻辑的混乱,而letconst解决了这个问题,同时这两者都是块级作用域,只会在一个花括号{}内生效,可以很好的避免作用域混乱产生逻辑错误。 一般情况下,使用let表示变量,用const表示常量。

 1var a = 1;
 2b = 2;
 3console.log(b); // 输出 2
 4function test(){
 5	let c = 3;
 6	const d = 4;
 7	console.log(a); // 输出 1
 8	console.log(b); // 输出 0,这里因为是在下面的 var b = 0 后面执行,所以 b 被赋值 0
 9	console.log(c); // 输出 3
10	console.log(d); // 输出 4
11}
12var b = 0;
13// 这里看做 var b 和 b = 0,var b 被提升到作用域顶部
14// 所以 b 会被先赋值 2 ,在这里再被赋值 0
15test()
16console.log(a); // 输出 1
17console.log(b); // 输出 0
18console.log(c); // 报错 c 未定义
19console.log(d); // 报错 d 未定义

模板字符串

在 ES6 之前,我们往往这么处理模板字符串:

1$("body").html("This demonstrates the output of HTML \
2content to the page, including student's\
3" + name + ", " + seatNumber + ", " + sex + " and so on.");

里面充满了+"\\,可读性和可维护性就不是很好。 但是在 ES6 之后,我们可以这样来处理模板字符串:

1$("body").html(`This demonstrates the output of HTML content to the page, 
2including student's ${name}, ${seatNumber}, ${sex} and so on.`);

``来包裹模板字符串。用${}来包裹变量。可以简单清晰的处理模板字符串。

箭头函数

这个本质就是语法糖了,不用写function,如果是直接返回一个值或者表达式的场合,还可以省略掉return{},而且可以继承当前上下文的this对象,用起来会比较方便。

 1// ES6 之前
 2var add = function (a, b) {
 3    return a + b;
 4};
 5// ES6 之后使用箭头函数
 6var add = (a, b) => a + b;
 7
 8// ES6 之前
 9[1,2,3].map((function(x){
10    return x + 1;
11}).bind(this));
12// ES6 之后使用箭头函数
13[1,2,3].map(x => x + 1);

函数参数的默认值

在 ES6 之前,函数的参数是不能直接设置默认值的,我们只能通过判断参数是否存在,再对其进行赋值,而 ES6 简化了这一点,可以直接赋初始值了。

 1// ES6 之前
 2function who(name) {
 3    if (!name) {
 4        name = '张三'
 5    }
 6    console.log(name);
 7}
 8
 9// ES6 之后
10function who(name = '张三') {
11    console.log(name);
12}
13
14who('李四'); // 输出 李四
15who(); // 输出 张三

Vue相关

谈一谈Vue.nextTick()函数

Vue中是异步更新DOM的,Vue.nextTick()是Vue框架提供给我们的DOM更新后的回调函数,当数据变化,DOM更新后,就会执行该函数,常见于需要处理DOM场合,例如。

 1// 改变数据
 2vm.message = 'changed'
 3
 4// 想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
 5console.log(vm.$el.textContent) // 并不会得到'changed'
 6
 7// 这样可以,nextTick里面的代码会在DOM更新后执行
 8Vue.nextTick(function(){
 9    console.log(vm.$el.textContent) // 可以得到'changed'
10})