Web fonts and extension for Azure App Service

在前端开发中,图标(Icon)是一种必不可少的资源文件。从最开始的png小图,到CSS Sprites,再到近些年的Font Awesome这类的图标字体文件,搭建一个简洁大方的网站越来越容易。我的博客使用Hexo框架,搭配Next主题,整体效果我也比较满意。但当我将其托管到Azure Website上时,却发现了两个404错误,提示找不到Font Awesome的两个字体文件。
404 errors我们抛开这个错误,先来说说字体文件以及计算机系统是如何显示字符的。

图标字体文件

首先介绍一款软件——FontForge,这是一个开源的字体编辑器。我们从Windows系统的字体文件夹(C:\Windows\Fonts\)中随便找出一款中文字体,如微软雅黑,用FontForge打开,点击CJK区域的任意一个汉字,你会发现每个汉字其实就是一个矢量图形,整个字体文件就是一个以Unicode为索引的图形表,Uicode值就是每个字符的编号。类似的,其它字符如英文字母、希腊字母也都是一个矢量图形。这也是这些字符放大后不会变得模糊的原因。
Chinese characters in CJK block

现在的字体文件通常是TTF(TrueType fonts)字体,例如我们上图所示的微软雅黑字体,它所表示的字库也叫做矢量字库。与矢量字库对应的就是点阵字库了,它们的区别是早期没有图形接口的DOS系统、某些Linux终端或者一些嵌入式系统只能渲染点阵字体(Raster fonts,也称为光栅字体、位图字体),而且点阵字体放大后会变得模糊。

当我们知道字体文件就是一个图形库的索引表时,就能大致理解计算机系统是如何显示字符的了。首先我们的字符是存储在文件中,而负责处理文字的软件(Word、Notepad等)都会显式或隐式的使用一种编码方案来存储这些字符,例如ANSI、UTF-8、Unicode、GBK等等。然后当计算机系统要显式这些字符的时候,会读取这些编码值,结合当前使用的字体文件,使用这些编码值就可以在字体文件中找到对应的图形,最终这些图形通过显卡输出到图形显示器上。这也就解释了为什么当文件的编码方案设置错误时,就会出现乱码。

既然我们用到的各种字符(汉字、英文字母、阿拉伯数字等等)本质上是一个图形,为啥不把网站设计中常用的图标也放到字体文件中呢?于是Font Awesomeiconfont这之类的专门用于网页设计的图标字体应运而生。与此同时你会发现,当把图标看成是普通的文本后,font-size、color之类的文本修饰也能随手解决,再加上CSS3的支持,还能添加阴影、旋转等效果。

使用字体文件中的图标

在网页中使用Font Awesome中的图标,只需要以下两个步骤:

  1. 引入CSS文件

    1
    <link href="/lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
  2. <i><span>中引入相关的class,例如:

    1
    2
    3
    <i class="fa fa-home"></i>
    <i class="fa fa-home" style="font-size:48px;"></i>
    <i class="fa fa-home" style="font-size:60px;color:red;"></i>

    效果如下:

Font Awesome 一般和内联元素搭配使用,因此<i><span>常用于字体图标。

你不禁要问,这是如何办到的呢?答案就在Font Awesome提供的CSS里面。

1
2
3
4
5
6
7
8
9
10
11
@font-face {
font-family: FontAwesome;
src: url(../fonts/fontawesome-webfont.eot?v=4.7.0);
src: url(../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0) format('embedded-opentype'),
url(../fonts/fontawesome-webfont.woff2?v=4.7.0) format('woff2'),
url(../fonts/fontawesome-webfont.woff?v=4.7.0) format('woff'),
url(../fonts/fontawesome-webfont.ttf?v=4.7.0) format('truetype'),
url(../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular) format('svg');
font-weight: 400;
font-style: normal
}

首先你会看到一个@font-face的声明,其中font-family属性定义了这个字体的名称,src属性定义了渲染该字体需要下载的字体文件。定义两个src以及一堆url是为了浏览器兼容性的需要,format属性告诉浏览器这个字体的格式,可选的字体格式有 woff、woff2、truetype、opentype、embedded-opentype、svg等。不同的浏览器支持不同的字体格式。

browser_support_for_font_formats

然后,在class fa中引用了这个字体,并且定义了一些基本属性。

1
2
3
4
5
6
7
.fa {
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}

最后,对于每一个图标,定义了一个class,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.fa-home:before {
content: "\f015"
}

.fa-file-o:before {
content: "\f016"
}

.fa-clock-o:before {
content: "\f017"
}

.fa-road:before {
content: "\f018"
}

这里,使用:before在DOM中增加了一个伪元素,使用反斜杠\表示一个16进制数字,这个数字就是该图标在字体文件中的编码,也就是它的索引位置。

font_awesome_icons

伪类(Pseudo-classes,使用一个冒号:)与伪元素(Pseudo-elements,使用两个冒号::)是不一样的。但由于历史及CSS版本的原因,大多数浏览器同时支持用一个冒号:或两个冒号::来实现伪元素。

MIME Types

说完了字体和Font Awesome,回到我们要解决的这个问题上来。Chrome的开发者工具报了两个404错误,提示下面两个文件找不到。

  • fontawesome-webfont.woff
  • fontawesome-webfont.woff2

很显然,这两个文件在服务器上一定是有的。Azure App Service背后使用的是IIS服务器,熟悉IIS的同学可能已经猜到了,这个是因为IIS上没有添加相应的MIME Types配置。IIS中的MIME Types配置用来指示哪些文件类型被视为静态文件,从而直接发送到客户端浏览器,而不是交给背后的CGI程序或者ISAPI扩展和过滤器来做动态处理。

Azure App Service现在支持运行在Linux系统中,因此背后的Web服务器就不是IIS了。我在创建App Service的时候,选择的是Windows系统,因此背后的Web服务器是IIS。
app_service_os_version

使用web.config

最显而易见的方式是配置web.config来让IIS支持这两种文件类型。

1
2
3
4
5
6
7
8
9
10
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=".woff" />
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
</staticContent>
</system.webServer>
</configuration>

对于一个静态博客网站来说,添加一个web.config文件显得有些突兀,有点被入侵的感觉。并且对于每一个有这种问题的网站,都得添加一个web.config文件,太繁琐。有没有更好的办法呢?答案是使用Site Extension。

使用Site Extension

Azure Site Extension是App Service提供的一种平台接口,允许开发者或网站运营者对网站的运行环境做一些定制化和扩展。它的实现机制是使用XDT Transforms来修改ApplicationHost.configApplicationHost.config是IIS7以上版本中的IIS配置文件,它包含了当前机器上所有的网站、应用程序、虚拟目录、应用程序池以及一些全局的服务器配置。我们平时使用的web.config,其实就是覆盖了这个文件中的部分配置信息。当然有一些重要的配置信息是无法被覆盖的。

使用XDT Transforms来配置MIME Types也比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<location path="%XDT_SITENAME%" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">
<system.webServer xdt:Transform="InsertIfMissing">
<staticContent xdt:Transform="InsertIfMissing">
<remove fileExtension=".woff" />
<mimeMap fileExtension=".woff" mimeType="application/font-woff" xdt:Transform="InsertIfMissing" xdt:Locator="Match(fileExtension)"/>
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" xdt:Transform="InsertIfMissing" xdt:Locator="Match(fileExtension)"/>
</staticContent>
</system.webServer>
</location>
</configuration>

将上面的xml文件保存为applicationhost.xdt,打开App Service对应的scm站点,将这个文件上传到SiteExtensions目录下,重启App Service,改动就生效了。
app_service_scm_site

如果你想把这个Site Extension分享给其他人,你需要将其打包成一个NuGet包,然后将其上传到Azure官方的托管平台上。具体步骤可以参考这篇文章:Writing a Site Extension for Azure Websites

完整的代码在这里:https://github.com/johnnyqian/enable-font-awesome-site-extension

Site Extensions的托管

Site Extensions最开始的时候是托管在https://www.siteextensions.net上的。2018年2月份的时候,官方宣布将Site Extensions的托管迁移到https://www.nuget.org。原因是siteextensions.net是个低流量的网站,不值得投入大量的运维成本,因此将其合并到nuget.org

熟悉.NET的同学应该对NuGet不陌生,它是.NET生态系统中的包管理工具。类似的工具有很多,比如Java中的Maven,Node中的npm等。nuget.org是托管NuGet包的一个在线网站,它已经成为.NET生态系统一个重要的组成部分,甚至.NET中的一些BCL类库也通过nuget.org来发布,而不是跟随.NET Framework。

Site Extension的包发布到nuget.org后,如何与其它常规的NuGet包区分开呢?答案是需要在nuspec中添加如下的改动:

1
2
3
4
<tags>AzureSiteExtension</tags>
<packageTypes>
<packageType name="AzureSiteExtension" />
</packageTypes>

我的这个FontAwesome的Site Extension在这两个托管平台都可以找到:

在Azure Portal中,选择一个App Service,找到Extensions这个选项卡,点击添加,找到需要的Extension就可以了。
enable_static_web_fonts

其它常用的Site Extensions

下面两个是我经常用到的Site Extensions。

1. HTTP to HTTPs

对于启用了全站HTTPS的网站来说,这个extension就是必须的了。当用户输入的是HTTP的URL时,这个extension会将URL重定向到以HTTPS开头。在添加Extensions的页面查找Redirect HTTP to HTTPS可以找到这个extension。代码在这里:https://github.com/gregjhogan/redirect-http-to-https-site-extension

你也可以打开App Service对应的scm站点,搜索和安装extensions。
app_service_search_extensions

2. No-WWW

网站URL中的WWW前缀其实是一个历史问题,到底要不要带WWW前缀,是个有争议的问题。很多流行的站点已经不在其主站的URL中加上WWW前缀了,例如TwitterStack Overflow等。但是这个概念已经是根深蒂固了,许多人在输入URL的时候会输入WWW,因此我们需要将其重定向到不带WWW的域名地址(Bare Domain)。作为一个博客网站,我倾向于使用不带WWW前缀的URL。在添加Extensions的页面查找Remove www domain prefix可以找到这个extension。代码在这里:https://github.com/jamesharling/No-WWW

与此相反的是,很多大型的商业公司,例如GoogleMicrosoftQQ等,它们的主站都是带WWW前缀的。当用户没有键入WWW前缀时,网站会将访问链接重定位到带WWW前缀的URL。目前nuget.org上还没有人创建这样的extension,如果你有兴趣,可以尝试着创建它并将其上传到nuget.org上。

不光是网站URL中的WWW有争议,连URL中http(s)之后的两道斜杠(Forward Slashes)都是多余的。“万维网之父”——蒂姆·伯纳斯-李(Tim Berners-Lee)坦言,当初设计时留下些许遗憾。例如,万维网的网址须以http(s)://打头,伯纳斯-李认为,两道斜杠增加了用户的字符输入量(Berners-Lee ‘sorry’ for slashes),其实并没有必要。

总结

本文从我自己搭建Blog中遇到的两个404错误开始谈起,简单叙述了字体和图标字体的基本原理,以及如何在网页中使用FontAwesome这类的图标字体。最后谈到了使用Azure Site Extension来解决最开始的错误。希望对你在Azure平台上开发Web应用有所帮助。

相关链接