使用Nginx配合免费的七牛帐号打造自己的CDN加速服务

初次领略nginx的神奇

之前有听说过nginx,当时只不过认为是普通的HTTP前置机一样的服务,确实没想这么好用:反向代理、URL转发、负载均衡,统统集中在这不到2M的软件中,真是让人惊叹。

下载安装nginx

最新版的 nginx 是 nginx-1.11.4
我在本地PC安装测试的是Windows版,Linux版下载和配置是一样的。
下载回来后解压,可以直接鼠标点击nginx.exe运行。(PS:运行之前保证80端口是空闲的)
运行后一闪而过,根本不知道发生了什么。这时候请打开浏览器,输入https://localhost,如果看到nginx的欢迎页面,说明刚才双击nginx.exe已经产生了作用,nginx服务器已经在后台帮你默默运行着一个超轻量级的WEB服务器。

配置nginx实现反向代理Tomcat

OK,目前我们运行的nginx还只能给浏览器响应简单的HTML、CSS、js等静态资源,下面我们通过简单几行配置,让ngnix与处理J2EE的tomcat进行连通,当客户端浏览器访问指定格式的URL时,由nginx统一接管,然后nginx主动访问本地或远程的Tomcat(本地的tomcat需运行在另一个端口上,不要与nginx冲突),得到响应后,再由nginx转发给客户端浏览器,这个流程就叫做「反向代理(Reverse Proxy)」
具体实现方法如下:

  • 设置Tomcat的端口号,避开80端口,使用8080,or any others you like.设置tomcat的conf文件夹下的server.xml,此处不表。
  • 对nginx的配置文件nginx.conf进行修改,此配置文件在nginx文件夹中的conf文件夹中:
http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        #转交所有URL给tomcat
        location / {
            proxy_pass https://localhost:8080;
        }

        #下面这个配置将URL指向resources文件夹的路径全部转发给了远程CDN
        location ^~ /resources/ {
            rewrite /resources/(.*) https://files.hexcode.cn/resources/$1;
            expires 7d;
        }
    }
}

原版的nginx.conf文件里面密密麻麻好多注释,第一次使用确实有点绕脑,也有很多名词不曾接触过,我建议立刻马上去除那些注释,去掉那些注释后,真正有用的配置脚本不超过20行,行行重要,字字玑珠。

location / {
    proxy_pass https://localhost:8080;
}

原版配置文件是用root属性,指向了磁盘路径,这里改成proxy_pass属性,指向domain+端口号组成的URL,表示所有请求使用反向代理,让nginx帮客户端浏览器去https://localhost:8080取数据。

将静态资源转移到七牛CDN上

你一定注意到了上面的配置文件,我写上了这么一段:

#下面这个配置将URL指向resources文件夹的路径全部转发给了远程CDN
location ^~ /resources/ {
    rewrite /resources/(.*) https://files.hexcode.cn/resources/$1;
    expires 7d;
}

别着急,听我慢慢道来:
我们 JBlog 的系统,将所有的js,css,fonts等文件全部放在/resources/文件夹下,当时使用SpringMVC的静态资源语法配置的,但这些资源本质上还是要通过本地Tomcat传输给客户端浏览器的,所有的带宽负载和流量计价都是消耗的本地的,如何将这一部分流量和带宽,转嫁到其他云服务上呢,使用nginx会相当方便。
一旦用了nginx,如果你在学SpringMVC时,遇到配置静态资源这一章,可以直接跳过,为什么?因为你配置好nginx后,指定的静态资源压根跑不到tomcat那边去,在nginx这边就帮你转发走了,而且只要你愿意,你可以把这些静态资源全部放到第三方CDN云服务上去加速,这样做是相当有好处的:第一,你的服务器不再传输这些大块头的CSS,JS还有fonts字体文件,节省了带宽,节约了流量;第二,第三方CDN一般带宽会比你买的VPS的带宽高很多,像七牛免费账户有5M以上的带宽给免费用户,还会帮你把文件放置全国多个节点上,让东南西北的浏览器都能极速下载这些静态资源;第三,一般我们很难在Tomcat层级指定某一个CSS文件的缓存策略,但是使用nginx以后,只要你熟悉正则表达式,你可以对任意通配的URL设置独立的缓存策略,这样客户端在访问你动态页面时你可以设置为禁止缓存,但动态页面中嵌入的CSS,你可以独立设置为缓存个一两天。
回到上面的配置文件,我们将URL中指向/resources/的所有文件,重定向到一个七牛免费域名中,同时我们使用七牛的同步软件 qrsbox将resources文件夹完整同步到云端,这样,nginx可以第一时间阻断访问本地资源的操作,让用户全部转向云端服务器下载这些静态资源。其中(.*)是正则表达式,表示任意字符出现任意次数,后面的$1是与其对应的占位值,指向了.*所指代的实际值。
加速,缓存,节约流量,降低本地服务器的带宽负载,nginx太棒了!
nginx还有更多更强大的功能,比如对于分布式服务非常重要的「负载均衡」,我们这里的JBlog项目因为就一台WEB服务器,而且基本上不会有高并发的可能性,遇到DDOS攻击机会也非常小,所以暂时用不到这个高档玩意儿,有兴趣的朋友可以继续研究。

页面中「返回顶部」图标按钮的实现

「返回顶部」现在广泛用于各大网站,尤其是页面高度比较高,干货内容比较多的网站,简直风靡一时
如何将「返回顶部」做得美观得体这是个问题,下面跟我一起来设计网站的「返回顶部」图标按钮。

首先定义一个a标签:

<a href="#" title="回到顶部" style="display:none" class="am-icon-btn am-icon-arrow-up" id="goTop"></a>

使用display:none先将其隐藏,这里应用到了AmazeUI的class名,但是我并不想将AmazeUI的css全部引入,毕竟这个页面除了返回顶部,不需要AmazeUI的其他功能。
那就自己来写这几个class的css吧:

#goTop{
    position:fixed;
    bottom:20px;
    right:20px;
    text-decoration:none;
}

这个很容易理解,固定到屏幕右下方。

.am-icon-btn{
    box-sizing:border-box;
    display:inline-block;
    width:48px;height:48px;
    font-size: 24px;
    line-height:48px;
    border-radius:50%;
    background-color:#CCC;
    color:#555555;
    text-align:center
}

这个类主要定义的边框,一个灰色背景的圆形按钮

.am-icon-arrow-up:before{
    content:"\f062";
    font-family: "FontAwesome"
}

这个是定义图标字体的,并且使用了伪类before,表示将会在a标签内部的开始位置添加一个字符\f062,这个字符来自于FontAwesome字体,对此不太了解的可以参考:https://fontawesome.io/icons/
因为并未使用AmazeUI的整个库,那么FontAwesome就要自己来定义了,借此机会熟悉一下CSS中引入新字体的方法:

@font-face {font-family:'FontAwesome';src:url('../amazeui/fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../amazeui/fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'), url('../amazeui/fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'), url('../amazeui/fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'), url('../amazeui/fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype');font-weight:normal;font-style:normal}

这些eotwoffttf文件是适配不同浏览器差异的,这些图标字体来自于AmazeUI,或者在https://fontawesome.io/icons/ 下载,体积在100KB左右,浏览器将在能成功寻找到的第一个字体库后结束寻找,一般将这个@font-face放到CSS文件的顶部,免得应用图标时找不到字体。
到此为止就设计出「返回顶部」的样式了,下面要写JS来实现其功能:

$('#goTop').click(function(){$('html,body').animate({scrollTop: '0px'}, 800);return false;});
window.onscroll = function () {
    if (document.documentElement.scrollTop + document.body.scrollTop > 100) {
        document.getElementById("goTop").style.display = "block";
    }
    else {
        document.getElementById("goTop").style.display = "none";
    }
}

代码相当简单,第一行使用jQuery的语法,在800毫秒内将html标签和body标签移动到顶部0像素,也就是最顶部。
第二段是监听页面的滚轮事件的,当页面滚轮距离顶部100像素以内时,隐藏我们的a标签,否则显示a标签。

效果如图所示:

为网站添加RSS订阅功能

#RSS标准 RSS|Atom 都是一种以XML形式描述的聚合内容文本,使得阅读者在不打开我方HTML的情况下,即能初步阅读我方的投递内容,阅读者可以使用大量的阅读工具通过一个简单的xml文件URL,订阅我方的内容,而在XML索引发生增加时,这些阅读工具大多能及时提醒阅读者我方有新内容发布。使用RSS已逐渐成为各大Blog系统的标配,诸如博客园、CSDN、WordPress等都内置了RSS生成地址。RSS可以实时主动通知订阅者,提高网站流量和用户粘性。 #为独立BLOG系统添加RSS功能 我们独立设计的BLOG如何添加RSS功能,下面以JBlog项目为例,一步一步带大家来设计。 - 阅读RSS规范文件 - 设计Freemarker的RSS模板 - 制定生成策略,编码生成rss.xml文件 - 验证生成的rss.xml并尝试订阅 ### 阅读RSS规范文件 推荐阅读 [RSS2.0标准样例](https://static.userland.com/gems/backend/rssTwoExample2.xml) ### 设计Freemarker的RSS模板 我编写的rss模板如下: ``` ${blogCommon.blogTitle} https://${blogCommon.blogUrl} ${blogCommon.blogDescription} zh-cn Copyright 2015-2016 Ding Ding ${today?datetime} www.hexcode.cn ${blogCommon.blogEmail} (Ding Ding) ${blogCommon.blogEmail} (Ding Ding) 5 https://${blogCommon.blogUrl}/resources/favicon.png ${blogCommon.blogTitle} https://${blogCommon.blogUrl} <#list articleList as article> <![CDATA[${article.title}]]> https://${blogCommon.blogUrl}/article/show/<#if article.urlName ??>${article.urlName}<#else>${article.articleId} ${blogCommon.blogEmail} (Ding Ding) https://${blogCommon.blogUrl}/article/show/<#if article.urlName ??>${article.urlName}<#else>${article.articleId} <#list article.tags as tag> ${tag.title} ${article.createDate} ``` ### 制定生成策略,编码生成rss.xml文件 系统将拦截URL为`/admin/article/**`或者`/article/admin/**`通配的路径,在所有的POST请求之后,抛出一个线程,重构根目录下的rss.xml文件,这样的生成策略保证了rss.xml对系统的性能影响最小,不用每次响应用户的RSS请求时,对数据库进行大面积扫描,又保证了每次管理员增、删、改文章时(这些操作一般使用POST方式提交),会生成一个最新的RSS文件,同时,使用后台抛出线程的方式,避免了用户在前台执行这些增删改操作后,WEB不能及时响应,影响用户体验的问题。 具体实现方式是使用的SpringMVC的拦截器,SpringMVC配置文件: ``` ``` JAVA拦截器类: ``` public class RSSInterceptor implements HandlerInterceptor{ @Autowired private IArticleService articleService; @Autowired private BlogCommon blogCommon; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO Auto-generated method stub return true; //这里一定要返回true啊,要不然全都拦截掉了,默认生成时是false,你懂的,我查错了很久,坑爹的。 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { String templateFilePath = request.getServletContext().getRealPath("/WEB-INF/views/ftl"); String outputFilePath = request.getServletContext().getRealPath("/rss.xml"); //如果对文章有POST操作,则更新一次RSS if("POST".equalsIgnoreCase(request.getMethod())){ //抛个线程来处理RSS的重构,别影响浏览器返回response给用户 new Thread(() -> { List
articleList = this.articleService.findAllForRSS(); RSSUtil rssUtil = new RSSUtil( articleList ,blogCommon ,templateFilePath ,outputFilePath); try { rssUtil.generateRSS(); } catch (Exception e) { e.printStackTrace(); } }).start(); } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub } } ``` RssUtil类: ``` public class RSSUtil { private List
articleList; private String templateFilePath; private String outputFilePath; private BlogCommon blogCommon; public RSSUtil(List
articleList,BlogCommon blogCommon, String templateFilePath, String outputFilePath){ this.articleList = articleList; this.blogCommon = blogCommon; this.templateFilePath = templateFilePath; this.outputFilePath = outputFilePath; } public void generateRSS() throws IOException, TemplateException{ Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); cfg.setDirectoryForTemplateLoading(new File(templateFilePath)); cfg.setDefaultEncoding("UTF-8"); cfg.setClassicCompatible(true); cfg.setLocale(Locale.ENGLISH); cfg.setTimeZone(TimeZone.getTimeZone("GMT")); cfg.setDateTimeFormat("EEE, d MMM yyyy hh:mm:ss z"); //因为RSS标准中,对时间格式和时区都有很明确的要求,所以这里要设置正确 Map root = new HashMap(); root.put("articleList", articleList); root.put("blogCommon", blogCommon); root.put("today", new Date()); Template temp = cfg.getTemplate("rss.xml"); File outFile = new File(outputFilePath); Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"utf-8")); temp.process(root, out); } } ``` 生成的rss.xml在根目录下,是静态文件,最后别忘了将它排除到springMVC的控制范围以外,要不然SpringMvc会将它拦截到Controller中,又影响效率了。 下面的定义放到SpringMVC拦截器之前,web.xml: ``` default /rss.xml ``` ### 验证和订阅 将项目部署到公网上,将rss.xml的URL地址提交到 https://www.feedvalidator.org/ ,即可以分析生成的rss.xml是否通过验证,如果不能通过,会有详细的错误报告,定位到行,可以反复纠正,最终通过验证后,可以得到一个奖励图标: ![](https://www.feedvalidator.org/images/valid-rss-rogers.png) 通过验证后的rss.xml可以被任何人订阅,最简单的订阅工具是foxmail,在foxmail左下方有个RSS图标,在本地就可以简单的订阅,另外还有 「[一览](https://www.yilan.io/)」,可以云端管理很多订阅源,最厉害的 Google Reader 已经成为传说。

丁丁生于 1987.07.01 ,30岁,英文ID:newflydd

  • 现居住地 江苏 ● 泰州 ● 姜堰
  • 创建了 Jblog 开源博客系统
  • 坚持十余年的 独立博客 作者
  • 大学本科毕业后就职于 中国电信江苏泰州分公司,前两年从事Oracle数据库DBA工作,两年后公司精简技术人员,被安排到农村担任支局长(其本质是搞销售),于2016年因志向不合从国企辞职,在小城镇找了一份程序员的工作。
  • Git OSChina 上积极参与开源社区