代理自动配置 (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

主机

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

描述

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

返回值格式

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

连接应直接进行,不使用任何代理

PROXY 主机:端口

应使用指定的代理

SOCKS 主机:端口

应使用指定的 SOCKS 服务器

Firefox 的最新版本也支持

HTTP 主机:端口

应使用指定的代理

HTTPS 主机:端口

应使用指定的 HTTPS 代理

SOCKS4 主机:端口, SOCKS5 主机:端口

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

如果存在多个以分号分隔的设置,则使用最左侧的设置,直到 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 文件或任何其他文件中。
  • 本文档末尾的示例是完整的。无需额外的语法即可将其保存到文件中并使用。(当然,必须编辑 JavaScript 以反映您站点的域名和/或子网。)

预定义函数和环境

这些函数可用于构建 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)

参数

主机

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

描述

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

示例

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

dnsDomainIs()

语法

js
dnsDomainIs(host, domain)

参数

主机

是 URL 中的主机名。

是要针对主机名测试的域名。

描述

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

示例

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

localHostOrDomainIs()

语法

js
localHostOrDomainIs(host, hostDom)

参数

主机

URL 中的主机名。

hostDom

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

描述

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

示例

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)

参数

主机

是 URL 中的主机名。

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

示例

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

isInNet()

语法

js
isInNet(host, pattern, mask)

参数

主机

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

pattern

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

mask

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

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

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

示例

js
function FindProxyForURL(url, host) {
  alert(isInNet(host, "192.0.2.172", "255.255.255.255"));
  // "PAC-alert: true"
}

dnsResolve()

js
dnsResolve(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 地址,格式为点分隔的整数字符串。为了更具帮助性,它将尝试几种替代方案,然后才回退到回环地址(例如 127.0.0.1)。

示例

js
myIpAddress()

dnsDomainLevels()

语法

js
dnsDomainLevels(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 通配符表达式,则返回 true

对特定通配符表达式语法的支持因浏览器而异:*(匹配任意数量的字符)和 ?(匹配一个字符)始终支持,而 [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)
weekdayRange(wd1, wd2, gmt)

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

参数

wd1 和 wd2

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

gmt

是字符串“GMT”或省略。

只有第一个参数是强制性的。第二个、第三个或两者都可以省略。

如果只有一个参数存在,函数在参数表示的星期几返回 true 值。如果将字符串“GMT”指定为第二个参数,则时间被视为 GMT。否则,它们被假定为本地时区。

如果同时定义了 wd1wd2,则当当前星期几在这两个有序星期几之间时,条件为 true。边界是包含的,但边界是有序的。如果指定了“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(dayOrMonthOrYear)
dateRange(dayOrMonthOrYear, gmt)  // ambiguity is resolved by assuming year is greater than 31
dateRange(day1, day2)
dateRange(day1, day2, gmt)
dateRange(month1, month2)
dateRange(month1, month2, gmt)
dateRange(year1, year2)
dateRange(year1, year2, gmt)
dateRange(day1, month1, day2, month2)
dateRange(day1, month1, day2, month2, gmt)
dateRange(month1, year1, month2, year2)
dateRange(month1, year1, month2, year2, gmt)
dateRange(day1, month1, year1, day2, month2, year2)
dateRange(day1, month1, year1, day2, month2, year2, gmt)

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

参数

是介于 1 和 31 之间的有序日期(整数)。

月份

是有序月份字符串之一:"JAN""FEB""MAR""APR""MAY""JUN""JUL""AUG""SEP""OCT""NOV""DEC"

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

gmt

是字符串“GMT”(使时间比较发生在 GMT 时区)或省略。如果未指定,则时间被视为本地时区。

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

警告: 日、月、年的顺序很重要;在 Firefox 49 之前,dateRange("JAN", "DEC") 总是评估为 true。现在,dateRange("DEC", "JAN") 仅当当前月份是十二月或一月时才评估为 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)
timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt)

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

参数

小时

是 0 到 23 之间的小时。(0 是午夜,23 是晚上 11 点。)

min

分钟从 0 到 59。

秒从 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 message

示例 1

除本地主机外,所有请求都使用代理

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

所有未完全限定的主机,或本地域中的主机,都将直接连接。所有其他请求都将通过 w3proxy.mozilla.org:8080。如果代理关闭,连接将自动变为直接连接

js
function FindProxyForURL(url, host) {
  if (isPlainHostName(host) || dnsDomainIs(host, ".mozilla.org")) {
    return "DIRECT";
  }
  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";
  }
  return "PROXY w3proxy.mozilla.org:8080; DIRECT";
}

上述示例将对除 mozilla.org 域中的本地主机之外的所有请求使用代理,但 www.mozilla.orgmerchant.mozilla.org 主机将通过代理访问。

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

示例 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";
  }
  return "PROXY proxy.mydomain.com:8080";
}

示例 5

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

此示例更为复杂。有四个(4)代理服务器;其中一个作为所有其他服务器的热备用,因此如果其余三个中的任何一个出现故障,第四个将接管。此外,其余三个代理服务器根据 URL 模式分担负载,这使得它们的缓存更有效(三个服务器上任何文档只有一个副本 - 而不是每个服务器上一个副本)。负载分布如下

Proxy 用途
#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";
  }
  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++ 字符串字面量。要将其提取到自己的文件中,只需将该块复制到 JavaScript 中,并使用 console.log 指令将其打印出来。

Microsoft 通常有自己的实现。过去 他们的库存在一些问题,但大多数问题现已解决。他们定义了 一些新的以 "Ex" 结尾的函数,用于处理 IPv6 地址。该功能受 Chromium 支持,但 Firefox 尚未支持(bugzilla #558253)。

另见