windows 下搭建https + node.js + nginx

最近做一个微信小程序的时候因为要求所有请求都得是https的连接,服务器端https 倒是搭建好了,可本地测试没法进行啊,于是只能自己在本地搭建个https的服务。步骤很少,和把大象放进冰箱需要的步骤一样!只需要三步:第一步:要使用ssl,肯定需要生成证书,这里我就生成的自签名证书。 第二部:安装nginx和配置ssl。第三部:用nginx反向代理到node服务端口

Read More

Top
Top
Top

Hexo-lazyload-image图片懒加载

最近在看Google Chrome新出的一个API是无意间想到了对图片的懒加载,后来想想自己的网站还不支持呢,索性花了些时间让网站给支持上了,并发现Hexo上还没有一个懒加载的插件,又倒腾着写了个hexo的插件hexo-lazyload-image,并发布到NPM上供大家使用,从这几天下载数来看看来大家还是很有这个需求 :)

Read More

Top

Javascript中原型链继承的简单理解

以前对于Javascipt中的继承,大部分只是基于代码层面,理论理解虽然看了很多,总是当时理解了过几天确又忘了怎么的了。这两天又看了一遍《Javascrip面向对象编程指南》,其中在说到原型链的时候有一段话解释了很多以前自己容易混淆的地方:

首先我们知道每个对象都会有一个构造器,而原型本身也是一个对象,这意味着它必然也有一个构造器,而这个构造器又会有自己的原型,于是这种结构就会持续下去,形成一个原型链。

实践出真知

理解这段话并不难,可如果没有在实践中去理解,就会像以前一样,老是记不住。

实例对象的各种属性

先看看最简单的内置对象string类型的各种构造函数(构造器)和原型,a一定是实例化的对象,而不是构造函数(构造函数一般是大写)。

image

从上面我们可以很直观的看到很多东西:

  • a是由String构造函数(constructor)创建的。
  • a的构造器函数(constructor)是有原型(prototype)的。
  • a是没有原型属性(prototype)的。
  • 神秘的proto直接是对a构造器的原型的引用。

所以如果a里有我们继承的原型属性值rating,我们平时就可以这样使用:

1
2
a.constructor === String
a.constructor.prototype.rating === a.rating

构造函数的原型

在JS中实现继承的方式有很多,而我最喜欢这种Mozilla推荐的:

1
2
3
4
5
function User(auth) {
BaseManager.call(this, auth);
}
User.prototype = Object.create(BaseManager.prototype); // IE8 不支持Object.create
User.prototype.constructor = User;

代码其实不难,也容易理解,首先是构造函数内调用基类,然后是原型覆盖,最后是构造函数重新赋值。其中最最最应该理解的是User是个构造函数而不是实例化的对象,只有构造函数是有prototype属性的,这和上面的实例化对象a是不一样的。

Top

Travis-ci自动编译部署github上的项目

在使用Hexo写完一篇博客后,都需要手动在本地编译,并生成静态文件,最后在上传至github服务器上才能发布,繁琐步骤姑且不说,万一哪天换了台电脑,没有Hexo环境的时候如何写博客呢?要是直接在github源码里写好文章后能自动编译发布就好了,好在github的好基友travis可以轻松帮我们同时实现这种CI,CD。

Read More

Top

踩过的坑--CORS跨域请求中预检(preflight)


开头

这两天在使用NodeJS Express搭建REST服务器时遇到一个很典型的AJAX跨域包含自定义请求头问题(用于身份验证),在花了大半天时间排查问题后发现自己对CORS真正的理解还很不够,尤其是pre-flight。

需求描述

服务端使用NodeJS Express搭建包含JWT身份验证的REST Full API, 客户端在获取到JWT信息之后的每次API请求头中都附带上JWT信息,完成身份验证后才能执行API操作,否则返回401错误。

代码

服务器端(CORS核心部分):

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
------ App -----
...
// Enable CORS from client-side
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials");
res.header("Access-Control-Allow-Credentials", "true");
next();
});
//parse application/json and look for raw text
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.text());
app.use(bodyParser.json({ type: 'application/json' }));
// Routes configuration
apiRoutes(app);
app.listen(port);
------- User -----
//==========================
// User Routes
//==========================
apiRoutes.use('/user', passport.authenticate('jwt', {session: false }), userRoutes);
userRoutes.get('/', user.getUsers);
userRoutes.get('/:id', user.getUser);
userRoutes.post('/', user.postUser);
userRoutes.put('/:id', user.updateUser);
userRoutes.delete('/:id', user.deleteUser);

上面的代码看起来还是那么多清晰,在PostMan 测试中附带jwt也是没有任何的问题,成功返回。
image

接下来是客户端(jquery ajax):

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
------- Core --------
function BaseManager(auth) {
this.baseApiUrl = 'http://localhost:8080/api/';
this.auth = auth;
}
BaseManager.prototype.get = function (url, successCallback, errorCallback) {
this.ajax(url, {}, 'get', successCallback, errorCallback);
}
BaseManager.prototype.ajax = function (url, data, type, successCallback, errorCallback) {
let that = this;
$.ajax({
url: url,
method: type,
data: data,
beforeSend: function (req) {
req.setRequestHeader('Authorization', that.auth.authorizationToken);
}
})
.done(successCallback)
.fail(errorCallback);
}
----------- User -------
User.prototype.getUserById = function (id, successCallback, errorCallback) {
let url = this.baseApiUrl + '/user/' + id;
this.get(url, successCallback, errorCallback);
}

永远的401

然后, 问题出现了,尽管参数是如何的对,Chrome console下总是返回让人咬牙切齿的大红色401,甚至断点都没有进入到passport的Jwt middleware下。
image

无数次的尝试,先是怀疑客户端ajax调用没对,甚至搬用最原生的ajax方法, 也怀疑过是服务端Jwt passport没写对,最后比较http请求头的时候发现了一些问题。

使用Post man在node服务器端得到的request是这样的:

image

通过浏览器ajax请求是这样的:
image

有人给我把请求头信息更改了!Authorization不见了,甚至连req.method都变成了OPTIONS,而不是GET。

罪魁祸首—预检(Pre-flight)

百思不得其解,Google相关关键词后,pre-flight浮出水面,到了这步,突然想起阮一峰的《跨域资源共享 CORS 详解》,当时只是略读,大概了解CORS中有两种请求:简单请求和非简单请求。于是又翻出来看了下,此时的情况正是属于非简单请求,会发送两次的请求,第一次就是preflight,用于请求验证, 第二次才是用户真正需要发送的请求。

对于Pre-flight权威的解读: mozilla.org

回到代码中,不巧,每次服务端捕捉到的就是这个preflight请求,然后做next,其中就包括Jwt 中间件,而因为请求头中没有Authorization这个header,Jwt就返回了401,而这个过程是在passport的JWT中自动检测的,自己写的JWT验证部分甚至都没有执行到!

解决办法

看了express cors源码后,其实把请求类型OPTIONS做个简单的过滤就好啦!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
// Enable CORS from client-side
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials");
res.header("Access-Control-Allow-Credentials", "true");
if (req.method == "OPTIONS") {
res.send(200);
}
else {
next();
}
});

结语

又想了一下为什么之前的项目一直没有这个问题,其实是因为很多框架以及帮我们实现好了,比如说.NET中的WebAPI, 在做验证的时候我们都不用去考虑需要捕捉pre-flight请求,而在express中,甚至如果我当初直接使用三方库express cors 也可以避免,但是幸运的是,因为这种偶然,我们更有机会看得更清楚这些请求的后面到底是什么。

看似简单的问题,却包括了很多需要自己去了解的东西,尤其是http各种请求头的含义,比如Content-type, Accept, 以及对应ajax应该传递的参数,最后,当然还有 Pre-flight!

Top

给Github自定义域名加上HTTPS


写在开头

随着Https越来越成为一种趋势,最近也给自己家博客弄上了高大上的https,主要是结合使用的cloudflare和七牛云(图床)使用,关键是免费!所以想把这个过程记录下来,万一有人用得到呢。(还想抽空写写对Https的原理的理解,主要目的还是总结前段时间自己对Https的学习。)

Https时代

According to Mozilla since January 2017 more than half of the Web traffic is encrypted. wiki pedia

维基百科告诉我们,自从2017年1月,超过一半的网络请求是通过加密过后的。百度也告诉站长们:

为了给用户提供一个安全可靠的网络环境,继启用https加密之后,百度搜索再次重磅推出:全面支持https页面直接收录;另外从相关性的角度,百度搜索引擎认为权值相同的站点,采用https协议的页面更加安全,排名上会优先对待。百度站长

Google 今年更厉害,从1月份开始,所有没有用Https的网站会在Chrome浏览器地址栏前面加上不安全的图标:

Beginning in January 2017 (Chrome 56), we’ll mark HTTP pages that collect passwords or credit cards as non-secure, as part of a long-term plan to mark all HTTP sites as non-secure. Google

百度2015年实现全站HTTPS,并且Google和百度都收录Https的网站并且提高其搜索排名,国外大型互联网网站基本都使用https,所以,有人说2017年是一个Https年。那既然如此,高大上的Https我们普通老百姓玩得起吗?要知道,一般的Https证书动辄也是几千块一年!

天上掉下个证书

没错,Cloudflare免费提供给证书,尽管你没有自己的服务器。Cloudflare是一个相当厉害的DNS服务商和CDN提供商,提供各种安全防范解决方案,全世界各地都有他的节点,对于国内,百度选择和他一起合作就是个很好的栗子,所以不用担心国内解析速度。所以我准备把主站的证书使用Cloudflare提供的。

再一个就是七牛云,作为博客,不可能把文章图片全都放github上,所以最好还是要有自己的图床,但是好多图片床都是不支持https的,好在七牛可以免费申请证书。所以这是第二个证书。

准备

因为自己的博客站点暂时是托管到github上的,不是自己的服务器,所以并不能使用Let’s encrypt在服务器端生成免费的证书,但好在一切都有cloudflare!

  • 有一个自己的域名(废话)
  • 自己的Github博客地址 (一般都是https://[username].github.io)
  • 注册Cloudflare
  • 注册七牛云 (如果有自己的图片床,并支持https, 可省去)

Cloudflare,主要用于域名解析,这是成功的关键!只有在他那里域名解析,他才能为我们提供多种证书服务。

开始第一步: Github 自定义域名

我们知道Github可以托管开源和私自项目(私有收费),同样,功能强大的Github也可以提供静态页面站点,默认站点是 https://[username].github.io ,对应的站点代码是在reponsitory名为[username].github.io 下,如果没有,请创建自己的默认repository,可参考官方说明

image
创建成功后,我们就成功的创建了自己的个人站点: https://troyyang.github.io 显然这还不是我们的最终目的。

接下来,打开这个repository, 定位到repo setting,绑定自己的域名,绑定完成后,我们可以看到repo代码下新加了一个CNAME的文件,换句话说,其实我们也可以直接在repo中直接添加这个文件即可,而不需要在setting中去手动设置,这个在我们静态站点发布的时候非常有用,因为每次发布后提交都会删除原有的文件,所以我们就可以在生成的文件中默认加上这个文件。

image

CNAME

1
troyyang.com

第二步:使用Cloudflare解析域名

修改默认DNS服务器

在使用Cloudflare之前,我使用的是万网(现在是阿里云)的默认DNS服务器,也就是

1
2
dns9.hichina.com
dns10.hichina.com

现在修改为Cloudflare

1
2
apollo.ns.cloudflare.com
mary.ns.cloudflare.com

个人觉得cloudflare作为DNS服务器特别快,修改了任何A记录或者其他记录会马上生效,不用再等待几个小时。

域名解析

登录cloudflare, 将域名A记录指向Github服务器地址(同时也可指定CNAME记录去加上www),绑定完成几分钟后访问troyyang.com或者www.troyyang.com 就可以访问到我们Github上那个默认的repo静态站点。这个时候可以尝试去访问https://troyyang.com 理论上是不会成功的,哈哈。
image

第三步:使用Cloudflare的 Universal SSL 证书

在Cloudflare管理页面,导航到Crypto,我们会看到SSL在Cloudflare上使用证书有三种方式: Flexible、Full、Full Strict,

  • Flexible SSL: 在访客与Cloudflare之间是加密的,从Cloudflare到自己服务器是不加密的,所以
  1. 你不需要在站点服务器上安装任何证书。
  2. 访客可以在浏览器地址栏上看到加密的图标。(证书签名来自Cloudflare)
  • Full SSL: 从访客到Cloudflare, 从Cloudflare到站点服务器都是加密的。Full 和 Full (Strict)不同之处在于Full Strict会去验证你服务器上的证书是否合法,而Full不会验证,所以你可以在你服务器上安装任何证书,包括自签名证书也是可以的。当然
  1. 访客可以在浏览器地址栏上看到加密的图标。(证书签名来自Cloudflare)
  • Full SSL (strict): 从访客到Cloudflare, 从Cloudflare到站点服务器都是加密的。你必须在你的服务器上安装有可信赖的CA证书,这个证书必须是未过期,包含有域名等信息的。同样
  1. 访客可以在浏览器地址栏上看到加密的图标。(证书签名来自自己申请的CA)

三种模式如图
image

当然,我们选择Flexible,选择后我们还需要在下面的Edge Certificates栏目中新增Universal SSL 证书(当初以为选择Flexible后就等待24小时激活就完了,可几天过去了状态一直处于initilizing certification, 问了客服后,客服帮我加上这个Universal SSL证书就好了,给他们客服赞一个)

在Edge Certificates中,点击Order SSL Certificate按钮,弹出几种证书,我们当然选择免费的那个, 然后填写证书服务的域名troyyang.com和*.troyyang.com.

image

一步一步完成后就等待了,一般情况不会超过24小时就会生效,激活后就如下图(我个人选的是Full),然后就可以尽情的访问 https://troyyang.com

image

如何你查看域名证书,细心的你除了看到Cloudflare签发的证书外,还有证书有效期只有半年,这个不用担心过期,客服小哥回复说他们会在快过期时自动延期的。

image

七牛图床Https支持

Cloudflare客服小哥在帮我加好Universal SSL证书后,回复我说你网站还包括非https的内容也就导致所谓的mixed-content 问题,也就导致我当时在地址栏还看不到那个https图标,这个是我能想到的,因为我知道我的图片还都不是https,因为图片存放在七牛云上的,赶紧回去看看七牛是怎么支持https,于是在踩了无数坑过后终于让图片支持上了。

第一步: 在七牛上使用自定义域名 (images.troyyang.com)

要使用https图床,必须得使用自定义域名(我使用的是images.troyyang.com),这个比较简单,可以参考页面,我们可以先不选择https模式,让正常http先工作。主要工作就是在Cloudflare上新增一个CNAME记录指向七牛为自己域名生成的域名地址就好了。一切正常后,我们应该可以访问任意图片比如http://images.troyyang.com/2017-05-01-hexo-2015-wordpress.jpg

要使用自定义域名,在七牛上,你必须等往账号里充至少10元,让自己成为标准用户,

第二步: 给自定义域名加上https支持

现在要给自己的自定义图片域名加上https支持,在七牛上有两种方式,一种是上传自己已有的证书,第二种是申请免费证书或者购买证书。对于第一种,除了上传自己的证书公钥以外,还需要私钥也一同上传,所以我还是选择的第二种,反正也只是一个二级域名的证书,也并不存在任何私密性的东西。

在七牛管理页面,在证书页面选择购买证书,然后选择TrustAsia的DV限免性,最关键的就是DNS的TXT验证了(验证指南),我也是在使用了三次申请后才弄明白申请失败原因: Github上默认会解析出两个IP地址

image

解决方案就是,在Cloudflare上先删除所有A记录,CNAME记录,先添加上用作域名验证的Txt记录,等证书申请成功后(大约十几分钟),在恢复A记录,CNAME记录。这个是我Cloudflare上的相关解析最终样子
image

有一点需要说明的是在七牛上如果只使用http的话,只要一个月不超过20G流量,是不会收费的,但是https是不在免费额度里面的。

Top

《CSS设计指南》笔记

选择器

  • 子元素 > : 用于选择所有给定子元素,如 .food>li
  • 后代元素: 用于选择所有子代和后代元素,如 .food li
  • 子-星 > *:用于选择所有直接子元素,而不包括后代元素. 注意:在为子元素设定垂直外边距时,只能使用 margin-top 和 margin-bottom,不能使用简写的 margin,否则会抵消用“子-星选择符”应用给这些元素 的水平外边距,如果你想进一步缩进某个子元素的内容,就应该给该子元素应用内边距如让子元素与栏边界保持一定距离

    1
    article > * {margin:0 20px;}
  • 非首位子元素 + : 这个选择符会选择除第一个之外的所有指定元 素,如

    1
    .list1 li + li {border-top:1px solid #f00;}

盒子模型

  • box-sizing:border-box 可用于避免改变内边距(边框)时导致整个盒子尺寸变化(width此时只是代表内容的宽度),
  • .Inner 另一种解决盒子尺寸变化的方式就是在其内部再包一层盒子

布局

  • 使用table-cell 布局(css3)
    1
    2
    3
    nav {display:table-cell; width:150px; padding:10px; background:#dcd9c0;}
    article {display:table-cell; padding:10px 20px; background:#ffed53;}
    aside {display:table-cell; width:210px; padding:10px; background:#3f7ccf;}

Display

  • 将行内元素改为块级元素实现文本选择范围扩大
    如,导航栏中文本
    1
    .list1 a {display:block; padding:3px 10px; textdecoration: none; font:20px Exo, helvetica, arial, sansserif;

font

  • 常用颜色
    image

background-clip

借助background-clip可以实现类似外边距分割效果,如

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
.multi-drop-menu ul {
float: left;
}
.multi-drop-menu li {
float: left;
list-style-type: none;
}
.multi-drop-menu a {
display: block;
color: #555;
background-color: #eee;
padding: .2em 1em;
border-width: 3px;
border-color: transparent;
}
.multi-drop-menu li a {
display: block;
border-right-style: solid;
background-clip: padding-box;
text-decoration: none;
}
<nav class="multi-drop-menu vertical">
<ul>
<li><a href="#">Shirts</a></li>
<li><a href="#">Pants</a></li>
<li><a href="#">Dresses</a></li>
<li><a href="#">Shoes</a></li>
</ul>
</nav>

类似的用途如facebook的弹出框

image

使用background-clip把元素背景控制在padding或content区域内,这样一来,只需一个非常div,我们在这个div上加上透明边框,并配合background-clip把背景超过padding或content的边缘外的背景色直接裁剪掉著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: http://www.w3cplus.com/content/css3-background-clip © w3cplus.com

Top

小米路由器 安装 Shadowsocks 客户端

前因

当然是为了省去多个终端连接的烦恼,而我的直接原因确是为了Google神器Chromecast能正常使用(就单单是电视投射功能就足以让我心动)。大半年前在美国买了一个Chromecast,在美国的时候工作的好好的,回国后确怎么也投射不了,总是找不到设备,大概猜到是因为被墙的原因,可惜电脑挂起了VPN还是不行,后来才知道是电视也需要连接Google 服务!坑爹啊!然后就一直静静的放着放着直到这次买了个服务器(也是为了科学上网)后,想试试让路由器直接连Shadowsocks。

准备

  • 一台运行着Shadowsocks,并且能访问外网的服务器
  • 一台小米MINI路由器(其他的也行)
  • 一台装载了linux远程连接客户端的电脑(XShell或者Putty)

第一步:开启路由器SSH

我的是小米MINI路由器,因为默认是稳定版而不是开发版,所以第一步就是升级(也可能是降级)到开发版去开启SSH,具体步骤在这里或者 这里

升级前可以先备份路由信息

第二步:连接到路由器

升级完成后,使用在小米官网给出当前小米账号的root账号密码便可以登录路由器系统XIAO QIANG, 实质也是linux系统的一个distribution,查询后得知事实上生活中很多小的硬件设备都是搭载的linux系统,因为其开销实在太小啦
![image]https://images.troyyang.com/2017-04-15-LoginXiaoMiRouter.PNG)

查询当前路由器系统信息

1
2
uname -a
/// Linux XiaoQiang 2.6.36 #1 MiWiFi-R1CM-2.15.75 Thu Apr 13 17:10:07 CST 2017 mips GNU/Li

第三部:安装Shadowsocks

原本以为这个shadowsocks客户端其实就是github上的那个Linux Shadowsocks, 然后似乎并不是,只能猜测针对当前路由器又做过一次包装, 然后就发现了这么一个宝藏一键安装脚本:http://d.ukoi.net/Miwifi/

也不知道作者是谁,只能猜测是对小米路由器系统很了解的人,所以根据这些安装脚本,我们就可以一步一步走向世界!之前试过很多小米论坛上的脚本,可惜基本都是不能下载的,所以很感激这位作者。

运行下面的脚本:

可以根据不同的小米路由器版本选择不同的脚本,更改相应的部分

1
2
3
4
5
6
7
8
// userdisk目录下的文件不会被系统reset
cd /userdisk
// 下载
wget http://d.ukoi.net/Miwifi/MINI/mini_install.sh
// 对文件赋权限
chmod +x mini_install.sh
// 安装
sh mini_install.sh

之后就是按照输入要求输入客户端连接的一些参数,完成后不出意外基本没问题啦,家里面所有wifi覆盖的地方都能愉快的科学上网,当然最重要的就是我的电视也能投射啦。。。。

还有一点就是这个脚本使用的是IP分流的,也就是说只有在GFW列表里的网站才会使用VPS,所以可以放心使用。

参考

http://www.miui.com/forum.php?mod=viewthread&tid=4133822&extra=

Top