代理自動配置 (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)。

另見