0%

Flutter 是一款用同一套代码构建高性能、高保真的 iOS 及安卓应用的跨平台移动端应用 SDK。

文档中提到:

Flutter 包括一个 react 风格的框架、一个 2D 渲染引擎、一些预制的插件以及开发者工具。

upload successful

文本希望能快速为 JavaScript 开发者们提供一个简练的入门指南,我会试着以 JS 与 npm 生态系统来类比 Flutter / Dart 与 Pub 包库。

如果你对最新的 Flutter 教程、库、公告及社区的更新感兴趣,我建议您订阅双周刊 Flutter Newsletter


我在 React Native EU 的演讲 React Native — 跨平台及超越中讨论并演示了 React 生态系统中 React Native WebReact PrimitivesReactXP 的不同之处,并且我也有机会讨论 WeexFlutter 的不同之处。

在尝试 Flutter 之后,我认为它是近几年我所关注的前端技术中最让我激动的一个。在本文中,我将讨论为何它如此令我激动,并介绍如何尽可能快的入门 Flutter。

Read more »

upload successful

TL;DR

本文涵盖了从创建简单的 Dockerfile 到生产环境多级构建 Python 应用的例子。以下为本指南的内容摘要:

  • 使用合适的基础镜像(开发环境使用 debian,生产环境使用 alpine)。
  • 在开发时使用 gunicorn 进行热加载。
  • 优化 Docker 的 cache layer(缓存层)—— 按照正确的顺序使用命令,仅在必要时运行 pip install
  • 使用 flask 的 static 及 template 目录部署静态文件(比如 React、Vue、Angular 生成的 bundle)。
  • 使用 alpine 进行生产环境下的多级构建,减少最终镜像文件的大小。
  • #彩蛋 — 在开发时可以用 gunicorn 的 --reload--reload_extra_files 监视文件(包括 html、css 及 js)的修改。

如果你需要以上步骤的代码,请参考 GitHub repo.

Read more »

进入 Grub 之后,显示 GRUB> 命令提示符。可根据以下操作加载 linux kernel 并进行引导。

查找引导分区

1
ls

将返回 hd0 之类的硬盘符 由于我的 linux 装载在第一块硬盘的第一个分区上,由此

1
ls (hd0,1)
可以看到根目录 / 下的目录结构。

装载 kernel 并引导

在根目录下有 vmlinuz 与 initrd.img 两个文件,使用

1
2
linux (hd0,1)/vmlinuz root=/dev/sda1 ro
initrd (hd0,1)/initrd.img
装载内核

boot

1
boot

即可启动系统。启动一次之后 Grub 会保存此设置,之后可直接进入系统。

upload successful

TL;DR

本文涵盖了从创建简单的 Dockerfile 到生产环境多级构建 Node.js Web 应用的例子。以下为本指南的内容摘要:

  • 使用合适的基础镜像(开发环境使用 carbon,生产环境使用 alpine)。
  • 在开发时使用 nodemon 进行热加载。
  • 优化 Docker 的 cache layer(缓存层)—— 按照正确的顺序使用命令,仅在需要时运行 npm install
  • 使用 serve 包部署静态文件(比如 React、Vue、Angular 生成的 bundle)。
  • 使用 alpine 进行生产环境下的多级构建,减少最终镜像文件的大小。
  • #建议 — 1) 使用 COPY 代替 ADD 2) 使用 init 标识,处理 CTRL-C 等内核信号。
Read more »

Swift playground 对于试用新的 framework探索语言的新特性来说十分有用。它提供的实时反馈能让你快速尝试新的想法与解决方案,大大提高生产力。

自 Swift 问世以来,无论是设计 framework API,还是给 app 开发新功能,我一直在不停地使用 playground,希望找到将它整合进工作流的办法。

本周,让我们来了解如何将 Swift playground 应用于编写单元测试,以及如何让 TDD - 测试驱动开发(ish)工作流变得更加顺畅。

基础

实际上在 playground 编写测试与编写 test target 基本一致。你可以先导入 XCTest,然后创建测试用例,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Foundation
import XCTest

class UserManagerTests: XCTestCase {
var manager: UserManager!

override func setUp() {
super.setUp()
manager = UserManager()
}

func testLoggingIn() {
XCTAssertNil(manager.user)

let user = User(id: 7, name: "John")
manager.login(user: user)
XCTAssertEqual(manager.user, user)
}
}

如何访问你的代码

不过,如果你还没有实现直接在 playground 中测试的代码,那么在刚开始时访问代码可能会有点麻烦。你必须根据代码的来源( app 还是 framework ),而选择不同的方式来访问将要测试的代码

测试 app 代码

由于可以在编写 playground 时不直接导入 app target,因此可以使用下面的几种方法测试 app 代码:

1) 复制代码 这大概是最简单的方法了。将想测试的代码复制至 playground 运行,最后再拷回去。这个方法简单粗暴。

2) 复制文件 如果你不想直接将要测试的代码放到 playground 中,也可以将需要的源文件复制到 playground 的 Sources 目录中(使用 ⌘ + 0 显示 organizer,然后将文件拖进去)。接下来同上,在运行测试之后再将改变后的文件拷回覆盖源文件。

3) 创建 framework target 如果你讨厌复制文件,你也可以创建一个包含需要测试代码的 framework。在 Xcode 中创建一个新的 framework(或使用 SwiftPlate 创建一个跨平台 framework),接着按照下面的步骤操作。

测试 framework 代码

你可以通过以下操作将任意 framework 加入 playground:

  • 将 framework 的 Xcode 工程拖入 playground 的 organizer 中。
  • 系统将提示你将 playground 保存为一个工作区,照做即可(请注意不要将 playground 的内部工作区覆盖掉,而应该在 playground 文件夹外去创建一个新的工作区)。
  • 打开此工作区。
  • 选择你的 framework 的 scheme,对其进行构建。
  • 现在,可以 import 你的 framework,开始编码了!

如果你希望自动执行上述操作,可以使用我写的脚本 - Playground,它能让你通过一行命令完成上述除了 framework 的构建与 import 之外的所有操作:

1
$ playground -d /Path/To/Framework/Project.xcodeproj

运行测试

现在已经可以访问需要测试的代码了,并且我们还为其编写好了一个测试用例。现在试着运行这个测试用例! 🚀

在一般的 test target 中,你一般会使用 ⌘ + U 来运行你的测试;但在 playground 中,我希望 Xcode 能自动运行测试(以获得舒爽的实时反馈)。最简单的实现方式就是为你的测试用例运行 defaultTestSuite,如下所示:

1
UserManagerTests.defaultTestSuite().run()

执行上面的操作会运行测试,并将测试结果转储至控制台(可使用 ⌘ + ⇧ + C 呼出)。这样做虽然没问题,但很容易错过错误信息。为此,可以创建一个测试观察者(test observer),在测试发生错误时触发一个断言失败(assertionFailure)错误:

1
2
3
4
5
6
7
8
9
10
11
class TestObserver: NSObject, XCTestObservation {
func testCase(_ testCase: XCTestCase,
didFailWithDescription description: String,
inFile filePath: String?,
atLine lineNumber: UInt) {
assertionFailure(description, line: lineNumber)
}
}

let testObserver = TestObserver()
XCTestObservationCenter.shared().addTestObserver(testObserver)

在开始测试出现失败时,我们将看到编辑器提示一个行内错误 🎉

如果你用的是 Swift 4,需要将上面的代码改成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
class TestObserver: NSObject, XCTestObservation {
func testCase(_ testCase: XCTestCase,
didFailWithDescription description: String,
inFile filePath: String?,
atLine lineNumber: Int) {
assertionFailure(description, line: UInt(lineNumber))
}
}

let testObserver = TestObserver()
XCTestObservationCenter.shared.addTestObserver(testObserver)
UserManagerTests.defaultTestSuite.run()

总结

虽然需要额外做一些设置,但我还是很喜欢使用 Swift playground 进行单元测试。我觉得这样通过快速的反馈并轻松进行修改,更加接近理想中的红绿重构。这也可以构建更健壮的测试与更高的测试覆盖率。

我个人倾向于为正在开发的 app 与 framework 准备好一个 playground,以便更轻松地深入调试。此外,我还倾向于围绕 framework 构建 app,这样只需简单将代码引入 playground 就能开始编码。我会在之后的博文中讨论这些结构与设置的细节。

你怎么看?你是否准备使用 playground 进行单元测试?或者你是否在尝试其它方法?请通过评论或 Twitter @johnsundell 让作者知道你的意见、问题与反馈。

感谢您的阅读 🚀

本文发布于掘金 https://juejin.im/post/5a8e66276fb9a0633c661f3f

简介

在本文中,我将介绍什么是 Nginx 以及如何为 Flask Web 应用配置 Nginx。本文是《部署 Flask 应用》系列文章的一部分。我曾找到过多份关于 Nginx 及其配置的文章,但我希望能更深入其细节,了解如何使用 Nginx 为 Flask Web 应用服务以及如何为此进行配置。Nginx 的配置文件有点让人困惑,因为大多数的文档仅仅是简单罗列了一个配置文件,而没有对配置中每一步做了什么进行任何解释。希望本文能让你清晰地理解如何为你的应用配置 Nginx。

什么是 Nginx?

在 Nginx(发音为“engine-X”)的官网中,有着这个工具的概要描述:

Nginx 是一款免费、开源、高性能的 HTTP 服务器以及反向代理,同时也可以作为 IMAP/POP3 代理服务器。Nginx 以其高性能、稳定性、丰富的功能、简单的配置、低资源消耗而闻名。

我们可以拓展理解此说明…… Nginx 是一个可以为你的 Web 应用处理 HTTP 请求的服务器。对于典型的 Web 应用,Nginx 可以配置为 HTTP 请求进行以下操作:

  • 将请求 反向代理 至上游服务器(例如 Gunicorn、uWsgi、Apache 等)。
  • 为静态资源(Javascript 文件、CSS 文件、图像、文档、静态 HTML 文件)提供服务。

同时 Nginx 也提供了负载均衡功能,可以让多个上游服务器为请求提供服务,不过在本文中暂不讨论此功能。

下图为描述 Nginx 如何为 Flask Web 应用提供服务的简图:

upload successful

Nginx 会处理来自因特网(比如来自你应用的用户)的 Http 请求。根据你对 Nginx 的配置,它可以直接提供并向请求源返回静态内容(Javascript 文件、CSS 文件、图像、文档、静态 HTML 文件)。此外,它也能将请求反向代理至 WSGI(Web Server Gateway Interface)以让你在 Flask Web 应用中生成动态内容(HTML)并返回给用户。

上面的示意图假定用户使用了 Docker,但不使用 Docker 时 Nginx 的配置也与此十分相似(仅仅省略了图中容器的概念)。

为什么你需要 Nginx 与 Gunicorn?

Nginx 作为一个 HTTP 服务器,在许多应用中都被使用:列表。它提供了许多的功能,但无法直接为 Flask 应用提供服务。而 Gunicorn 可以做到这一点。Nginx 收到 HTTP 请求,并将其传递给 Gunicorn 交由你的 Flask 应用进行处理(比如你在 view.py 中定义的路由)。Gunicorn 是一个 WSGI 服务器,可以处理 HTTP 请求,并将它们通过路由交给任何支持 WSGI 的 python 应用处理(比如 Flask、Django、Pyramid 等)。

Nginx 配置文件的结构

注意:本文应用的是 Nginx v1.11.3,配置文件所在的位置根据你 Nginx 版本的不同会有所变化,比如 /opt/nginx/conf/。

根据你安装、使用 Nginx 方式的不同,配置文件的结构会略有不同。大多数的配置结构如下所示:

结构 1

如果你使用的是从源代码编译得到的 Nginx 或者官方的 Docker 镜像,那么配置文件在 /etc/nginx/ 中,主配置文件为 /etc/nginx/nginx.conf。在 /etc/nginx/nginx.conf 的最下面的一行会将位于 /etc/nginx/conf.d/ 目录下的其余配置文件内容载入配置中:

  • include /etc/nginx/conf.d/*.conf;

结构 2

如果你是通过包管理器(比如 Ubuntu 的 apt-get)安装的 Nginx,那么你的 /etc/nginx/ 下会有下面两个子目录:

  • sites-available – 包含为多个网站准备的多个配置文件。
  • sites-enabled – 包含一个指向 sites-available 目录中配置文件的软链接。

这两个目录继承于 Apache,将应用于 Nginx 的配置。

由于我的 Flask 应用使用的是 Docker 部署,因此在本文将主要关注上面的结构 1。

Nginx 的配置

Nginx 的顶层配置文件是 nginx.conf。Nginx 接受多层级的配置文件,这也使得用户可以针对自己的应用进行弹性的配置。如需了解配置文件中各参数的详细信息,可以参阅 Nginx 官方文档

在 Nginx 中,由配置块(block)来组织各个配置参数。以下为在本文中我们将提到的配置块:

  • Main – 定义于 nginx.conf(所有不属于配置块的参数均属 Main 块)
  • Events – 定义于 nginx.conf
  • Http – 定义于 nginx.conf
  • Server – 定义于 application_name.conf

将这些配置块拆分至不同的文件,可以让你在 nginx.conf 中定义 Nginx 的高级别配置,在其它的 *.conf 文件中为你的应用定义虚拟主机或服务器的参数。

nginx.conf 详细说明

安装 Nginx 时自带的默认 nginx.conf 文件可以适用于大多数服务器的初步配置。让我们仔细探查 nginx.conf 的内容,并思考如何拓展这里的默认设置。

Main 部分

nginx.conf 的 main 配置块(即那些不在配置块中的参数)为:

1
2
3
4
5
user  nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

第一个参数(user)将定义 Nginx 服务器的拥有者以及运行用户。当 Nginx 通过 Docker 容器运行时,使用默认值就够了。

第二个参数(worker_processes)定义了 worker processes(工作进程)的数量。此参数推荐的默认值为当前服务器使用内核的数量。对于基础的虚拟私有服务器(VPS)来说,默认值 1 就是个不错的选择。当你拓展 VPS 性能时可以增加这个数字。

第三个参数(error_log)定义了错误日志在文件系统中存放的位置,并能额外定义一个参数来规定需要记录日志的最小错误等级。这个参数使用默认值即可。

第四个参数(pid)定义了用于存储 Nginx 主进程 pid 的文件位置。这个参数使用默认值即可。

events 配置块

events 配置块定义了一些会影响连接处理的参数。它也是 Nginx.conf 文件中第一个配置块:

1
2
3
events {
worker_connections 1024;
}

在这个配置块中仅有一个单独的参数(worker_connections),定义了工作进程可以打开的最大并发连接数。默认值定义了总共可用 1024 个连接,无需更改(但你需要计算用户请求站点及请求 WSGI 服务器的连接数)。

http 配置块

http 配置块定义了一些关于 Nginx 如何处理 HTTP Web 流量的参数。它是 nginx.conf 文件中第二个配置块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

include /etc/nginx/conf.d/*.conf;
}

第一个参数(include)指定了需要引入的配置文件,在此引入的是位于 /etc/nginx/ 的 mime.types 文件,这个文件定义了各种 Nginx 支持的文件类型。此参数应该保持默认值。

第二个参数(default_type)指定了默认给用户返回的文件类型。对于 Flask 应用来说,返回的是动态生成的 HTML 文件,因此这个参数应改为 default_type text/html;

第三个参数(log_format)指定了日志的格式,应当保持默认值。

第四个参数(access_log)指定了 Nginx 日志的访问位置,应当保持默认值。

第五个参数(send_file)以及第六个参数(tcp_nopush)稍微有点复杂。可以参阅《优化 Nginx》一文来了解这些参数(包括 tcp_nodelay)的详细情况。由于我们打算用 Nginx 来传递静态内容,因此可以这么设置这些参数:

1
2
3
sendfile        on;
tcp_nopush on;
tcp_nodelay on;

第七个参数(keepalive_timeout)定义了与客户端保持连接的超时时长,应当保持默认值。

第八个参数(gzip)定义了 gzip 压缩算法的使用方法,以减少传输数据量。虽然数据量减少了,但也因此增加平台在压缩过程中的性能消耗,好处两两抵消,因此保持它的默认值(off)。

第九个,也是最后一个参数(include)定义了位于 /etc/nginx/conf.d/ 下后缀名为 .conf 的其它配置文件。现在我们将使用这些配置文件定义静态内容服务器以及 WSGI 服务器的反向代理。

nginx.conf 的最终配置

在 nginx.conf 默认设置之上,我们需要根据需要调整一些参数(并加上注释),下面为最终版本的 nginx.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Define the user that will own and run the Nginx server
user nginx;
# Define the number of worker processes; recommended value is the number of
# cores that are being used by your server
worker_processes 1;

# Define the location on the file system of the error log, plus the minimum
# severity to log messages for
error_log /var/log/nginx/error.log warn;
# Define the file that will store the process ID of the main NGINX process
pid /var/run/nginx.pid;

# events block defines the parameters that affect connection processing.
events {
# Define the maximum number of simultaneous connections that can be opened by a worker process
worker_connections 1024;
}

# http block defines the parameters for how NGINX should handle HTTP web traffic
http {
# Include the file defining the list of file types that are supported by NGINX
include /etc/nginx/mime.types;
# Define the default file type that is returned to the user
default_type text/html;

# Define the format of log messages.
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

# Define the location of the log of access attempts to NGINX
access_log /var/log/nginx/access.log main;

# Define the parameters to optimize the delivery of static content
sendfile on;
tcp_nopush on;
tcp_nodelay on;

# Define the timeout value for keep-alive connections with the client
keepalive_timeout 65;

# Define the usage of the gzip compression algorithm to reduce the amount of data to transmit
#gzip on;

# Include additional parameters for virtual host(s)/server(s)
include /etc/nginx/conf.d/*.conf;
}
为静态内容部署及反向代理配置 Nginx

如果你查看默认的 /etc/nginx/conf.g/default.conf,可以看到它提供了一个简单的服务器配置块,并给了许多取消注释即可使用的可选配置。我们不会挨个去研究这个文件中的配置,而是直接探讨对于我们部署静态内容以及 WSGI 反向代理有用的关键参数。以下是推荐的 application_name.conf 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Define the parameters for a specific virtual host/server
server {
# Define the directory where the contents being requested are stored
# root /usr/src/app/project/;

# Define the default page that will be served If no page was requested
# (ie. if www.kennedyfamilyrecipes.com is requested)
# index index.html;

# Define the server name, IP address, and/or port of the server
listen 80;
# server_name xxx.yyy.zzz.aaa

# Define the specified charset to the “Content-Type” response header field
charset utf-8;

# Configure NGINX to deliver static content from the specified folder
location /static {
alias /usr/src/app/project/static;
}

# Configure NGINX to reverse proxy HTTP requests to the upstream server (Gunicorn (WSGI server))
location / {
# Define the location of the proxy server to send the request to
proxy_pass http://web:8000;

# Redefine the header fields that NGINX sends to the upstream server
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# Define the maximum file size on file uploads
client_max_body_size 5M;
}
}

服务器配置块为特定的虚拟主机或服务器定义了参数。通常为你在 VPS 上部署的单个 Web 应用。

第一个参数(root)定义了被请求的内容所存储的位置。当 Nginx 收到用户请求时,它便会在此目录中查找。由于在默认的”/“路径中定义过了,因此可以注释掉这个不必要的参数。

第二个参数(index)定义了在请求未指定页面时(比如访问 www.kennedyfamilyrecipes.com)所得到的默认页面。由于我们使用的是 Flask Web 应用生成的动态内容,因此需要注释掉这个参数。

前两个参数(root 和 index)都包含在此配置文件中,在一些情况下可以用于 Nginx 的配置。

第三个参数(server_name)和第四个参数(listen)需要一同使用。如果你的 Web 应用程序已经部署好了,那么你需要设置这些参数为:(注,端口默认为 80,此时不需要填)

1
2
3
4
5
server {

Listen 192.241.229.181;

}

如果你除了 www.kennedyfamilyrecipes.com 之外还要部署另一个 Flask 应用 blog.kennedyfamilyrecipes.com,那么你需要将”server“配置块拆开,分别配置”user_name“和”listen“:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name *.kennedyfamilyrecipes.com;

. . .

}

server {
listen 80;
server_name blog.kennedyfamilyrecipes.com;

. . .

}

Nginx 将选择最匹配请求的”server_name“。也就是说对”blog.kennedyfamilyrecipes.com“的请求会优先匹配”blog.kennedyfamilyrecipes.com“而不是”*.kennedyfamilyrecipes.com“。

第五个参数(charset)定义了响应头”Content-Type“的字符集值,应当设置为”utf-8“。

第一个”location“配置块定义了 Nginx 需要递送位于以下位置的静态内容:

1
2
3
location /static {
alias /usr/src/app/project/static;
}

location 配置块定义了如何处理请求的 URI(域名或 IP、端口号之后的部分)。在这第一个 location 配置块(/static)中,我们定义了 Nginx 将会处理来自 www.kennedyfamilyrecipes.com/static/ 的请求,检索位于 /usr/src/app/project/static 目录下的文件。例如,请求 www.kennedyfamilyrecipes.com/static/img/img_1203.jpg 将会返回位于 /usr/src/app/project/static/img/img_1203.jpg 的图片文件。如果文件不存在,则向用户返回 404 错误码(NOT FOUND)。

第二个 location 配置块("/")定义反向代理。这个 location 配置块会定义 Nginx 如何将请求传递给 我们的 Flask 应用接口所在的 WSGI(Gunicorn)服务器。仔细看看其中的每个参数:

1
2
3
4
5
6
7
8
   location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 5M;
}

第一个参数(proxy_pass)定义了接收转发请求的代理服务器的位置。如果你想将请求转发至本机的服务器时可以使用:

1
proxy_pass http://localhost:8000/;

如果你希望将请求转发给指定的 Unix socket 时(比如和 Nginx 运行在同一台机器中的 Gunicorn 服务器),可以使用:

1
proxy_pass http://unix:/tmp/backend.socket:/

如果你使用 Docker 容器运行的 Nginx,希望与容器中的 Gunicorn 进行通信,那么可以直接使用运行 Gunicorn 的容器名称:

1
proxy_pass http://web:8000;

第二个参数(proxy_pass_header)可以让你重新定义发往上游服务器(比如 Gunicorn)的请求的头部。这个参数可以进行以下四次设置:

  • Nginx 服务器的名称及端口(Host $host)
  • 原始客户端请求的模式(比如是 http 请求还是 https 请求)(X-Forwarded-Proto $scheme)
  • 用户的 IP 地址(X-Real-IP $remote_addr)
  • 至当前节点位置,客户端经过的所有代理的 IP 地址(X-Forwarded-For $proxy_add_x_forwarded_for)

第三个参数(client_max_body_size)定义了文件上传允许的最大大小,对于需要上传文件的 Web 应用来说非常重要。由于图像大小一般在 2 MB 内,因此在这儿设置 5 MB 基本上可以满足任何图像。

总结

本文介绍了什么是 Nginx 服务器,以及如何为一个 Flask 应用对其进行配置。Nginx 是大多数 Web 应用的关键组件,它为用户提供静态内容、反向代理请求至上游服务器(在我们的 Flask Web 应用中是 WSGI),以及负载均衡(本文未提及)。希望看完本文后你能更轻松地理解 Nginx 的配置!

引用资料

How to Configure NGINX (Linode)

NGINX Wiki

NGINX Pitfalls and Common Mistakes

How to Configure the NGINX Web Server on a VPS (DigitalOcean)

Understanding NGINX Server and Location Block Selection Algorithms (DigitalOcean)

NGINX Optimization: Understanding sendfile, tcp_nodelay, and tcp_nopush

本文发布于掘金 https://juejin.im/post/5a795febf265da4e94499949

就在不到一个月前,HTML 5.2 正式成为了 W3C 的推荐标准(REC)。当一个规范到达 REC 阶段,就意味着它已经正式得到了 W3C 成员和理事长的认可。并且 W3C 将正式推荐浏览器厂商部署、web 开发者实现此规范。

在 REC 阶段有个原则叫做“任何新事物都至少要有两种独立的实现”,这对于我们 web 开发者来说是一个实践新特性的绝佳机会。

在 HTML 5.2 中有一些添加和删除,具体改变可以参考官方的 HTML 5.2 变动内容网页。本文将介绍一些我认为与我的开发有关的改动。

新特性

原生的 <dialog> 元素

在 HTML 5.2 的所有改动中,最让我激动的就是关于 <dialog> 元素这个原生对话框的介绍。在 web 中,对话框比比皆是,但是它们的实现方式都各有不同。对话框很难实现可访问性,这导致大多数的对话框对那些不方便以视觉方式访问网页的用户来说都是不可用的。

新的 <dialog> 元素旨在改变这种状况,它提供了一种简单的方式来实现模态对话框。之后我会单独写一篇文章专门介绍这个元素的工作方式,在此先简单介绍一下。

由一个 <dialog> 元素创建对话框:

1
2
3
4
<dialog>  
<h2>Dialog Title</h2>
<p>Dialog content and other stuff will go here</p>
</dialog>

默认情况下,对话框会在视图中(以及 DOM 访问中)隐藏,只有设置 open 属性后,对话框才会显示。

1
<dialog open>  

open 属性可以通过调用 show()close() 方法开启或关闭,任何 HTMLDialogElement 都可以调用这两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<button id="open">Open Dialog</button>  
<button id="close">Close Dialog</button>

<dialog id="dialog">
<h2>Dialog Title</h2>
<p>Dialog content and other stuff will go here</p>
</dialog>

<script>
const dialog = document.getElementById("dialog");

document.getElementById("open").addEventListener("click", () => {
dialog.show();
});

document.getElementById("close").addEventListener("click", () => {
dialog.close();
});
</script>

目前,Chrome 浏览器已经支持 <dialog> 元素,Firefox 也即将支持(behind a flag)。

upload successful

上图为 caniuse.com 关于 dialog 特性主流浏览器兼容情况的数据

在 iFrame 中使用 Payment Request API(支付请求 API)

Payment Request API 是支付结算表单的原生替代方案。它将支付信息置于浏览器处理,用来代替之前各个网站各不相同的结算表单,旨在为用户提供一种标准、一致的支付方式。

在 HTML 5.2 之前,这种支付请求无法在文档嵌入的 iframe 中使用,导致第三方嵌入式支付解决方案(如 Stripe, Paystack)基本不可能使用这个 API,因为它们通常是在 iframe 中处理支付接口。

为此,HTML 5.2 引入了用于 iframe 的 allowpaymentrequest 属性,允许用户在宿主网页中访问 iframe 的 Payment Request API。

1
<iframe allowpaymentrequest>  

苹果的图标尺寸

如要定义网页图标,我们可以在文档的 head 中使用 <link rel="icon"> 元素。如果要定义不同尺寸的图标,我们可以使用 sizes 属性。

1
2
<link rel="icon" sizes="16x16" href="path/to/icon16.png">  
<link rel="icon" sizes="32x32" href="path/to/icon32.png">

这个属性虽然纯粹是个建议,但如果提供了多种尺寸的图标,可以让用户代理(UA)决定使用哪种尺寸的图标。在大多数设备有着不同的“最佳”图标尺寸时尤为重要。

在 HTML 5.2 之前,sizes 属性仅能用于 rel 为 icon 的 link 元素中。然而,苹果的 iOS 设备不支持 sizes 属性。为了解决这个问题,苹果自己引入了一个他们设备专用的 rel appple-touch-icon 用于定义他们设备上使用的图标。

在 HTML 5.2 中,规范定义了 sizes 属性不再仅仅可用于 rel 为 icon 的元素,也能用于 rel 为 apple-touch-icon 的元素。这样可以让我们为不同的苹果设备提供不同尺寸的图标。不过直到现在为止,据我所知苹果的设备还是不支持 sizes 属性。在将来苹果最终支持此规范时,它将派上用场。

新的有效实践

除了新特性之外,HTML 5.2 还将一些之前无效的 HTML 写法认定为有效。

多个 <main> 元素

<main> 元素代表网页的主要内容。虽然不同网页的重复内容可以放在 header、section 或者其它元素中,但 <main> 元素是为页面上的特定内容保留的。因此在 HTML 5.2 之前,<main> 元素在 DOM 中必须唯一才能令页面有效。

随着单页面应用(SPA)的普及,要坚持这个原则变得困难起来。在同一个网页的 DOM 中可能会有多个 <main> 元素,但在任意时刻只能给用户展示其中的一个。

使用 HTML 5.2,我们只要保证同一时刻只有一个 <main> 元素可见,就能在我们的标签中使用多个 <main> 元素。与此同时其它的 <main> 元素必须使用 hidden 属性进行隐藏。

1
2
3
<main>...</main>  
<main hidden>...</main>
<main hidden>...</main>

我们都知道,通过 CSS 来隐藏元素的方法有很多,但多余的 <main> 元素必须使用 hidden 属性进行隐藏。任何其它隐藏此元素的方法(如 display: none;visibility: hidden;)都将无效。

<body> 中写样式

一般来说,使用<style>元素定义的内联 CSS 样式会放置在 HTML 文档的 <head> 中。随着组件化开发的流行,开发者已经发现编写 style 并放置在与其相关的 html 中更加有益。

在 HTML 5.2 中,可以在 HTML 文档 <body> 内的任何地方定义内联 <style> 样式块。这意味着样式定义可以离它们被使用的地方更近。

1
2
3
4
5
6
7
<body>  
<p>I’m cornflowerblue!</p>
<style>
p { color: cornflowerblue; }
</style>
<p>I’m cornflowerblue!</p>
</body>

然而仍需注意的是,由于性能问题,样式还是应当优先考虑放在 <head>。参见 规范

样式元素最好用于文档的 head 中。在文档的 body 中使用样式可能导致重复定义样式,触发重布局、导致重绘,因此需要小心使用。

此外还应该注意的是如示例所示,样式不存在作用域。后来在 HTML 文档中定义的内联样式仍然会应用于之前定义的元素,所以它可能会触发重绘。

<legend> 中的标题元素

在表单中,<legend> 元素表示 <fieldset> 表单域中的标题。在 HTML 5.2 前,legend 元素的内容必须为纯文本。而现在,它可以包含标题元素(<h1>等)了。

1
2
3
4
5
6
7
8
<fieldset>  
<legend><h2>Basic Information</h2></legend>
<!-- Form fields for basic information -->
</fieldset>
<fieldset>
<legend><h2>Contact Information</h2></legend>
<!-- Form fields for contact information -->
</fieldset>

当我们想用 fieldset 对表单中不同部分进行分组时,这个特性非常有用。在这种情况下使用标题元素是有意义的,因为这能让那些依赖于文档大纲的用户可以轻松导航至表单的对应部分。

移除的特性

在 HTML 5.2 中移除了一些元素,具体为:

  • keygen:曾经用于帮助表单生成公钥
  • menumenuitem:曾经用于创建导航与内容菜单

新的无效实践

最后,一些开发实践方式被规定不再有效。

<p> 中不再能包含行内、浮动、块类型的子元素

在 HTML 5.2 中,<p> 元素中唯一合法的子元素只能是文字内容。这也意味着以下类型的元素不再能嵌套于段落标签 <p> 内:

  • 行内块(Inline blocks)
  • 行内表格(Inline tables)
  • 浮动块与固定位置块

不再支持严格文档类型(Strict Doctypes)

最后,我们终于可以和这些文档类型说再见了!

1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">  
1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  

本文发布于掘金 https://juejin.im/post/5a5dab05518825734107edda#comment

原因是服务端未开启 NAT

解决方案

1、关闭 ocserv:

1
/etc/init.d/ocserv stop

2、开启 NAT

1
iptables -t nat -A POSTROUTING -j MASQUERADE

3、开启 ocserv:

1
ocserv -c /etc/ocserv/config

1
/etc/init.d/ocserv start

编辑距离又称为 Levenshtein 距离,指两个字串之间,由一个转成另一个所需的最少编辑操作次数。操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。(cite from baidu baike

最小编辑距离在应用中常作为一种相似度计算方法。例如DNA分析、拼写纠错(Spell correction)、命名实体抽取(Named Entity Extraction)、实体共指(Entity Conference)、识别平行网页等。

计算某两个字符串的编辑距离时,可以列下面的表:(如 apple 与 people)

0 p e o p l e
0 0 1 2 3 4 5 6
a 1
p 2
p 3
l 4
e 5

从上到下,从左到右遍历,对于每一个空来说,它的值为:

  • 左边数字 +1
  • 上面数字 +1
  • 如对应的行头与列头的字母相等,则取左上角的数字;否则取左上角数字 +1

这三者的最小值。反复遍历,如下所示:

0 p e o p l e
0 0 1 2 3 4 5 6
a 1 1 2 3 4 5 6
p 2
p 3
l 4
e 5
0 p e o p l e
0 0 1 2 3 4 5 6
a 1 1 2 3 4 5 6
p 2 1 2 3 3 4 5
p 3
l 4
e 5
0 p e o p l e
0 0 1 2 3 4 5 6
a 1 1 2 3 4 5 6
p 2 1 2 3 3 4 5
p 3 2 2 3 3 4 5
l 4
e 5
0 p e o p l e
0 0 1 2 3 4 5 6
a 1 1 2 3 4 5 6
p 2 1 2 3 3 4 5
p 3 2 2 3 3 4 5
l 4 3 3 3 4 3 4
e 5
0 p e o p l e
0 0 1 2 3 4 5 6
a 1 1 2 3 4 5 6
p 2 1 2 3 3 4 5
p 3 2 2 3 3 4 5
l 4 3 3 3 4 3 4
e 5 4 3 4 4 4 3

右下角数字为 3,则 apple 与 people 的编辑距离为 3。

代码: 根据以上自然语言的描述,可以直接利用遍历写出相应代码。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13

def literation(matrix):
for i in range(2, len(matrix)):
for j in range(2, len(matrix[0])):
up_num = matrix[i - 1][j]
left_num = matrix[i][j - 1]
left_up_num = matrix[i - 1][j - 1]
if matrix[0][j] != matrix[i][0]:
left_up_num += 1
matrix[i][j] = min(up_num + 1, left_num + 1, left_up_num)
print_matrix(matrix)
return matrix

完整代码放在 github 上

运行结果如下:

upload successful