起步
子域一词语是我学 flask 首次接触到的,当时书上说了这么个功能,没有细讲,于是我长久不知道它是做啥的。再后来一路磕磕碰碰,像收集龙珠似的找到些子域的意义,今天就和着 Iris 的 Subdomain 来说说吧。
什么是子域
常见如 baidu.com 就是一个域名。域名等级是从右往左,com 是顶级域名,baidu 是二级域名。baidu.com 的子域名就可以是 a.baidu.com、b.baidu.com 之类。百度百科的域名就是子域应用的典型案例:baike.baidu.com。
子域:破除浏览器限制
事实上,不同浏览器都会有一个限制,就是对同一个域名并发请求数的限制。我们可以通过实验来理解这一现象。
准备图片素材
创建目录 img,随意放入一张 jpg 格式的图片,建议图片大小不低于 500k,这样有利于后面的观察。我还写了一个简单脚本,用来批量生成图片:
file=$1
for i in {1..30}; do
if [ $i -lt 10 ]; then
i="0$i"
fi
cp $file "$i.jpg"
done
用法:
# generate.sh 是脚本名
# origin.jpg 是 img 目录下的图片名
$ sh generate.sh origin.jpg
脚本成功运行后应该会有:01.jpg、02.jpg ……30.jpg 这样的图片产生。
准备 html 文件
在项目根目录下创建目录 html,在 html 目录下创建 index.html 文件。写入内容如下:
<!-- index.html -->
...
<body>
<img src="http://ying.com:8080/static/01.jpg" />
<img src="http://ying.com:8080/static/02.jpg" />
<img src="http://ying.com:8080/static/03.jpg" />
...
<img src="http://ying.com:8080/static/29.jpg" />
<img src="http://ying.com:8080/static/30.jpg" />
</body>
利用 Iris 构建后台代码:
func main() {
app := iris.Default()
// 注册 html 文件
htmlEng := iris.HTML("html", ".html")
htmlEng.Reload(true) // 开启自动加载
app.RegisterView(htmlEng)
app.Get("/", func(ctx iris.Context) {
ctx.View("index.html")
})
// 静态文件的视图函数
app.Get("/static/{file}", func(ctx iris.Context) {
file := ctx.Params().Get("file")
// 构建图片路径
filepath := strings.Join(
[]string{".", "img", file},
string(os.PathSeparator),
)
fd, _ := os.Open(filepath)
defer fd.Close()
// 读取图片所有内容
stream, _ := ioutil.ReadAll(fd)
ctx.Write(stream)
})
app.Run(iris.Addr(":8080"))
}
由于是本地测试,还需要修改 hosts 文件。我现在手上的是 mac 电脑,hosts 文件在 /etc 目录下(win 电脑不同,请自行百度)。在 hosts 文件中追加:
127.0.0.1 ying.com
设置 chrome 浏览器
请根据下图对 chrome 浏览器进行配置。
- Fast 3G 的意思是将环境设置为 3g 网速,这样有利于我们观察实验现象。
观察现象
现在浏览器访问:http://ying.com:8080。
在 network 中可以观察到,图片的请求总是 6 个为一组。你可以刷新浏览器多尝试几次。
现在在 waterfall 右边右键鼠标,开启 Domain,也就是显示每次浏览器请求的域名。
修改 index.html 文件。修改内容如下:
<body>
<img src="http://z.ying.com:8080/static/01.jpg" />
...
<img src="http://z.ying.com:8080/static/10.jpg" />
<img src="http://t.ying.com:8080/static/11.jpg" />
...
<img src="http://t.ying.com:8080/static/20.jpg" />
<img src="http://y.ying.com:8080/static/21.jpg" />
...
<img src="http://y.ying.com:8080/static/30.jpg" />
</body>
- 01.jpg - 10.jpg 对应域名 z.ying.com
- 11.jpg - 20.jpg 对应域名 t.ying.com
- 21.jpg - 30.jpg 对应域名 y.ying.com
在 hosts 文件中追加内容如下:
127.0.0.1 z.ying.com
127.0.0.1 t.ying.com
127.0.0.1 y.ying.com
浏览器继续访问:http://ying.com:8080。可以观察到的现象是,每个域名下都会有 6 个请求。
现在结论就出来了,尽管访问的是同一 IP,但通过不同域名,解决了浏览器最大并发请求数的限制问题。诚然,我们没有真正做到破除浏览器的限制,但实现了曲线救国。
子域:url 模块化
当系统庞大到需要模块化时,一般会用 url path 对 url 进行模块划分。假设域名是 ying.com,告警模块相关 url 可以是 ying.com:8080/alert/...,用户中心相关 url 可以是 ying.com:8080/user/...。虽说没什么问题,但似乎不太优雅(个人主观意识),如果 url 划分方式是 alert.ying.com,user.ying.com 感觉会好许多。另一方面,借助二级域名,实现模块化多机部署要简单许多。
静态子域
静态子域表示知道子域的具体名称,如 baike.baidu.com 子域部门就是 baike。Iris 实现子域的方式有两种,第一种是 Subdomain 方法。
Subdomain(subdomain string, middleware ...Handler) Party
- subdomain 表示子域名称。
- middleware 表示子域匹配成功后的操作,每个 middleware 中必须有
ctx.Next()
,否则不能正常匹配之后的路由。可以省略。
现在实现 alert.ying.com 与 user.ying.com 子域访问。
func main() {
app := iris.Default()
alertSubdomain := app.Subdomain("alert")
userSubdomain := app.Subdomain("user")
// 系统首页
app.Get("/", func(ctx iris.Context) {
ctx.HTML("ying index")
})
// 告警模块首页
alertSubdomain.Get("/", func(ctx iris.Context) {
ctx.HTML("alert module")
})
// 用户模块首页
userSubdomain.Get("/", func(ctx iris.Context) {
ctx.HTML("user module")
})
app.Run(iris.Addr(":8080"))
}
hosts 文件追加以下内容:
127.0.0.1 ying.com
127.0.0.1 alert.ying.com
127.0.0.1 user.ying.com
现在,你可以通过访问以下链接来感受区别:
第二种实现子域方式需要借助 Party。Party 方法在讲 分组路由 时提到过,用它来实现子域仅需要 path + "." 即可。上述代码可修改成如下:
...
alertSubdomain := app.Party("alert.")
userSubdomain := app.Party("user.")
...
事实上,在 Iris 源码中可以看到,Subdomain 方法也是借助 Party 实现的。
// Iris 源码
func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party {
...
if l := len(subdomain); l < 1 {
return api
} else if subdomain[l-1] != '.' {
// "user" -> "user."
subdomain += "."
}
return api.Party(subdomain, middleware...)
}
动态子域
又叫做通配符子域。例如前面说到的 alert.ying.com 与 user.ying.com,对静态子域来讲它们不是一类域名,但对动态子域来讲它们是一类域名。最常见的应用就是 github 上挂载静态博客,在我的账号上自动生成域名 youguanxinqing.github.io,一位朋友则是 tflins.github.io 。
借助 Iris 我们可以实现这一特性。
func main() {
app := iris.Default()
userSubdomain := app.WildcardSubdomain()
// *.ying.com:8080
userSubdomain.Get("/", func(ctx iris.Context) {
// 获取子域名称
subdomain := ctx.Subdomain()
html := fmt.Sprintf("hello, %v", subdomain)
ctx.HTML(html)
})
app.Run(iris.Addr(":8080"))
}
hosts 文件追加内容如下:
127.0.0.1 tflins.ying.com
127.0.0.1 youguanxinqing.ying.com
- 子域名必须在 hosts 文件中配置过才能正常访问。
Party 依然是可以替代 WildcardSubdomian() 的存在。
// = app.WildcardSubdomain()
userSubdomain := app.Party("*.")
尽管 WildcardSubdomain 也是通过 Party 实现功能,从性能上说,直接调用 Party 也会更快,但不论是 Subdomain 还是 WildcardSubdomain 中都有一些校验逻辑用来保护程序。因此最好是该静态就用 Subdomain,该动态就用 WildcardSubdomain。
域名重定向
在访问百度时,我们发现不论是 baidu.com 还是 www.baidu.com 跳转到的都是同一个界面,这对 Iris 框架来说 so easy。
现在我们把所有请求 http://ying.com:8080/path?query 都重定向到 http://www.ying.com:8080/path?query。代码如下:
app := iris.Default()
www := app.Subdomain("www")
// http://ying.com:8080 -> http://www.ying.com:8080
www.Get("/", func(ctx iris.Context) {
ctx.HTML("www index")
})
// http://ying.com:8080/user -> http://www.ying.com:8080/user
www.Get("/user", func(ctx iris.Context) {
ctx.HTML("www user")
})
// 重定向
app.SubdomainRedirect(app, www)
// 必须显示绑定域名
app.Run(iris.Addr("ying.com:8080"))
- 函数签名:
SubdomainRedirect(from, to Party) Party
。 from
可以是动态域,但to
不可以(app.SubdomainRedirect(app.WildcardSubdomain(), www)
)。- 当
to
不是根域(app)时,from 可以是根域。反之依然。
hosts 文件追加如下内容:
127.0.0.1 ying.com
127.0.0.1 www.ying.com
感谢
- 参考 https://github.com/kataras/iris/wiki/Routing-subdomains
- 参考 https://github.com/kataras/iris/blob/master/_examples/subdomains/redirect/main.go
Note:此次实验中修改 hosts 文件,当不用时应该还原,避免真实存在的网站不能正常访问。
还不快抢沙发