一筹莫展的LeetCode10 正则表达式匹配

基本上是一筹莫展的题目,果断时间再遇到估计也还是一筹莫展。

题目

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
[10] Regular Expression Matching  

https://leetcode.com/problems/regular-expression-matching/description/

* algorithms
* Hard (25.59%)
* Total Accepted: 333.7K
* Total Submissions: 1.3M
* Testcase Example: '"aa"\n"a"'

Given an input string (s) and a pattern (p), implement regular expression matching with support for '.' and '*'.


'.' Matches any single character.
'*' Matches zero or more of the preceding element.


The matching should cover the entire input string (not partial).

Note:


s could be empty and contains only lowercase letters a-z.
p could be empty and contains only lowercase letters a-z, and characters like . or *.


Example 1:


Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".


Example 2:


Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".


Example 3:


Input:
s = "ab"
p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".


Example 4:


Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore, it matches "aab".


Example 5:


Input:
s = "mississippi"
p = "mis*is*p*."
Output: false

解读

基本就是要考虑遇到.*以及*的时候。

举个例子来说,

1
2
s = "mississippi"
p = "mis*is*p*."

我们大可跳过前面不是特殊情况的完全匹配的部分,直到

1
2
s = "mi|ssissippi"
p = "mi|s*is*p*."

因为*可以匹配0到多个前面的字符s。所以,我们可以去试着递归匹配

1
2
3
4
5
6
7
8
9
s = "ssissippi"
p = "is*p*."


s = "sissippi"
p = "is*p*."

s = "issippi"
p = "is*p*."

所以基本流程是

  1. 当p长度为0,检查s是否长度为0,是的话返回true,否则返回false。
  2. 当p的长度为1, 检查s的长度是否为1,如果s长度为1,并且s和p相同亦或者p为”.”,则返回true,否则返回false。
  3. 当p[1]不为*的时候递归匹配s[1..-1] p[1..-1]
  4. 当p[1]为*的时候,这个时候就麻烦了。因为*可以匹配0个到多个p[0],所以我们得做一次循环,在循环中递归匹配。

Ruby 解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def is_match(s, p)
return s.length == 0 if p.length == 0
return s.length == 1 && (s[0] == p[0] || p[0] ==".") if p.length == 1
if p[1] != "*"
return false if s.length == 0
return (s[0] == p[0] || p[0] ==".") && is_match(s[1..-1], p[1..-1])
end
temp = s
while s.length != 0 && (s[0] == p[0] || p[0] == ".")
result = is_match(s[1..-1], p[2..-1])
return true if result == true
s = s[1..-1]
end
return is_match(temp, p[2..-1])
end

后话

不看答案我是真的不会,看了答案我还是懵的。

leetcode6 ZigZag Ruby解法

题目

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
[6] ZigZag Conversion  

https://leetcode.com/problems/zigzag-conversion/description/

* algorithms
* Medium (32.97%)
* Total Accepted: 352.2K
* Total Submissions: 1.1M
* Testcase Example: '"PAYPALISHIRING"\n3'

The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)


P A H N
A P L S I I G
Y I R


And then read line by line: "PAHNAPLSIIGYIR"

Write the code that will take a string and make this conversion given a number of rows:


string convert(string s, int numRows);

Example 1:


Input: s = "PAYPALISHIRING", numRows = 3
Output: "PAHNAPLSIIGYIR"


Example 2:


Input: s = "PAYPALISHIRING", numRows = 4
Output: "PINALSIGYAHRPI"
Explanation:

P I N
A L S I G
Y A H R
P I

解题思路

这道题目虽然属于中级难度,但是其实非常简单。
读清题意之后可以知道,我们可以把字符串按照长度分组。每个组内的元素的行号都可以根据这个元素在每个小组之内的位置推测出来。

以下面的例子来说

1
2
3
4
5
6
s = "PAYPALISHIRING", numRows = 3

result is
P A H N
A P L S I I G
Y I R

首先,我们可以设定一个二维数组result来存放结果。

很显然,我们期待的结果是result = [[P,A,H,N],[A,P,L,S,I,I,G],[Y,I,R]],
这个问题就转换为获取s中的index为i的元素,我们求要把s[i]放入0到numRows的哪个子数组中。

很容易发现,每个|/可以分为一组,比如PAYP, ALIS,HIRI, NG

这个组的长度也很好求,就是两列,第一列和row相当长度,第二列则去掉首位,所以是numRows -2

所以我们可知每个组的长度为2*numRows - 2

假设每个组的长度为split_length, s[i]所在组内的位置positioni%split_length, 这个位置有两种情况

  1. 这个元素在| 上。 这个时候, 且position < numRows, 我们把s[i] 放入 result[position]组中。

  2. 这个元素在/ 上。 这个时候, 且position > numRows, 我们把s[i]放入 result[numRows- 1 - (numRows - 1 - position)] 组中。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# @param {String} s
# @param {Integer} num_rows
# @return {String}
def convert(s, num_rows)
return s if num_rows == 1 || s.length == num_rows
split_length = 2 * num_rows - 2
result = []
for i in 0..s.length-1
position = i % split_length
if (position < num_rows)
result[position] = result[position] || []
result[position] << s[i]
else
result[2* num_rows - position - 2] = result[2* num_rows - position - 2] || []
result[2* num_rows - position - 2] << s[i]
end
end
return result.flatten.join
end

ok, pass

1
2
3
4
➜  java_learning leetcode submit 6.zigzag-conversion.rb
✔ Accepted
✔ 1158/1158 cases passed (68 ms)
[WARN] Failed to get submission beat ratio.

最长回文字符串算法-Manacher’s Algorithm 的Ruby解法

背景

Manacher’s Algorithm 是将最大回文字符串算法的复杂度降低到O(n)的算法。

原题来自LeetCode的第五题。

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
➜  my_blog git:(master) ✗ leetcode show 5
[5] Longest Palindromic Substring

https://leetcode.com/problems/longest-palindromic-substring/description/

* algorithms
* Medium (27.79%)
* Total Accepted: 632.5K
* Total Submissions: 2.3M
* Testcase Example: '"babad"'

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:


Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.


Example 2:


Input: "cbbd"
Output: "bb"

动态规划解法

假设已经有一个判断字符串是否回文的函数, i = 0, j = s.length

  1. 如果这个字符串s已经是回文,则直接返回这个字符串。

  2. 如果这个字符串s不是回文,我们考虑去寻找两个子字符串的最大回文。s[i+1..j], s[i..j-1]

于是有了以下的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def longest_palindrome(s)
return s if is_palindrome(s)
i = 0
j = s.length - 1
s1 = longest_palindrome(s[i+1..j])
s2 = longest_palindrome(s[i..j-1])
return (s1.length > s2.length ? s1 : s2)
end


def is_palindrome(s)
i = 0
j = s.length - 1
while i < j
return false if s[i] != s[j]
i += 1
j -= 1
end
return true
end

然后提交之后,Leetcode说,不好意思,超时了。。。两年之前通过的解答为啥超时了?

看了一下,testcase从原来的94增加到了103。。。

之前解题时候只图通过,而没仔细想过优化,上网一找,回文问题还是有固定的最优解决方式的。

马拉车算法

马拉车算法太复杂了。不看答案估计一辈子想不出来,光看思路也是十分费解。

我自然是不打算在这里写一遍思路。单纯引用别人已经整理好的东西即可。

比如这位大哥的解说是我最大致能够看懂的。

点击这里

网上大多数文章并没提供Ruby的马拉车算法解法。这里贴一下我根据思路的整理。

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
def longest_palindrome(s)
return s if s.length < 2
s = "#" + s.split('').join("#") + "#"
i = 0
c = 1
j = 2
r = 2
p = []
p[0] = 0
p[1] = 1
while j < s.length
i = 2*c - j
if r - j <= p[i]
p[j] = r - j
while ( s[j + p[j] + 1] == s[j - p[j] - 1])
p[j]+=1
end
if p[j] + j > r
c = j
r = j + p[j]
end
else
p[j] = p[i]
end
j = j + 1
end
max = p.max
max_index = p.index(max)
return s[max_index-max..max_index+max].gsub("#", "")
end
1
2
3
4
➜  ruby leetcode submit 5.longest-palindromic-substring.rb
✔ Accepted
✔ 103/103 cases passed (88 ms)
[WARN] Failed to get submission beat ratio.

OK, 提交成功。

后话

最近又开始刷题了,啊哈啊青春。

她们

(本故事纯属虚构)

“你还在东京吗? 我来旅游了,现在在涩谷,下班的话一起吃个饭呗。”

盯着RabbitMQ里面堵塞的一百八十万条Message一筹莫展的我,被突然弹起的微信消息打断。

手指停留在输入框上,想要回复些什么,
脑海里却浮现不出这个人是谁。

下意识去翻看以前的聊天记录却发现换手机的这两年来完全没有说过一句话。

昵称xyk,头像白纸一张,性别保密,朋友圈只开放三天,当然看过去就是空空如也。

大学时代的同学?高中同学?直接问本人大概也是失礼吧,被manager喊进会议室之前,匆匆忙忙回复了个“哦”字,就把这回事忘了。

下班之后几乎已经走到车站的检票口,又收到了xyk的消息。

“我在车站这里有个车厢和小狗像的这里,你还要多久?”

这才想起来下午有那么两句对话之后漫不经心被理解成了晚上要一起吃饭了。

可是,你是谁呀?

等反应过来不知不觉坐上了和回家相反的方向,走出车站门口的时候头上已经是大盛堂书店外面罗拉捧着比特币的广告牌了。

“学长,在这里!” 是个女生的声音,却看到个小帅的眼睛男像我招手。

似曾相识。该是大学里的后辈? 经常一起玩的后辈也就那么几个为什么我对眼前这位帅哥一点印象都没有。

就在这时,背后被猛拍了一下。回头的一瞬间,

脸上的汗珠和车站的喧嚣瞬间凝固住了,下意识想揉一下眼睛却被眼镜框狠狠地刮了一下。

是她。

和很多年前一模一样。

碎花布裙和马尾辫,垫着脚尖比我高那么一点的瘦瘦的身段,下手拍人还是那么狠。

上大学的时候,和她的冲突从未断过。

从刚开始不熟悉的时候,硬要死皮赖脸在食堂挤在一桌吃饭被赶走,到后来故意路过她们宿舍门口的时候,看见她穿着吊带背心吃雪糕的样子,险被砸中花盆。

手机还不是很流行的时候,宿舍里都挂着一个电话机,总是她第一个接电话,刚说完“喂”就被挂断的次数也是不计其数。

眼前的男生大概是她的现任男友吧,据说是同校后辈,不是那么面熟。却感觉很是般配。

已经不记得晚上吃的是哪家店的什么东西,味道怎么样了。她的男友很大方地请了客。
只是吃东西的时候聊起以前的事情她又陷入了一贯的霸道和自豪。

“哈哈我有重要消息宣布!” 她的脸上是酒后的红晕。

她拿出手机,翻开相册,一排婚纱照映入眼帘。新娘洁白的婚纱和新郎俊美的西装。她捧着一束花,站在新娘的旁边,他的男友站在了新郎的旁边。

“看吧,我再也不用当护花使者了!” 她笑着说道。

新娘幸福的微笑和新郎自信满满的笑容看起来是那么和谐,那么美好,那么让人憧憬。

然而,我始终不敢看一眼新娘的眼睛。

大概是怕在手机屏幕的反射里看到自己现在的样子。

(完)

难道大家都没有发现OS 10.15下Chrome76的豆腐块吗

背景

先讲一个笑话。

第一反应是什么?

什么福利网站吗?

这个是github上一个中文repo的readme的第一二行。

原文是“一些高质量电子书资源分享”

这些变成方块无法显示的文字成为豆腐块,仔细对照可以发现豆腐块里的文字都是简体字特有的文字。

这个是为什么?

打开某中文网页,发现完全没有任何豆腐块。

这个是OS的问题吗?

用safari打开同样网站,简体中文显示也完全没问题。

那这个是chrome的问题咯?

用OS10.13下的chrome76打开同样网站,简体中文显示也是没有问题。

那就是chrome76和OS 10.15的兼容性问题了。

解决方法

OS10.15本来就是未公开的beta版本,chrome有类似bug可能也算正常不过。如果是自己host的网站,可以检查网页的lang设定。将默认缺失的设定加上lang="zh-cn"即可。

如果是别人的网站呢?

hmm,我也不知道。

我的SMTP服务为什么挂了

背景

今天打开Mailer的邮箱,发现很久以前给人回复的邮件静静地躺在发送失败的草稿箱里。打开发送,提示输入邮箱密码却怎么也发不出去。

还好我要回复的只是帮人内推的简历,也不知道发我简历的那位哥们会不会恨我。
要是一封情书可能你就真的直到别人快结婚了也不知道自己当年为什么被无视了是吧。

当然说到底,发送邮件有问题肯定是要去修复的。谁让我自架邮件服务器自找麻烦呢。
(简单算一笔帐的话,这个邮箱价值不菲。不仅包含每年域名的更新费7000大洋,还包括每个月的服务器费用1000大洋。)

问题排查

首先得去找验证失败的Log。

1
tailf /var/log/mail.log

试着去登陆等待后台滚动出log来。

1
2
3
Aug  1 23:39:22 bocchi postfix/smtpd[17310]: warning: SASL authentication failure: cannot connect to saslauthd server: No such file or directory
Aug 1 23:39:22 bocchi postfix/smtpd[17310]: warning: SASL authentication failure: Password verification failed
Aug 1 23:39:22 bocchi postfix/smtpd[17310]: warning: xxxxx[xxxx.xxxx.xxxx.xxxx]: SASL PLAIN authentication failed: generic failure

Postfix 验证的时候使用到了Saslauthd。看问题应该是Postfix找不到SASL的进程文件目录。

那么,Saslauthd在跑吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo systemctl status saslauthd

● saslauthd.service - LSB: saslauthd startup script
Loaded: loaded (/etc/init.d/saslauthd; bad; vendor preset: enabled)
Active: active (running) since Mon 2018-12-03 13:22:36 JST; 7 months 28 days ago
Docs: man:systemd-sysv-generator(8)
Process: 999 ExecStart=/etc/init.d/saslauthd start (code=exited, status=0/SUCCESS)
CGroup: /system.slice/saslauthd.service
├─1110 /usr/sbin/saslauthd -a pam -c -m /var/spool/postfix/var/run/saslauthd -n 5
├─1111 /usr/sbin/saslauthd -a pam -c -m /var/spool/postfix/var/run/saslauthd -n 5
├─1112 /usr/sbin/saslauthd -a pam -c -m /var/spool/postfix/var/run/saslauthd -n 5
├─1113 /usr/sbin/saslauthd -a pam -c -m /var/spool/postfix/var/run/saslauthd -n 5
└─1114 /usr/sbin/saslauthd -a pam -c -m /var/spool/postfix/var/run/saslauthd -n 5

好像也没啥问题。至少,我敢确定以前我是可以好好发送邮件的。

谷歌了一下类似问题,貌似可以归结到postfix在chroot下运行的时候,会去寻找/var/spool/postfix/var/run/saslauthd。而在非chroot的情况下会去寻找/var/run/saslauthd

于是去查看postfix的config。

1
2
smtp inet n - y - - smtpd
`

y代表chroot模式。是我眼花了吗? 那岂不是没有任何问题,并且/var/spool/postfix/var/run/saslauthd 也是存在的。

抱着死马当活马医的心态,建立一下从/var/run/saslauthd/var/spool/postfix/var/run/saslauthd的symlink。

再试着发送邮件。”刷~”地一声,如同十年的便秘一样瞬间发送成功了。

继续谷歌,看到很多人抱怨说postfix不管怎么配置chroot总是默认不使用,于是我也懒得追究了。

事情的真相

从一开始配置postfix的时候,应该是抄了哪边的manual。于是神使鬼差使用了ln -s /var/spool/postfix/var/run/saslauthd /var/run/saslauthd

然而,现在使用的服务器的运营商中间因为机房迁移断电重启过一次,导致我的sysmlink失效了。

啊sysmlink重启还能失效? 对的,因为这个目录是/var/run。。。

最近我又收到服务器运营商的maint邮件说要有宕机重启了。这次我应该知道怎么做了。

Model的定义优先还是DB的schema优先?

背景

最近负责把项目的代码从PHP5.6升级到PHP7.3。
层出不穷的问题请见前几篇博客。
当然之前遇到的大多数是PHP版本升级后的兼容问题,
而这次遇到了Yii重构导致现行代码出错的问题。

找不到的 ActiveModel Property

PHP5.6 => PHP7.3, yii1.1.15 => Yii1.1.21。
升级之后在Error Log观察到了类似Property "MyselfDefinedModel.id" is not defined的例外。

显然翻查DB的schema,我们在对应的MyselfDefinedModel的Table中并没有设定id作为Primary Key。
甚至没有id这个column,那么这个Id是怎么来的呢?

经过一番寻找,
在更新后的Yii1.1.21中我们发现了一些微妙的变化。

1
2
3
4
5
6
7
8
9
10
11
12
// yii 1.1.21
// framework/yiilite.php
if(($modelPk=$model->primaryKey())!==null || $table->primaryKey===null){
$table->primaryKey=$modelPk;
...
}

// yii 1.1.15
if($table->primaryKey===null) {
$table->primaryKey=$model->primaryKey();
...
}

可以发现,在新版本的Yii中,无论$table->primaryKey是否为null,我们始终会用$model->primaryKey()的结果去赋值给$table->primaryKey

然而我们自定义的MyselfDefinedModel 继承自BaseModel, 而我们在BaseModel中定义了public function primaryKey() {return 'id'}的方法。

于是,没有做任何override的MyselfDefinedModel自动继承了primaryKey()的方法,当Acitverecord去寻找期PrimaryKey的时候,新版本的Yii便让这个值成为了PrimaryKey。

1
2
3
4
5
6
7
8
9
10
class BaseModel extend CActiveRecord
{
...
public function primaryKey() {return 'id'};
...
}

class MyselfDefinedModel extend BaseModel
{
}

解决方法

不更改Yii自身的情况下,我们可以在MyselfDefinedModel中overridepublic function primaryKey() 然后返回正确的primary Key。

1
2
3
4
5
6
class MyselfDefinedModel extend BaseModel
{
...
public function primaryKey() {return 'other_id'};
...
}

毕竟,忘记在子类中override PrimaryKey本身就存在问题。
但是,一旦修改起来,PrimaryKey不是id的表出奇的多,那不如去把Yii改了?

如此产生了以下的新问题,Yii的关于获取PrimaryKey的变化,会不会是一次失败的重构?

后话

于是屁颠屁颠跑到了github的yii1.1 repository,提出了issue。表示自己怀疑这段重构有问题。

并表示具体做法是把之前某个commit的重构给revert。从而让DB schema 优先于model中的设定。

很幸运的是这个问题不过10分钟就收到了回复。有人站起来指出model配置优先于DB schema的必要性。

简而言之是model中的public function primaryKey() 方法是唯一可以重新指定primaryKey的地方,所以必须要优先于DB schema。

嗯但是DB schema 放着好好的不用,到底有哪些情况下我们需要重新指定我们的PrimaryKey呢?

于是,当我们需要对PrimaryKey加一些校验的时候,我们希望动态生成这些校验值附加到我们自定义的PrimaryKey上,然后通过校验的PrimaryKey再去访问数据。(具体联想到GlobalId也可以用这种方法实现)

问题Close。以上。

PHP7.3关于session_id()的变化

PHP7.3 以前

首先看下面一段code。

1
2
3
session_save_path("/your_session_path");
session_start();
echo(session_id());

直观上讲,我们定义session的存放路径。开始一个session,然后去获取这个session的id。

如果是一个新的session,我们将获取到新生成的session_id, 作为PHPSESSID存放在Cookie之中。

如果传来的Cookie中已经存在PHPSESSID,我们期待输出其中的内容。

就算,

我指定的路径根本就没有全线放置Session。

PHP7.3

到了PHP7.3情况就发生了变化。

如果,我们对my_session_path有执行读写的权限,那一切OK,该生成新的就生成新的,改读取PHPSESSID就读取。

但是如果我们没有my_session_path的权限,那就麻烦大了。我们将获取不到session_id,获取的结果为空字符串。

后记

这个问题让我发现了长久以来其实后台的某个工具一直没有读写session path的权限。

Cannot Change Session when sesion is active

背景

最近把准备把项目直接从PHP5.6 升级到7.3。
升级过程中遇到个比较棘手的问题,那就是出现了大量Cannot change session name when session is active的Warnning。

剖析

我们通常使用session_name($name)来改变session的名称(默认PHPSESSIONID)。而这个warning出现在我们在session_start()之后调用session_name

1
2
3
4
5
6
7
8
9
10
11
12
<?php

session_start();
session_name("hoge");

//echo session_name()."<br/>";
//echo session_id()."<br/>";

session_regenerate_id(true);

echo session_name()."<br/>";
echo session_id()."<br/>";

上面这段,如果使用PHP5.6,则会产生两个cookie。一个是PHPSESSIONID,另一个则是我们变更之后的hoge。echo输出的内容为hoge

1
2
d78ca52754275ef448ea8bff2ef4c051
hoge

环境切换到PHP7.3,我们发现出现了Warnning,然后查看Cookie,我们只看到了PHPSESSIONID

1
2
3
Warning: session_name(): Cannot change session name when session is active in /Users/ning.a.li/workplace/operations/tmp/test.php on line 4
PHPSESSID
tqa2u87v89ul1a7k7srrn8fhf5

原因我知道了,那么为什么会出现这种问题呢?

原来,现有的code在我们开始load framework之前有对session和cookie进行操作,而进入framework之后,由于framework并不知道我们已经建立了session,framework尝试根据config建立session的时候就会产生Warning。

那么如何解决这个warnning呢?

首先我们得考虑是否真正有必要变更session_name

通常,当我们有两个web app运行在同一个domain下面的时候,我们可以通过设置不同的session_name来区分两个不同app的用户。

但是这种情况往往会造成不必要的麻烦。

就目前项目的code来说,修正的方法很简单,我们在framework中对session_name相关的调用添加@即可。即类似@session_name(my_session)

后记

虽然网上多数人抱怨这个Warning设计的莫名其妙,多数情况下,相比还是现有的Code的设计更加不合理吧。

PHP

什么时候需要ProxyPreserveHost ?

背景

在apache httpd 中设置Reverse Proxy的时候通常我们有如下配置

1
2
3
# https://myfrontend/images => https://mybackend/images
ProxyPass /images https://mybackend/images
ProxyPassReverse /images https://mybackend/images

ProxyPass不多说了,ProxyPassReverse是把response中的Location, Content-Location, URI等header由mybackend替换成myfrontend
今天配置服务器的时候,发现如果以上配置情况下,如果还有ProxyPreserveHost on配置,则我们的https://myfrontend/images的response变成了403。这是为什么呢?

ProxyPreserveHost 的作用

ProxyPreserveHost 的作用如文档上写的,很简单,当我们设置ProxyPass /images https://mybackend/images的时候,通过myfrontend服务器发往mybackend的request的Host Header应该是Host: mybackend
而如果ProxyPreserveHost on,则我们的request的header则回变成Host: myfrontend

文档上说,我们大多数情况下不需要这个配置。除非在mybackend上配置了大量的vhost,我们需要通过识别原来request的Host header来判断request交给哪个vhost处理。

这种说法略抽象,让我们在稍微深入考虑一下。

当我们的mybackend的服务器上,配置有myfrontend的vhost的时候,使用ProxyPreserveHost on就可以把我们的request交给myfrontend的vhost来处理。

这个时候,这个配置在mybackend上的vhost myfrontend是无法外部的DNS来resolve的。外部的DNS把myfrontend已经resolve到了我们的Reverse Proxy服务器。

所以当ProxyPreserveHost on 的时候就会有如下流程。

1
https://myfrontend/images(user request) => myfrontend(Reverse proxy to mybackend server with Host: myfrontend ) => mybackend(match with vhost setting as myfrontend)

因为在大多数情况下,我们通常不会在mybackend的服务器上配置和Reverse Proxy一模一样的vhost,所以就如文档中所说,我们大多数情况下不需要此配置。

后话

在web的世界里,当我们的目标服务器上配置有DNS无法resolve的vhost的时候,通常我们可以用ip+port,设置Host header为该无法resolve的vhost的方法来访问。
这个容易忽视的web的实质面貌,在本次讨论的问题中体现的淋漓尽致。