代理自动配置 (PAC) 文件

代理自动配置 (PAC) 文件是一个 JavaScript 函数,它决定 Web 浏览器请求(HTTP、HTTPS 和 FTP)是直接发送到目标服务器还是转发到 Web 代理服务器。PAC 文件中包含的 JavaScript 函数定义了该函数

语法

js
function FindProxyForURL(url, host) {
  // …
}

参数

url

正在访问的 URL。https:// URL 的路径和查询部分将被去除。在 Chrome(版本 52 到 73)中,您可以通过在策略中将 PacHttpsUrlStrippingEnabled 设置为 false 或使用 --unsafe-pac-url 命令行标志启动来禁用此功能(在 Chrome 74 中,只有标志有效,从 75 开始,就无法禁用路径去除;从 Chrome 81 开始,路径去除不适用于 HTTP URL,但有兴趣更改此行为以匹配 HTTPS);在 Firefox 中,首选项为 network.proxy.autoconfig_url.include_path

host

从 URL 中提取的主机名。这只是为了方便;它与 :// 和第一个 :/ 之间的字符串相同。端口号不包含在此参数中。必要时可以从 URL 中提取。

描述

返回描述配置的字符串。此字符串的格式在下面的返回值格式中定义。

返回值格式

  • JavaScript 函数返回单个字符串
  • 如果字符串为 null,则不应使用代理
  • 该字符串可以包含以下任意数量的构建块,并用分号隔开
DIRECT

连接应直接建立,不使用任何代理

PROXY host:port

应使用指定的代理

SOCKS host:port

应使用指定的 SOCKS 服务器

Firefox 的最新版本也支持

HTTP host:port

应使用指定的代理

HTTPS host:port

应使用指定的 HTTPS 代理

SOCKS4 host:port, SOCKS5 host:port

应使用指定的 SOCKS 服务器(使用指定的 SOCK 版本)

如果有多个用分号分隔的设置,则将使用最左边的设置,直到 Firefox 无法建立与代理的连接。在这种情况下,将使用下一个值,依此类推。

浏览器将在 30 分钟后自动重试先前无响应的代理。额外的尝试将从一小时开始,每次尝试之间始终增加 30 分钟。

如果所有代理都关闭,并且没有指定 DIRECT 选项,浏览器将询问是否应暂时忽略代理,并尝试直接连接。20 分钟后,浏览器将询问是否应重试代理,并在另外 40 分钟后再次询问。查询将继续进行,每次查询之间始终增加 20 分钟。

示例

PROXY w3proxy.netscape.com:8080; PROXY mozilla.netscape.com:8081

主代理是 w3proxy:8080;如果该代理关闭,请开始使用 mozilla:8081,直到主代理重新启动。

PROXY w3proxy.netscape.com:8080; PROXY mozilla.netscape.com:8081; DIRECT

与上面相同,但如果两个代理都关闭,则自动开始建立直接连接。(在上面的第一个示例中,Netscape 将询问用户是否确认建立直接连接;在本例中,没有用户干预。)

PROXY w3proxy.netscape.com:8080; SOCKS socks:1080

如果主代理关闭,请使用 SOCKS。

自动配置文件应保存到扩展名为 .pac 的文件中:proxy.pac

MIME 类型应设置为 application/x-ns-proxy-autoconfig

接下来,您应该配置服务器将 .pac 文件扩展名映射到 MIME 类型。

注意

  • JavaScript 函数应始终单独保存到文件中,但不要嵌入到 HTML 文件或任何其他文件中。
  • 本文件末尾的示例是完整的。不需要额外的语法来将其保存到文件中并使用它。(当然,必须编辑 JavaScripts 以反映您站点的域名和/或子网。)

预定义函数和环境

这些函数可用于构建 PAC 文件

注意: pactester(作为 pacparser 包的一部分)用于测试以下语法示例。

  • PAC 文件名为 proxy.pac
  • 命令行:pactester -p ~/pacparser-master/tests/proxy.pac -u https://www.mozilla.org(传递 host 参数 www.mozilla.orgurl 参数 https://www.mozilla.org

isPlainHostName()

语法

js
isPlainHostName(host)

参数

host

来自 URL 的主机名(不包括端口号)。

描述

当且仅当主机名中没有域名(没有点)时为真。

示例

js
isPlainHostName("www.mozilla.org"); // false
isPlainHostName("www"); // true

dnsDomainIs()

语法

js
dnsDomainIs(host, domain)

参数

host

是否是来自 URL 的主机名。

domain

是要测试主机名的域名。

描述

当且仅当主机名的域名匹配时返回真。

示例

js
dnsDomainIs("www.mozilla.org", ".mozilla.org") // true
dnsDomainIs("www", ".mozilla.org") // false

localHostOrDomainIs()

语法

js
localHostOrDomainIs(host, hostdom)

参数

host

来自 URL 的主机名。

hostdom

要匹配的完全限定主机名。

描述

如果主机名完全匹配指定的主机名,或者主机名中没有域名部分,但非限定主机名匹配,则为真。

示例

js
localHostOrDomainIs("www.mozilla.org", "www.mozilla.org") // true (exact match)
localHostOrDomainIs("www", "www.mozilla.org") // true (hostname match, domain not specified)
localHostOrDomainIs("www.google.com", "www.mozilla.org") // false (domain name mismatch)
localHostOrDomainIs("home.mozilla.org", "www.mozilla.org") // false (hostname mismatch)

isResolvable()

语法

js
isResolvable(host)

参数

host

是来自 URL 的主机名。

尝试解析主机名。如果成功,则返回真。

示例

js
isResolvable("www.mozilla.org") // true

isInNet()

语法

js
isInNet(host, pattern, mask)

参数

host

DNS 主机名或 IP 地址。如果传递主机名,则此函数将将其解析为 IP 地址。

pattern

点分隔格式的 IP 地址模式。

mask

IP 地址模式的掩码,用于告知 IP 地址的哪些部分应匹配。0 表示忽略,255 表示匹配。

当且仅当主机的 IP 地址匹配指定的 IP 地址模式时为真。

模式和掩码规范与 SOCKS 配置相同。

示例

js
function alertEval(str) {
  alert(`${str} is ${eval(str)}`);
}
function FindProxyForURL(url, host) {
  alertEval('isInNet(host, "192.0.2.172", "255.255.255.255")');
  // "PAC-alert: isInNet(host, "192.0.2.172", "255.255.255.255") is true"
}

dnsResolve()

js
dnsResolve(host)

参数

host

要解析的主机名。

将给定的 DNS 主机名解析为 IP 地址,并将其以点分隔格式作为字符串返回。

示例

js
dnsResolve("www.mozilla.org"); // returns the string "104.16.41.2"

convert_addr()

语法

js
convert_addr(ipaddr)

参数

ipaddr

任何点分隔地址,例如 IP 地址或掩码。

将四个点分隔的字节连接成一个 4 字节字,并将其转换为十进制。

示例

js
convert_addr("192.0.2.172"); // returns the decimal number 1745889538

myIpAddress()

语法

js
myIpAddress()

参数

(无)

返回值

返回 Firefox 运行的机器的服务器 IP 地址,以点分隔整数格式的字符串形式返回。

警告:myIpAddress() 返回与nslookup localhost在 Linux 机器上返回的服务器地址相同的 IP 地址。它不返回公共 IP 地址。

示例

js
myIpAddress() //returns the string "127.0.1.1" if you were running Firefox on that localhost

dnsDomainLevels()

语法

js
dnsDomainLevels(host)

参数

host

是来自 URL 的主机名。

返回主机名中 DNS 域名级别的数量(点的数量)。

示例

js
dnsDomainLevels("www") // 0
dnsDomainLevels("mozilla.org") // 1
dnsDomainLevels("www.mozilla.org"); // 2

shExpMatch()

语法

js
shExpMatch(str, shexp)

参数

str

是要比较的任何字符串(例如 URL 或主机名)。

shexp

是要比较的 shell 表达式。

如果字符串与指定的 shell glob 表达式匹配,则返回 true

对特定 glob 表达式语法的支持在不同的浏览器之间有所不同:*(匹配任意数量的字符)和 ?(匹配一个字符)始终受支持,而 [characters][^characters] 则由某些实现(包括 Firefox)额外支持。

注意:如果客户端支持,JavaScript 正则表达式通常提供一种更强大且一致的方法来模式匹配 URL(以及其他字符串)。

示例

js
shExpMatch("http://home.netscape.com/people/ari/index.html", "*/ari/*"); // returns true
shExpMatch("http://home.netscape.com/people/montulli/index.html", "*/ari/*"); // returns false

weekdayRange()

语法

js
weekdayRange(wd1, wd2, [gmt])

注意:(在 Firefox 49 之前)如果要将这些参数评估为范围,则 wd1 必须小于 wd2。请参阅下面的警告。

参数

wd1 和 wd2

一个有序的星期几字符串:"SUN""MON""TUE""WED""THU""FRI""SAT"

gmt

是字符串 "GMT" 或者省略。

只有第一个参数是必需的。第二个、第三个或两个都可以省略。

如果只有一个参数,则该函数在参数代表的星期几返回真值。如果字符串 "GMT" 作为第二个参数指定,则时间被认为是 GMT。否则,它们被认为是本地时区。

如果同时定义了wd1wd2,则如果当前星期几介于这两个有序星期几之间,则该条件为真。边界是包含的,但边界是有序的。如果指定了 "GMT" 参数,则时间被认为是 GMT。否则,将使用本地时区。

警告:日期的顺序很重要。在 Firefox 49 之前,weekdayRange("SUN", "SAT") 将始终评估为 true。现在,weekdayRange("WED", "SUN") 将仅在当前日期为星期三或星期天时评估为 true

示例

js
weekdayRange("MON", "FRI") // returns true Monday through Friday (local timezone)
weekdayRange("MON", "FRI", "GMT") // returns true Monday through Friday (GMT timezone)
weekdayRange("SAT") // returns true on Saturdays local time
weekdayRange("SAT", "GMT") // returns true on Saturdays GMT time
weekdayRange("FRI", "MON") // returns true Friday and Monday only (note, the order does matter!)

dateRange()

语法

js
dateRange(<day> | <month> | <year>, [gmt])  // ambiguity is resolved by assuming year is greater than 31
dateRange(<day1>, <day2>, [gmt])
dateRange(<month1>, <month2>, [gmt])
dateRange(<year1>, <year2>, [gmt])
dateRange(<day1>, <month1>, <day2>, <month2>, [gmt])
dateRange(<month1>, <year1>, <month2>, <year2>, [gmt])
dateRange(<day1>, <month1>, <year1>, <day2>, <month2>, <year2>, [gmt])

注意:(在 Firefox 49 之前)如果要将这些参数评估为范围,则 day1 必须小于 day2,month1 必须小于 month2,year1 必须小于 year2。请参阅下面的警告。

参数

day

是 1 到 31 之间的有序月份日(作为整数)。

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
month

是以下有序月份字符串之一。

"JAN"|"FEB"|"MAR"|"APR"|"MAY"|"JUN"|"JUL"|"AUG"|"SEP"|"OCT"|"NOV"|"DEC"
year

是有序的完整年份整数。例如,2016(不是 16)。

gmt

是字符串 "GMT",它使时间比较发生在 GMT 时区,或者省略。如果未指定,则时间被认为是本地时区。

如果只指定了一个值(来自每个类别:日、月、年),则该函数仅在与该规范匹配的日期返回真值。如果同时指定了两个值,则结果在这些时间之间为真,包括边界,但边界是有序的

警告:日期、月份和年份的顺序很重要;在 Firefox 49 之前,dateRange("JAN", "DEC") 将始终评估为 true。现在 dateRange("DEC", "JAN") 仅在当前月份为 12 月或 1 月时才评估为 true。

示例

js
dateRange(1) // returns true on the first day of each month, local timezone
dateRange(1, "GMT") // returns true on the first day of each month, GMT timezone
dateRange(1, 15) // returns true on the first half of each month
dateRange(24, "DEC");// returns true on 24th of December each year
dateRange("JAN", "MAR"); // returns true on the first quarter of the year

dateRange(1, "JUN", 15, "AUG");
// returns true from June 1st until August 15th, each year
// (including June 1st and August 15th)

dateRange(1, "JUN", 1995, 15, "AUG", 1995);
// returns true from June 1st, 1995, until August 15th, same year

dateRange("OCT", 1995, "MAR", 1996);
// returns true from October 1995 until March 1996
// (including the entire month of October 1995 and March 1996)

dateRange(1995);
// returns true during the entire year of 1995

dateRange(1995, 1997);
// returns true from beginning of year 1995 until the end of year 1997

timeRange()

语法

js
// The full range of expansions is analogous to dateRange.
timeRange(<hour1>, <min1>, <sec1>, <hour2>, <min2>, <sec2>, [gmt])

注意:(在 Firefox 49 之前)如果要将函数评估这些参数作为范围,则类别 hour1、min1、sec1 必须小于类别 hour2、min2、sec2。请参阅下面的警告。

参数

hour

小时从 0 到 23。(0 为午夜,23 为晚上 11 点。)

min

分钟从 0 到 59。

sec

秒从 0 到 59。

gmt

字符串 "GMT" 代表 GMT 时区,或者不指定,代表本地时区。

如果只指定一个值(从每个类别:小时、分钟、秒),则函数仅在与该规范匹配的时间才返回 true 值。如果指定两个值,则结果在这些时间之间为 true,包括边界,但边界是有序的

警告:小时、分钟、秒的顺序很重要;在 Firefox 49 之前,timeRange(0, 23) 将始终评估为 true。现在 timeRange(23, 0) 仅在当前小时为 23:00 或午夜时才评估为 true。

示例

js
timerange(12); // returns true from noon to 1pm
timerange(12, 13) // returns true from noon to 1pm
timerange(12, "GMT") // returns true from noon to 1pm, in the GMT timezone
timerange(9, 17) // returns true from 9am to 5pm
timerange(8, 30, 17, 0) // returns true from 8:30am to 5:00pm
timerange(0, 0, 0, 0, 0, 30) // returns true between midnight and 30 seconds past midnight

alert()

语法

js
alert(message)

参数

message

要记录的字符串

在浏览器控制台中记录消息。

示例

js
alert(`${host} = ${dnsResolve(host)}`) // logs the host name and its IP address
alert("Error: shouldn't reach this clause.") // log a simple message

示例 1

除本地主机外,所有内容都使用代理

注意:由于以下所有示例都非常具体,因此尚未进行测试。

所有未完全限定的主机或位于本地域中的主机将直接连接。其他所有内容都将通过 w3proxy.mozilla.org:8080 连接。如果代理出现故障,连接将自动变为直接连接。

js
function FindProxyForURL(url, host) {
  if (isPlainHostName(host) || dnsDomainIs(host, ".mozilla.org")) {
    return "DIRECT";
  } else {
    return "PROXY w3proxy.mozilla.org:8080; DIRECT";
  }
}

注意:对于只有一个代理的情况,这是最简单、最有效的自动配置文件。

示例 2

如上所述,但对位于防火墙外部的本地服务器使用代理

如果有一些主机(例如主 Web 服务器)属于本地域,但位于防火墙外部,并且只能通过代理服务器访问,则可以使用 localHostOrDomainIs() 函数处理这些例外情况。

js
function FindProxyForURL(url, host) {
  if (
    (isPlainHostName(host) || dnsDomainIs(host, ".mozilla.org")) &&
    !localHostOrDomainIs(host, "www.mozilla.org") &&
    !localHostOrDomainIs(host, "merchant.mozilla.org")
  ) {
    return "DIRECT";
  } else {
    return "PROXY w3proxy.mozilla.org:8080; DIRECT";
  }
}

上面的示例将使用代理来访问除 mozilla.org 域中的本地主机以外的所有内容,并且 www.mozilla.orgmerchant.mozilla.org 主机将通过代理访问。

注意:为了效率,上面的例外情况的顺序:localHostOrDomainIs() 函数仅针对位于本地域中的 URL 执行,而不是针对每个 URL 执行。请注意 表达式之前的括号和 表达式,以实现上述高效的行为。

示例 3

仅在无法解析主机时使用代理

此示例将在内部 DNS 服务器设置为只能解析内部主机名的环境中工作,目标是仅对无法解析的主机使用代理。

js
function FindProxyForURL(url, host) {
  if (isResolvable(host)) {
    return "DIRECT";
  }
  return "PROXY proxy.mydomain.com:8080";
}

上述操作需要每次都咨询 DNS;它可以与其他规则智能地组合在一起,以便仅在其他规则没有产生结果时才咨询 DNS。

js
function FindProxyForURL(url, host) {
  if (
    isPlainHostName(host) ||
    dnsDomainIs(host, ".mydomain.com") ||
    isResolvable(host)
  ) {
    return "DIRECT";
  }
  return "PROXY proxy.mydomain.com:8080";
}

示例 4

基于子网的决策

在此示例中,给定子网中的所有主机都直接连接,其他主机通过代理连接。

js
function FindProxyForURL(url, host) {
  if (isInNet(host, "192.0.2.172", "255.255.0.0")) {
    return "DIRECT";
  }
  return "PROXY proxy.mydomain.com:8080";
}

同样,可以通过在开头添加冗余规则来最大程度地减少对上述 DNS 服务器的使用。

js
function FindProxyForURL(url, host) {
  if (
    isPlainHostName(host) ||
    dnsDomainIs(host, ".mydomain.com") ||
    isInNet(host, "192.0.2.0", "255.255.0.0")
  ) {
    return "DIRECT";
  } else {
    return "PROXY proxy.mydomain.com:8080";
  }
}

示例 5

基于 URL 模式的负载均衡/路由

此示例更复杂。有四个 (4) 代理服务器;其中一个是对其他所有服务器的热备,因此,如果剩余的三个服务器中的任何一个出现故障,第四个服务器将接管。此外,剩余的三个代理服务器根据 URL 模式共享负载,这使得它们的缓存更加有效(三个服务器上只有一份任何文档的副本 - 与每个服务器上都有一份副本相反)。负载的分配方式如下

代理 目的
#1 .com 域
#2 .edu 域
#3 所有其他域
#4 热备

所有本地访问都希望直接进行。所有代理服务器都在端口 8080 上运行(它们不需要这样做,你只需更改端口,但请记住修改两端的配置)。请注意,字符串如何使用 JavaScript 中的 + 运算符连接。

js
function FindProxyForURL(url, host) {
  if (isPlainHostName(host) || dnsDomainIs(host, ".mydomain.com")) {
    return "DIRECT";
  } else if (shExpMatch(host, "*.com")) {
    return "PROXY proxy1.mydomain.com:8080; PROXY proxy4.mydomain.com:8080";
  } else if (shExpMatch(host, "*.edu")) {
    return "PROXY proxy2.mydomain.com:8080; PROXY proxy4.mydomain.com:8080";
  } else {
    return "PROXY proxy3.mydomain.com:8080; PROXY proxy4.mydomain.com:8080";
  }
}

示例 6

为特定协议设置代理

大多数标准 JavaScript 功能都可以在 FindProxyForURL() 函数中使用。例如,要根据协议设置不同的代理,可以使用 startsWith() 函数。

js
function FindProxyForURL(url, host) {
  if (url.startsWith("http:")) {
    return "PROXY http-proxy.mydomain.com:8080";
  } else if (url.startsWith("ftp:")) {
    return "PROXY ftp-proxy.mydomain.com:8080";
  } else if (url.startsWith("gopher:")) {
    return "PROXY gopher-proxy.mydomain.com:8080";
  } else if (url.startsWith("https:") || url.startsWith("snews:")) {
    return "PROXY security-proxy.mydomain.com:8080";
  }
  return "DIRECT";
}

注意:可以使用前面介绍的 shExpMatch() 函数来实现相同的目的。

例如

js
if (shExpMatch(url, "http:*")) {
  return "PROXY http-proxy.mydomain.com:8080";
}

注意:自动配置文件可以由 CGI 脚本输出。例如,这在使自动配置文件根据客户端 IP 地址(CGI 中的 REMOTE_ADDR 环境变量)以不同方式运行时很有用。

应该仔细考虑 isInNet()isResolvable()dnsResolve() 函数的使用,因为它们需要咨询 DNS 服务器。所有其他与自动配置相关的函数都是简单的字符串匹配函数,不需要使用 DNS 服务器。如果使用代理,代理将执行其 DNS 查找,这会对 DNS 服务器的影响加倍。大多数情况下,这些函数不是实现预期结果所必需的。

历史和实现

代理自动配置是在 1990 年代后期随着 JavaScript 的引入一起引入 Netscape Navigator 2.0 的。Netscape 的开源最终导致了 Firefox 本身的出现。

因此,PAC 及其 JavaScript 库的“最初”实现是早期版本的 Firefox 中的 nsProxyAutoConfig.js。这些实用程序在许多其他开源系统中都可以找到,包括 Chromium。Firefox 后来将该文件集成到 ProxyAutoConfig.cpp 中,作为 C++ 字符串文字。要将其提取到单独的文件中,只需使用 console.log 指令将该块复制到 JavaScript 中以打印它即可。

Microsoft 通常会创建自己的实现。以前 他们的库存在一些问题,但现在大多数问题都已解决。他们定义了 一些新的“Ex”后缀函数,围绕地址处理部分以支持 IPv6。Chromium 支持此功能,但 Firefox 尚未支持 (bugzilla #558253)。