Pages

Tuesday, 21 May 2013

PAC(自动代理配置文件)的设置和写法

PAC是“Proxy Auto Configure”的简称,即自动代理配置。在为浏览器或系统指定代理时,一般会看到“自动代理配置”的选项。

下面是Firefox的代理设置窗口,在最下面有个设置自动代理配置文件的输入框,在这里输入你的自动代理配置文件地址.

自动代理配置文件可以是本地文件,也可以是远程文件,格式如下:

    服务器端自动代理配置文件:http://www.somedomain.com/proxy.pac
    本地自动代理配置文件:file:///C:/IT/proxy.pac
    IE下本地自动代理配置文件的设置稍有不同:file://C:\net\proxy.pac (注意用的的反斜杠"\",文件夹不要有空格)

比起手动指定代理主机、端口,PAC的好处主要有以下几点:
    对不同的站点使用不同的代理配置,加速浏览
    配置好之后一劳永逸,不必频繁切换代理
    可以为站点配置代理串,如果前面的代理失效,则自动用后面的代理配置.
-------------------------------------

PAC自动代理文件的格式,教你如何写PAC文件


PAC文件格式

PAC文件是纯文本格式的,实际上就是JavaScript文件。Chrome/Chromium的扩展Switchy!的"Auto Switch Mode"功能实际上也是创建和维护一个简单的PAC文件,但功能比较弱。

对于一般的应用,即使你几乎不懂JavaScript和编程,也可以通过本文的介绍实现基本的功能。

PAC文件FindProxyForURL函数

PAC文件中必须包含一个函数:FindProxyForURL(url, host)。

参数url是用户输入的url,参数host是url中的主机名。

一个最简单的PAC文件内容如下:

function FindProxyForURL(url, host) {
 return "DIRECT";
}

这个PAC文件实际上什么也没做,对任何URL,都将"DIRECT"(直接连网)。

PAC文件返回值类型

除了可以return "DIRECT"以外,还有两种常用方式:

PROXY proxysample.com:8080

http代理的主机和端口,主机也可以用IP表示

SOCKS5 socks5sample.com:1080

socks5代理的主机和端口,主机也可以用IP表示

那么,我们可以猜测到,用pac指定一个http代理应该这样写

function FindProxyForURL(url, host) {
  return "PROXY 192.168.1.1:3128";
}

甚至可以指定多个代理 

function FindProxyForURL(url, host) {
  return "DIRECT; PROXY 192.168.1.1:3128; SOCKS5 lilinux.net:1080";
}

这句语句的意思是:

    对所有URL,都直接连接;
    如果不能直接连接,那么就使用192.168.1.1:3128这个http代理连接;
    如果还是不能连接,则使用lilinux.net:1080这个socks5代理连接。

使用不同连接的顺序和语句中的顺序一致,你可以根据自己的实际情况更改。

也许你明确知道哪些网站不能直连,必须用PROXY或者SOCKS5连接,那么可以对站点分别指定代理配置

function FindProxyForURL(url, host) {
   if (shExpMatch(url,"*.google.com/*")) {
     return "PROXY 192.168.1.1:3128";
   }
   if (shExpMatch(url, "*.wikipedia.com:*/*")) {
     return "SOCKS5 lilinux.net:1080";
   }
   if (isInNet(host, "10.0.0.0",  "255.0.0.0")){
     return "DIRECT";
   }
   return "DIRECT; PROXY 192.168.1.1:3128; SOCKS5 lilinux.net:1080";
}

这个PAC文件中引入了两个新的函数,但从字面意思上,我们也可以猜出代码的大概意思:

    当url是*.google.com/* 时,自动使用PROXY代理;
    当url是*.wikipedia.cm/*时,自动使用SOCKS5代理;
    当host是10.0.0.0 /255.0.0.0的子网内时,自动直连;
    如果都不匹配,则依次按DIRECT、PROXY、SOCKS5的次序尝试。

shExpMatch函数用来匹配url或者host,匹配的方式和DOS的通配符相似。例如前面用到的"*.google.com/*"可以匹配任意包含".google.com/"的字符串。

Chrome/Chromium 的扩展Switchy!创建的pac文件还自定义了一个函数,可以用来匹配正则表达式,不过个人认为在url匹配上通常不需要使用强大的正则表达式。

isInNet函数用来返回请求的host是否在指定的域内。值得注意的是,isInNet的第二个参数必须是 IP,不能是主机名。因此需要把主机名转换成IP。比如"isInNet(host, dnsResolve(www.google.com), "255.255.255.0")"讲到这里,应该可以解决你的问题了吧。

PAC文件可以使用的JavaScript函数

当然PAC也不止这么简单,它还提供了不少其它函数,在本文就不详细讲述了。

你也许想把pac文件发布到Internet上,这样其它用户就只需要在浏览器中指定pac文件的url即可。你得配置你的服务器映射 .pac 文件后缀到MIME类型: application/x-ns-proxy-autoconfig 如果使用的是Netscape服务器,编辑 config 目录下的 mime.types 文 件。如果是Apache, CERN or NCSA服务器,使用 AddType 指令.
------------------------------

PAC代理文件中,myIpAddress()函数的使用


上文详细介绍了PAC自动代理脚本的应用以及如何写PAC代理脚本。提到过函数myIpAddress(),可以根据IP地址的变化来自动使用代理。这样就方便使用VPN,或者使用笔记本电脑的,比如我在单位和家经常要切换代理。

myIpAddress函数可以得到自身的IP地址,然后我们可以根据IP地址在PAC文件中进行配置。
可以使用下面的匹配进行判断:
if(shExpMatch(myIpAddress(), "*135.36.*") ) {
  ......
} else {
 ......
}
建议不要使用isInNet(),因为myIpAddress()有可能会返回IPv6格式的IP地址,就会使得匹配失败。
-------------------------------------------------------------

PAC文件可以使用的JavaScript函数


下面是可用于FindProxyForURL()函数体中的条件函数:
基于主机名的函数:
  • isPlainHostName()
  • dnsDomainIs()
  • localHostOrDomainIs()
  • isResolvable()
  • isInNet()
相关的实用程序函数:
  • dnsResolve()
  • myIpAddress()
  • dnsDomainLevels()
基于 URL/主机名 的条件:
  • shExpMatch()
基于时间的条件:
  • weekdayRange()
  • dateRange()
  • timeRange()
  • isPlainHostName(host)
host 即上文所述不包含端口号的,url中的主机名,下同,不再赘述。如果主机名中不包含域名则返回true。参考如下:
  • isPlainHostName("www") 返回 true.
  • isPlainHostName("www.google.com") 返回 false.
  • dnsDomainIs(host, domain)
domain 用于和hostname进行比较的域名。如果hostname的域名和domain的值相匹配则返回true。参考如下:
  • dnsDomainIs("www.google.com", ".google.com") 返回 true.
  • dnsDomainIs("www", ".google.com") 返回 false.
  • dnsDomainIs("www.apple.com", ".google.com") 返回 false.
  • localHostOrDomainIs(host, hostdom)
hostdom 是要进行匹配的指定的主机名。如果hostname和指定的hostdom相匹配,或者在hostname中没有指定要进行匹配的域名部分,函数返回 true。参考如下:
  • localHostOrDomainIs("www. google.com", "www. google.com") 返回 true (exact match).
  • localHostOrDomainIs("www", "www. google.com") 返回 true (hostname match, domain not specified).
  • localHostOrDomainIs("www.apple.com", "www. google.com") 返回 false (domain name mismatch).
  • localHostOrDomainIs("adc.apple.com", "www. google.com") 返回 false (hostname mismatch).
isResolvable(host)
如果成功解析主机名则返回true。参考如下:
  • isResolvable("www.google.com") 返回 true (除非由于防火墙或某些其他原因导致DNS无法解析).
  • isResolvable("bogus.domain.foobar") 返回 false.
isInNet(host, pattern, mask)
host 在这里可以是DNS主机名,比如www.google.com,也可以是IP地址。如果传递的是主机名,此函数会将其解析成 IP 地址。 pattern 是点分隔格式的 IP 地址模式。
mask 是IP地址模式掩码,用于确定应对IP地址的哪些部分进行匹配。值为0表示忽略;255表示匹配。如果主机的IP地址与指定的IP地址模式匹配,则返回 true。参考如下:
  • isInNet(host, "198.95.249.79", "255.255.255.255") 如果host的IP地址为198.95.249.79则返回 true.
  • isInNet(host, "198.95.0.0", "255.255.0.0") 如果host的IP地址为198.95.*.*则返回 true.
dnsResolve(host)
host 是要解析的主机名。将给定DNS主机名解析成IP地址,并以点分隔格式的字符串形式将其返回。参考如下:
dnsResolve("www.google.com") 返回IP地址 "64.233.189.104".
myIpAddress()
此函数将以点分隔格式的字符串形式返回运行浏览器的那台计算机的IP地址。
dnsDomainLevels(host)
返回url主机名中的DNS层数(圆点数)。参考如下:
dnsDomainLevels("www") 返回 0
dnsDomainLevels("www.google.com") 返回 2
shExpMatch(str, shexp)
str 是要比较的任何字符串(例如,url或主机名)。
shexp 是用以进行比较的shell表达式。如果字符串与指定的 shell 表达式匹配,则此表达式为 true。参考如下:
shExpMatch("http://www.apple.com/downloads/macosx/index.html", "*/macosx/*") 返回 true.
shExpMatch("http://www.apple.com/downloads/support/index.html", "*/macosx/*") 返回 false.
----------------------------------------------

推荐一种经过优化的PAC写法



有人抱怨PAC慢,目前网上见到的 PAC写法主要是正则或
DomainIs,前者条目多时速度不理想,后者又过于死板长列表中很少使用。这里推荐一种不常见的写法。这个写法有如下优势: * ChkDomain 主函数根据根域名进行分组,可以成组跳过大片记录
* IsDomain 函数
高效先比较域名长度,再从后向前比较域名是否匹配
简洁 “Google.com” 只匹配 *.Google.com 或 Google.com
* 脚本比较格式化仍然可以通过程序处理或生成

示例:
//确定一个主代理容易修改
var Current_Proxy = “SOCKS5 127.0.0.1:8580“;
var No_Proxy = “DIRECT”;
// .Onion
var TOR_Proxy = “PROXY 127.0.0.1:9050“;
// 经常更改或需要程序化更改的条件放在了前面方便修改,
// 例如对“开头的Google搜索自动进行代理
var GoogleSearch_Str = “http://www.google.com/search*&q=“*“;
// 再设置一个网站测试代理是否成功
var IPTest_Str = “ip-adress.com“;
// //////////////////////////////////////////////////////////////////
// dRq, Apr 2009
// special thanks to no-ads author ;)
// //////////////////////////////////////////////////////////////////
var hLen = 0; // host长度
function FindProxyForURL(url, host) {
hLen = host.length;
//下面开始匹配,URI条件和域名条件的数量如果相差悬殊较少的可以放在前面
if (chkDomain(host)||chkMatch(host, url)) {
return Current_Proxy;
} else {
//Else放固定的专用代理,例如TOR的 .onion 只能用TOR
if (IsDomain(host, “onion”)) {
return TOR_Proxy;
}
return No_Proxy;
}
}
// URL匹配列表
function chkMatch(host, url) {
var IsHost = false;
if (0 || shExpMatch(host, “*.epochtimes.*”)
|| shExpMatch(host, “epochtimes.*”)
|| shExpMatch(url, “http://www.voanews.com/chinese*“)
|| shExpMatch(url, “http://www.voanews.com/vietnamese*“)
|| shExpMatch(url, “http://*.bbc.co.uk/chinese*“)
|| shExpMatch(url, “http://www.rfi.fr/actucn*“)
|| shExpMatch(url, “http://feeds.*”)
IsHost = true;
};
//复杂规则先domain避免大量无效匹配
if (IsDomain(host, “google.com“)) {
if (0   || shExpMatch(url, “*http://??.??.???.???/search?q=cache:*”)) {
|| shExpMatch(url, “http://images.google.com/images*q=“*“)) {
//前面定义的 google 搜索 暗号“
|| shExpMatch(url, GoogleSearch_Str)
IsHost = true;
};
};
return IsHost;
}
//域名列表
function chkDomain(host) {
var IsHost = false;
//.com和.net域 名很多如果能分开会快不少,分开!
switch (true) {
case IsDomain(host, “com”) :
if (0 || IsDomain(host, “64memo.com“)
|| IsDomain(host, “aboluowang.com“)
|| IsDomain(host, “blogspot.com“)
|| IsDomain(host, “chinaeweekly.com“)
|| IsDomain(host, “dongtaiwang.com“)
|| IsDomain(host, “epochtimes-bg.com“)
|| IsDomain(host, “feeds.feedburner.com“)
|| IsDomain(host, “groups.google.com“)
|| IsDomain(host, “heartyit.com“)
|| IsDomain(host, “wordpress.com“)
|| IsDomain(host, “youtube.com“)
//更多.com域名按格式括起来就可以了
// [tab]+|| IsDomain(host, “+域名+”))
IsHost = true;
break;
//。net
case IsDomain(host, “net”) :
if (0 || IsDomain(host, “77sea.net“)
|| IsDomain(host, “aiph.net“)
|| IsDomain(host, “chinainperspective.net“)
|| IsDomain(host, “freenet.sourceforge.net“)
|| IsDomain(host, “huping.net“)
|| IsDomain(host, “twitbrowser.net“)
|| IsDomain(host, “tibetpost.net“)
|| IsDomain(host, “xinsheng.net“)
IsHost = true;
break;
//。org
case IsDomain(host, “org”) :
if (0
|| IsDomain(host, “asiademo.org“)
|| IsDomain(host, “bignews.org“)
|| IsDomain(host, “chinaaffairs.org“)
|| IsDomain(host, “delegate.org“)
|| IsDomain(host, “cncitizen.org“)
|| IsDomain(host, “freenetproject.org“)
|| IsDomain(host, “grandtrial.org“)
|| IsDomain(host, “zh.wikipedia.org“))
IsHost = true;
break;
//所有非.com/net/org域 名都放在这里
default :
if (0 || IsDomain(host, “ccc.de“)
|| IsDomain(host, “autonet.com.tw“)
|| IsDomain(host, “bit.ly“)
|| IsDomain(host, “soundofhope.or.kr“)
|| IsDomain(host, “erabaru.or.id“)
|| IsDomain(host, “gcbbs.info“)
|| IsDomain(host, “libertytimes.com.tw“)
|| IsDomain(host, “open.com.hk“)
|| IsDomain(host, “rq2007.cc”)
|| IsDomain(host, “ff.im“)
|| IsDomain(host, “bit.ly“)
|| IsDomain(host, “shenyun.us“)
|| IsDomain(host, “videopedia.us“)
|| IsDomain(host, “yoshow.com.tw“)
|| IsDomain(host, “zyzg.us“)
|| IsDomain(host, “velkaepocha.sk“)
|| IsDomain(host, “cntv.us“)
|| IsDomain(host, “cna.com.tw“)
|| IsDomain(host, “boxun.us“)
//插入最前面定义的代理测试网站
|| IsDomain(host, IPTest_Str))
IsHost = true;
break;
}
return IsHost;
}
//先长度后内容
function IsDomain(host, domain) {
var dLen = domain.length;
if (hLen > dLen) {
return (host.substring(hLen – dLen – 1) == “.” + domain);
}
return (host == domain);
}
--------------------------------------------------------------------------

代理自动配置脚本——PAC文件


什么是PAC?

A proxy auto-config (PAC) file defines how web browsers and other user agents can automatically choose the appropriate proxy server (access method) for fetching a given URL.(摘自Wikipedia
正如这句定义,PAC文件被用来决定某一次网络访问所使用的代理。说得通俗点,就是当你访问某个网站时,由PAC文件判断该不该使用代理,或使用哪一个代理。

为什么会用到PAC?

这有什么用?如果你发问,那你要么不懂网络要么肯定没在大陆待过。在大陆的人都知道GFW,它是互联网上的文字 狱,是自由精神的枷锁,是中华名族的樊篱,如果要自由上网就必须“翻墙”。很多翻墙工具都是代理性质的(如Wallproxy、GoAgent等),但是 这些代理在访问大陆网站的时候又会增加延迟、拖慢网速。若能在访问被墙网站(如Facebook、Twitter)时通过代理连接,在访问其他网站时直接 连接,就能实现既“翻墙”,又不会因代理拖慢其他网站速度。实现这一想法的最便捷方案就是PAC文件。
还有另一部分人(包括我在内),使用中国教育网,只能访问极少一部分大陆网站,不能访任何外国站。这部分人对能够自动配置代理的PAC文件有更强烈 的需求。因为教育网要访问外国站就必须加代理,但是通过代理又无法访问教育网网站。如果不能自动配置代理,那么上网过程将会伴随不断的切换代理。而切换代 理那繁琐的过程相信没有任何一个人愿意忍受。

如何构造PAC文件?

PAC文件其实可看作是一个JavaScript脚本,只不过它仅支持其中一部分命令。PAC文件必须包含一个 FindProxyForURL(url, host)函数,这个函数可以看作是PAC文件的主函数,任何网络访问请求都会由系统传给这个函数。参数url就是要访问的URL,host就是要访问的 URL所包含的主机。例如访问http://en.wikipedia.org/wiki/Proxy_auto-config,URL就是它本身,host就是en.wikipedia.org。下面举两个Wikipedia中的例子来说明这个函数的用法:
function FindProxyForURL(url, host) {
    return "PROXY proxy.example.com:8080";
}
上面这两句就可以作为一个PAC文件,它对所有URL都返回同一个代理。也就是说这个PAC会让所有访问都通过proxy.example.com:8080这个代理。
下面这个相对复杂一些:
function FindProxyForURL(url, host) {
    //对于所有.edu.cn域名,直接连接
    if (shExpMatch(url,"*.edu.cn/*"))    {return "DIRECT";}
    //对于10.0.0.0到10.0.0.255之间的IP地址使用代理
    if (isInNet(host, "10.0.0.0",  "255.255.255.0"))    {
        return "PROXY proxy.example.com:8080";
    }
    //其他网站尝试使用代理访问,代理无效时,直接访问
    return "PROXY proxy.example.com:8080; DIRECT";
}
这个例子说明了JavaScript函数在PAC文件中的使用方法。例子中,所有以.edu.cn作为域名的网站都将采用直接连接,所有在 10.0.0.0到10.0.0.255之间的网站都会通过代理服务器proxy.example.com的8080端口连接(即使这个代理不可用)。其 他一切网络连接都将尝试通过proxy.example.com:8080连接,如果代理无响应则直接连接。关于PAC文件更详尽的格式说明请参考《Navigator Proxy Auto-Config File Format》,如果有兴趣你也可以阅读一些扩展内容《Automatic proxy HTTP server configuration in web browsers》。
有一点不得不提到的是:大多数系统和浏览器只支持ANSI编码的PAC文件,如果使用其他编码(如Unicode)将无法识别。

PAC文件怎么用?

对于Windows系统,直接在“Internet选项”中,“连接”标签,“局域网设置”按钮,选中“使用自动配置脚本”,下面的地址栏填写 PAC文件的URL。例如有个auto.pac在你的D盘根目录,则填写“file://d:/auto.pac”;如果这个pac在网络主机上,你就需 要填成类似“http://genghis-yang.tk/something/auto.pac”这样。

对于Firefox浏览器,“首选项”中“高级选项卡”,“网络”标签,“设置”按钮,在“自动代理配置URL”中填入你的PAC。

对于Linux系统(以ubuntu10.04为例),在“系统设置”里面打开“网络代理首选项”,在“自动代理配置”中填入PAC的URL。

其他配置方式也很雷同,这里就不赘述了。

Google Code上的一个项目——pac-maker, 做教育网代理自动配置脚本,包括一个按月更新的PAC文件和一个PAC文件自动生成程序。它主要是为教育网这个特殊群体服务的,主要目的就是让教育网不可 访问的IP自动使用代理,而可访问的则直接访问。这个工程刚刚起步,还存在一些问题,期待有人能加入进来,或者提交些Issues。
---------------

用Python重写的pac-maker项目

这次更新得比较彻底,从1.1直接到2.0版本。为什么没有1.2、1.3……因为重写了代码,这次完全用Python构建,完全不一样的体验。
这也是我第一次用Python写代码,手生得很,写了一上午才搞定。不过考虑到自己才学Python不到一周,连个Hello World都没写过,也就飘飘然了。代码很短,加上废话不到50行,这让我不得不佩服Python的高效,难怪Thinking in系列的作者Bruce Eckel都夸赞说,没有一种语言比得上Python使他的工作效率如此之高。看看版本1用C++写的代码那叫一个长,光写代码,调通,就花了快一天。再 说2.0版还加入了自动联网获取所需资源的功能,而这一功能要是用C++来写又不知道要写多少行,也不知道要花费多少时间。而Python的标准库中就包 含了我这程序中要用到的方方面面:获取网页页面的urllib库、正则表达式的re库、还有各种方便的字符串操作。得益于这些内建的库,真个两三行 Python就顶得上C++写一屏。
之前自己对各种解释性的语言很是不屑,认为C/C++才是编程王道,最让我诟病的就是那些语言孱弱的执行效率。不过随着开始写博客,开始使用一些 JavaScript和PHP,再后来看到一些别人写的Python程序,最后自己尝试,我不得不说C/C++的编程效率确实很低。上上个月C++11新 标准也正式公布,这是C++03之后8年来首次更新标准,看来C++也注意到新生代编程语言对自己的挑战了。毕竟曾经的执行效率优势在如今的4核、8核处 理器上已经体现的比较少了,编程也已经平民化,像我这种非计算机专业的也能随便开个项目了。写了这第一个Python程序我也尝到了Python的甜头, 计划着用GAE弄个在线小游戏玩玩,这就是后话了。