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