网络寻租

Programmer, Gamer, Hacker

如何写邮件样式

| Comments

最近一段时间都在写邮件的样式,以及测试邮件在各种邮箱中的展示效果,搞得焦头烂额,痛定思痛,在这里整理获得的一些撞墙经验吧。

基本的原则

用table来控制架构

虽然现在css已经进化到可以用来画画的地步, 但是各种邮箱客户端往往各自为政,你调试得非常完美的邮件,在用户那边,被客户端蹂躏得惨不忍睹。 我们还是需要退而求其次,找一个各种客户端都支持得非常好的布局方案:利用table来布局。

用table的方式安排页面各个元素的方法,有着悠久的历史,虽然不优雅,但是能够解决问题。 删除掉各种在web端应用广泛的floatposition吧,勇敢回归table

inline css样式

邮件中带有css外链?邮件中含有<style>区块?很抱歉,在Gmail以及各种在线邮箱客户端中, 它们都会被当做嫌疑分子,有杀错没漏过地一律干掉。唯一可以通行的就只有嵌入在tag中的style属性了。 不过不要紧,你不需要手写这些,现在有很多第三方的工具来帮助你自动生成,比如premailer

注意可用的css样式

虽然style可以通行,但是广大客户端为了显示正常,还会过滤掉一些影响大局稳定的css属性, 比如Gmail就会杀掉position: absolute的样式,hotmail看float不顺眼。

还有就是客户端应用程序的邮件显示渲染系统一般进行了客制化,邮件的样式还是最好还是限制在css1/css2标准里面安全一些。

有一个各个客户端的css支持表格,聊胜于无。

text模式和html模式

按照需要, 一封邮件可以同时提供texthtml两份,用户可以选择自己喜欢的看。 如果你的用户期望看到text格式的邮件,最好提供一份。

显示图片和非显示图片

因为安全因素考虑,大多数邮件客户端都不默认显示邮件的图片,需要用户手动确认。 所以你的邮件必须要能够在这种情况下面显示得不至于太寒碜。

各种需要处理状况

上面这些都是总览,这里整理一些其他额外需要注意的状况。

gmail会根据你的文字宽度给你添加<wbr>的标签强制换行。我最后采用table布局的方式解决了这个问题。

不同平台下的不同浏览器会采用不同的字体。所以你也页面不要根据开发环境的字体预设宽度。

有些android手机(比如我的galaxy nexus)gmail客户端会限制页面的宽度,有些文字段落会压缩掉宽度,没有找到解决办法。

测试

邮件显示效果的测试,我觉得是最烦的一件事情了。维度太多。需要测试的有:

  • 不同的邮件客户端,thunderbird,outlook,foxmail,它们还有不同的版本。
  • 不同的在线邮箱在不同浏览器下面展示的效果。维度大概是在线邮箱系统数量乘以浏览器数量了。
  • 不同操作系统。主要是字体影响比较大。
  • 多平台,各种手机客户端,平板设备。现在是移动时代,很多人有在移动设备上读邮件的习惯。

全部测试一遍很不现实,只能根据用户特征把主流的一些环境测试一下。

经验

花费了那么多时间,这里整理一下主要的经验以供借鉴:

如果简单的邮件,只是改一份文件就好,如果模块复杂,还是需要根据区块拆分一下。 也可以用一些预处理的工具来让文件变得更易读。 我是用haml,sass,以及rails的render partial功能来把复杂邮件模块化的。

因为邮件需要不断地调整,最好养成完成一个部分的改动后就git commit一下,或者至少git add变动, 因为很容易在改动中造成错误。

要进行流程控制,每次修改完毕后都过一遍测试,总是会出现一个环境改动正确了,其他环境又出现展示问题的状况。

资料

保证电子邮件的送达率

| Comments

现在大多数网站都会发送各种各样的邮件,保证邮件送达率至关重要,这里整理一下我调研的一些结果。

要让邮件正常到达用户的收件箱,需要保证几点:

  • 让收件方验证邮件来源是非伪造的,需要设置PTRSPFDKIM
  • 让收件方认可发送者是可靠的。

如何让收件方验证邮件来源是非伪造的

PTR

ptr是DNS记录的一种类型,有一个用途在于反方向从IP推导到域名,比如:

dig -x 203.208.36.17

把DNS的MX类型记录指向到你邮件服务器的地址一般就可以了。

SPF

SPF(Sender Policy Framework)这是一套电子邮件验证系统,通过在DNS记录中添加一个TXT类型的记录,指定谁有权以域名的名义发送邮件。如果你没有设置这条记录,很容易就被当做非法邮件spam掉。

这里是配置方法,以及一个简单的例子:

v=spf1 a mx ptr include:spf.mtasv.net ~all

这一行主要说明spf.mtasv.net(postmark,一个邮件发送服务商)代发的邮件是被认可的。

如果你域名挂靠在bluehost上面,这里是对应的设置方法

设置完毕,等待生效后,可以通过这种方式验证是否设置成功:

dig txt your-domain.com

返回的信息需要带有你设置的SPF记录。

DKIM

邮件本身是纯文本,协议也没有防止伪造的部分,在发送的过程中很容易被篡改, DKIM(DomainKeys Identified Mail) 对邮件用私钥加密,同时公钥信息放在域名DNS上面,这样收件方就可以验证邮件的真实性。

如果你用ubuntu以及postfix,这里是设置教程,以及一个简略的安装过程整理:

  • 安装opendkim sudo aptitude install opendkim
  • 设置 /etc/opendkim.conf
  • 告诉postfix采用opendkim,设置/etc/postfix/main.cf
  • 启动和重启对应的服务。

原理很简单,运行opendkim进程服务器,然后postfix把它当做一个过滤器来用,设置完成之后,对于所有的邮件,都会加上一个DKIM-Signature,一个具体的例子:

DKIM-Signature: v=1; a=rsa-sha256; d=example.net; s=brisbane;
c=relaxed/simple; q=dns/txt; l=1234; t=1117574938; x=1118006938;
h=from:to:subject:date:keywords:keywords;
bh=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=;
b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZVoG4ZHRNiYzR

一些参数的含义:

  • b:具体的数字签名。
  • bh:body hash。
  • d:签名的域名。
  • s:selector。一个域名可能有多个邮件发送服务器,需要用一个selector来区分。

然后接收方就会去检查brisbane._domainkey.example.net的DNS记录,你可以通过这种方式来查看是否它设置正确:

dig txt brisbane._domainkey.example.net

内容格式是k=rsa\; p=MIGfMA0GCSqGSIb3DQEBA...

检查是否设置成功

发送一封邮件到gmail里面,这样检查信息:

出现mailed-by表示你SPF设置正确了,出现signed-by表示你DKIM设置正确了。

让收件方认可发送者是可靠的

上面是一些技术上面的部分,同时还需要在内容上面以及行为上面让收件方认可你:

  • 收件人需要认可邮件内容。你发送的东西对于用户是有信息含量的,如果用户看到你的邮件就标注spam,再多的设置也没有用。
  • 邮件中带有退订链接。当用户不想收到你的邮件的时候,可以点击退订,防止用户直接把你spam掉。
  • 控制发送频率。各个邮箱提供商会检查来自一个IP的邮件投递速率,如果你发送速度过于频繁,也容易进黑名单。

第三方的服务

设置了上面的这些东西,你的邮件还是有可能被spam,以及你也不清楚到底邮件的到达率有多少,我建议针对商业的网站,还是需要采用一些第三方的邮件发送服务。这里推荐几个:

采用了这些第三方服务之后,你需要对应地更新SPFDKIM。 同时如果可能,尽量选择独立IP的服务,这样不会因为其他人发spam影响到你的邮件送达率。

mailgun和postmark都会想办法保证你的邮件送达, 因此价格也比其他的邮件服务商要贵,但是还是物有所值的。

mailgun带有campaign服务, 你可以设置一个campaign,然后发送带有这个campaign ID的邮件,之后会统计出送达率,开启率,链接点击率等信息给你。

还有就是针对国内垃圾邮件乱飞的状况,很多国内的邮箱服务商会比较严格,造成国外的第三方邮件发送服务不好用的状况,比如:

  • mailgun发到QQ邮箱,新浪邮箱里面会被spam掉。
  • postmark和mailgun都发不了邮件到tom邮箱里面。

需要注意一下。

引用资料以及工具

命令行下使用全局代理

| Comments

最近Bitbucket的git ssh访问被墙了,如果设置电脑全局翻墙会很麻烦,我需要能够有针对一个terminal,一个命令的翻墙方式。

询问了友邻之后,有几个工具浮出水面:tsocks以及proxychains

调研了一下tsocks,2002年就不再更新了,osx下面编译没有成功,不过ubuntu的源里面是有的。下载下来发现,它需要修改一个设置文件/etc/tsocks.conf,比较麻烦,于是我放弃使用这个工具。

proxychains要好一些,github上面的页面有mac下面用homebrew的安装方法,ubuntu的源里面也有,使用起来也是一行代码可以搞定的。

首先需要跑一个socks5 proxy,使用:

$ ssh -fN -D 4321 some.example.com

然后设置参数执行命令就好了:

$ PROXYCHAINS_SOCKS5=4321 proxychains wget http://wikileaks.org/IMG/wlogo.png

调试rails Autolink

| Comments

今天遇到一个bug,在我们网站GuruDigger里面的留言或者私信中,会自动把网址转换成链接,比如:

我今天发现一个好的网站,它是:www.dreamore.com

而对于下面这种状况,就错误地把后面的内容(就是那个句号)全部加到链接里面去了。

我今天发现一个好的网站,它是:www.dreamore.com。

我首先判断一下这个问题是否是一个问题。在使用中,中文的逗号和句号都是参见的分割符,用户会很容易就使用这种方法。我觉得这个问题需要解决。

首先是界定问题。这个很明显是渲染链接出错。我采用的是rails_autolinkgem,因为在rails3.2中,原先的autolink功能被移除了。

我下载了它的代码库,找到具体做这件事的文件:lib/rails_autolink/helpers.rb,和对应的位置:

1
2
3
4
AUTO_LINK_RE = %r{
  (?: ((?:ed2k|ftp|http|https|irc|mailto|news|gopher|nntp|telnet|webcal|xmpp|callto|feed|svn|urn|aim|rsync|tag|ssh|sftp|rtsp|afs|file):)// | www\. )
  [^\s<]+
}x

它是利用正则来找到链接位置,然后替换的。在[^\s<]+里面,没有取消掉中文的分隔符,这样把后面的东西全部匹配掉了。

找到了问题,如何解决呢?我尝试去掉分隔符,把它替换成[^\s<\p{P}]+,但是发现.是应该被匹配到的,也被包含在了\p{P}里面,于是我要细分以下。 文档中有:

/\p{Pc}/ - 'Punctuation: Connector'
/\p{Pd}/ - 'Punctuation: Dash'
/\p{Ps}/ - 'Punctuation: Open'
/\p{Pe}/ - 'Punctuation: Close'
/\p{Pi}/ - 'Punctuation: Initial Quote'
/\p{Pf}/ - 'Punctuation: Final Quote'
/\p{Po}/ - 'Punctuation: Other'

然后我完全不清楚每一个组里面有什么符号,google也找不到解释,这个时候我只能去挖ruby源码了。 我用find | grep来找Punctuation,被我找到具体的定义位置在:enc/unicode/name2ctype.h里面。英文的句号被分组在Punctuation: Other中。

这条路走不通,我又回头好好思考了一下,我觉得这个需求并不是全局的,只是针对采用中文的用户有效。我放弃做一个全局的方案,然后提交Pull Request, 改成自己客制化一个repo算了。于是我fork了这个项目,然后把[^\s<]+改成[^\s<,。]+push上去,Gemfile里面换成gem 'rails_autolink', github: "halida/rails_autolink", 发布到服务器上面去,问题解决了。

但是问题并没有完全结束,我必须在以后的版本里面注意到采用了一个客制化的gem,可能会有各种问题出现。未来的地雷需要小心不要踩到。 这是一个简单的bug处理,上面是我debug的整个过程,体力活,是以为记,大家对于这个过程有什么看法吗?

手机远程收听电脑声音

| Comments

我现在笔记本的声音都通过耳机输出,耳机有几个不好的地方,就是每次我离开电脑去上厕所或者干什么事情的时候,都要把耳机拔出来,非常不方便, 我想如果能够把声音换到手机上面,这样挺不错的。我的电脑是rMBP,手机是google galaxy手机。

我搜索到了一些解决方案:

我测试了Airfoil和iPlay的方案,都可以用,Airfoil如果要获取系统声音,需要重新启动电脑比较麻烦。并且价格也比较贵,iPlay的方案看起来比较适合我。 不过如果你是其他操作系统或者希望更多特性的话,Airfoil比较好。 不过最后我一个方案都没有用成,因为声音同步需要带宽100KB的上下行,我的无线路由器撑不住了。。

关于独立游戏

| Comments

我是一个游戏爱好者。从初中起我就在玩电脑游戏,一直持续到现在。初中到大学阶段我一直是以PC游戏为主,最近几年我渐渐转移到了独立游戏上面。

为什么我喜欢独立游戏

  • 我喜欢去接触各种新的领域,厌烦没有新意的东西。在独立游戏领域,会有更多人尝试新的游戏性,设计性的东西出来。
  • 独立游戏一般不会是大制作,游戏占用的硬盘空间不会很大,容易下载容易保存。
  • 独立游戏价格一般不会很高,我能够承受得起,并且能够购买相当数量的游戏。

我玩的一些独立游戏

Mosh介绍

| Comments

image

这年头做技术没有人不用ssh的吧,然后ssh使用起来还是很不爽的, 远程服务器慢的时候反应慢,ssh有的时候断掉要重新连。。。

mosh是ssh的替代方案,用来解决慢速网络和移动网络的问题,具体上来说:

  • 连上一台服务器后,不管你切换什么网络,或者网络断掉,或者机器休眠又醒过来,连接都能够一直保持。
  • 缓存输入输出,当连接慢的时候,屏幕上面显示的响应还是很快。

强烈建议大家都去用它。

原理

mosh它实现了一套自有的状态同步协议SSP:

  • 底层采用UDP,client和网络解耦合,
  • 内部client和server都维护到屏幕和输入的状态,根据状态发送diff/patch。
  • 再在上层加上一个预测模块,根据用户输入先在本地显示预期的输出结果,收到远程服务器返回的屏幕数据,再更新。

作者同时也提出,现在的应用往往没有针对移动作优化,如果一个用户切换了网络(比如正在移动中), 那么tcp连接断掉会出现各种问题,在这种场景下,SSP这种解决方案会很有用处。

建议大家可以看看mosh首页的视频,作者介绍得很充分。

使用

大家去看首页的介绍就好。这里整理一些我觉得需要提到的:

mosh首先通过ssh连进去,然后开启一个mosh server,端口随机在60000:61000,如果你开了防火墙需要允许这个范围。

指定ssh参数,比如你的ssh端口不是22:

1
mosh $server --ssh='ssh -p 2222'

当你在网络断开的时候,关掉了mosh的进程,远程服务器上面对应的server进程会一直存在, 这个时候重新连上去的时候,就会提示你。这个时候你需要手动kill掉这个进程, 具体见issue报告

Squeel介绍

| Comments

squeel是Rails下面一个处理复杂sql查询的框架, 具体用法可以点击前面的链接查看,这里整理一下我对这个东西的评估。

为什么要用它?

平时我们在rails里面,需要写复杂查询的时候,一般都是直接写sql,比如:

1
2
3
4
Person.where(
  '(name LIKE ? AND salary < ?) OR (name LIKE ? AND salary > ?)',
  'Ernie%', 50000, 'Joe%', 100000
)

这样的写法不是很好,因为:

  • 里面的sql是只针对一个数据库的,不能做到数据库的切换。
  • 里面的sql在ruby里面是字符串,一个是可读性差,一个是不能进行语法上面的检查,只能通过单元测试来保证。
  • 写这样语句的时候,需要切换成sql的思路,写完之后再切换回来。
  • 遇到复杂sql的时候,不容易拆分问题,只能通过大段长度的sql来处理。

采用squeel,就可以变成这些的写法:

1
Person.where{(name =~ 'Ernie%') & (salary < 50000) | (name =~ 'Joe%') & (salary > 100000)}

原理

squeel使用的方法是传一个block给where,然后这个block会被修改作用域,在这个作用域里面, 各种操作符和变量都会被转义,变成Node对象,然后形成一个抽象语法树,最后通过底层的arel变成真正执行的sql。

具体代码比较重要的是dsl.rb里面的evalmethod_missingstub.rbvisitor.rb

我思考了一下,这种方法应该是最简洁和干净的,语法是采用ruby的方式,魔法的部分都被包裹在block里面,和现有的功能无缝衔接。

使用心得

如果明白原理,使用它没有什么太多的问题,需要注意的是, 一定要看一遍log里面生成的sql,确认是自己期望的结构。

还有就是,squeel和原有系统切割得比较好,只有一点需要注意: 针对User.where(name: :aaa)里面把symbol当做value的状况,squeel会转变成:

1
select * from users where users.name == users.aaa

这是一个特性,如果你不期望这样的话,需要改成where(name: :aaa.to_s)。也可以关闭,具体看squeel的文档。

在我用的过程中,遇到几个问题:

  • 我直接用orand,结果发现按照文档应该是用|&,使用任何东西之前还是需要认真看一遍文档。
  • 复杂查询条件下面,如果用到了|&,为了保证是按照自己期望的优先级分割,需要用括号来明确界定,比如where{(name == 'aaa' | name == 'bbb') & (level == 1)}
  • 上面提到的symbol当做value的bug。
  • 因为使用了作用域切换,对象属性等就不能访问了,比如where{name == @user.name},需要改成where{name == my{@user.name}}

结论

考虑一下是否需要采用squeel:它的学习成本应该只需要一个小时,性能问题可以通过cache来解决,可以无缝衔接,收益是更清晰,更一致,更好用的代码。

试用gitlab

| Comments

image

今天我试着安装了一下gitlab,它是一个开源的类似github的系统, 利用它可以本地搭建一个github网站出来。可以解决一些需要客制化代码库,或者私有化管理的需求。

安装过程

按照文档进行,我中间走了一些弯路:

  • 安装ruby的时候,我是采用rvm,而不是教程里面的采用系统ruby,后来发现不对,必须安装成系统的版本,不然服务起不起来。
  • postgres用户gitlab不能登陆,后来搜索了一下,发现需要修改权限

其他过程比较顺利,基本按照教程来就可以了。

架构

其实整体系统还是比较简单的,安装了一个系统服务,后面主要是把gitlab当做一个rails应用来跑,后台启动了一个sidekiq。

针对git的部分,分离出来了git-shell,其实就是在系统里面安装了一个shell,用户用git连过来的时候,就用这个shell来进行版本库的操作,它是调用rails网站服务器的API来做权限管控之类的事情。

试用感觉

因为我只是试用一下,没有用于生产,只是稍微浏览了一下功能。 github有的功能它都有,还加上了用户管理,群组管理,查看系统日志等功能。 功能上面感觉还是够用的。

关于这个项目的架构思路,我觉得还是挺好的。 网站的部分归网站,git的部分归git,中间通过API来通讯。 git-shell的方式减少另外跑一个服务的成本,对于小用户群的环境来说可以, 但是不是很适应用户数量过多的状况,但是场景不多,问题不大。 不过需要安装系统的ruby环境倒是挺麻烦的,希望他们以后针对这个问题提出改进方案。

Pow介绍

| Comments

image

Pow是一个神奇的东西,原先你需要跑一个服务器应用,你可能需要在命令行下面执行一个命令,以及你自己需要时刻监控到这个程序的运行。但是有了POW,你需要做的事情只是做一个ln软链接就可以了。对了,它只能在OSX下面使用。

安装:

1
curl get.pow.cx | sh

删除:

1
curl get.pow.cx/uninstall.sh | sh

如果你需要设置一个基于Rack的应用,你需要做的是:

1
2
cd ~/.pow
ln -s /path/to/myapp

就是这么简单。

原理

  • Pow把目录当做Rack应用来执行,目录主要含有config.ru配置文件和public静态文件目录。
  • 在访问到目录的时候,它自动创建一个worker,最多每个应用2个worker,15分钟没有请求后自动回收。

一些特性整理

  • 链接到~/.pow里面的目录比如myapp会映射到http://myapp.dev/
  • www.myapp.dev这种子域名都会映射到myapp.dev,除非你重新创建一个www.myapp目录。
  • ~/pow多个软链接到同一个目录,只会生成一个worker。
  • 如果没有myapp,访问myapp.dev会给出提示。
  • Pow支持端口转发功能,只要echo 8080 > ~/.pow/proxiedapp,访问proxiedapp.dev就是访问本地端口8080。
  • Pow支持只含有public目录,直接serve静态文件。
  • 重新启动服务:touch tmp/restart.txt,或者直接杀掉进程就好了。会重新加载环境。
  • 每次访问都重启服务:生成这个文件:tmp/always_restart.txt。但是它不会重新加载环境。
  • log放在~/Library/Logs/Pow里面。
  • 重启Pow:touch ~/.pow/restart.txt

设置

Pow启动前会去执行目录下.powrc.powenv这2个脚本。

如何设定ruby版本?

  • rbenvrbenv local 1.9.3-p194
  • 用rvm,添加.rvmrc:rvm 1.8.7 因为rvm需要加载环境变量,你需要修改上面的2个脚本之一: sh if [ -f "$rvm_path/scripts/rvm" ] && [ -f ".rvmrc" ]; then source "$rvm_path/scripts/rvm" source ".rvmrc" fi
  • 或者直接设置PATH就好了。

FAQ

手动设置软链接太烦了,有什么方便的方法?

有的。 gem install powder,cd到你应用的目录,然后执行:

powder link

文档在这里

如何让局域网的其他人访问到这个服务?

xip.io,它是一个把网络地址转换到具体IP的服务,方便进行一些调试。 比如你的ip是10.0.0.2,用Pow跑的服务是app,那么同一个局域网的人就可以用app.10.0.0.2.xip.io来访问你的服务。

如何让外网的其他人访问到这个服务?

forward

资料