代理自動配置 (PAC) 檔案
代理自動配置 (PAC) 檔案是一個 JavaScript 函式,它決定 Web 瀏覽器請求(HTTP、HTTPS 和 FTP)是直接傳送到目標伺服器還是轉發到 Web 代理伺服器。PAC 檔案中包含的 JavaScript 函式定義了該函式
語法
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 檔案
- 基於主機名的條件
- 相關實用程式函式
- 基於 URL/主機名的條件
- 基於時間條件
- 日誌記錄實用程式
- 以前已經定義了一個關聯陣列(物件),因為當時 JavaScript 程式碼無法自行定義它
ProxyConfig.bindingsDeprecated
注意: pactester(作為 pacparser 包的一部分)用於測試以下語法示例。
- PAC 檔名為
proxy.pac - 命令列:
pactester -p ~/pacparser-master/tests/proxy.pac -u https://www.mozilla.org(傳遞host引數www.mozilla.org和url引數https://www.mozilla.org)
isPlainHostName()
語法
isPlainHostName(host)
引數
- host
-
來自 URL 的主機名(不包括埠號)。
描述
當且僅當主機名中沒有域名(沒有點)時為真。
示例
isPlainHostName("www.mozilla.org"); // false
isPlainHostName("www"); // true
dnsDomainIs()
localHostOrDomainIs()
語法
localHostOrDomainIs(host, hostdom)
引數
描述
如果主機名完全匹配指定的主機名,或者主機名中沒有域名部分,但非限定主機名匹配,則為真。
示例
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()
語法
isResolvable(host)
引數
- host
-
是來自 URL 的主機名。
嘗試解析主機名。如果成功,則返回真。
示例
isResolvable("www.mozilla.org") // true
isInNet()
語法
isInNet(host, pattern, mask)
引數
- host
-
DNS 主機名或 IP 地址。如果傳遞主機名,則此函式將將其解析為 IP 地址。
- pattern
-
點分隔格式的 IP 地址模式。
- mask
-
IP 地址模式的掩碼,用於告知 IP 地址的哪些部分應匹配。0 表示忽略,255 表示匹配。
當且僅當主機的 IP 地址匹配指定的 IP 地址模式時為真。
模式和掩碼規範與 SOCKS 配置相同。
示例
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()
dnsResolve(host)
引數
- host
-
要解析的主機名。
將給定的 DNS 主機名解析為 IP 地址,並將其以點分隔格式作為字串返回。
示例
dnsResolve("www.mozilla.org"); // returns the string "104.16.41.2"
convert_addr()
語法
convert_addr(ipaddr)
引數
- ipaddr
-
任何點分隔地址,例如 IP 地址或掩碼。
將四個點分隔的位元組連線成一個 4 位元組字,並將其轉換為十進位制。
示例
convert_addr("192.0.2.172"); // returns the decimal number 1745889538
myIpAddress()
語法
myIpAddress()
引數
(無)
返回值
返回 Firefox 執行的機器的伺服器 IP 地址,以點分隔整數格式的字串形式返回。
警告:myIpAddress() 返回與nslookup localhost在 Linux 機器上返回的伺服器地址相同的 IP 地址。它不返回公共 IP 地址。
示例
myIpAddress() //returns the string "127.0.1.1" if you were running Firefox on that localhost
dnsDomainLevels()
語法
dnsDomainLevels(host)
引數
- host
-
是來自 URL 的主機名。
返回主機名中 DNS 域名級別的數量(點的數量)。
示例
dnsDomainLevels("www") // 0
dnsDomainLevels("mozilla.org") // 1
dnsDomainLevels("www.mozilla.org"); // 2
shExpMatch()
語法
shExpMatch(str, shexp)
引數
如果字串與指定的 shell glob 表示式匹配,則返回 true。
對特定 glob 表示式語法的支援在不同的瀏覽器之間有所不同:*(匹配任意數量的字元)和 ?(匹配一個字元)始終受支援,而 [characters] 和 [^characters] 則由某些實現(包括 Firefox)額外支援。
注意:如果客戶端支援,JavaScript 正則表示式通常提供一種更強大且一致的方法來模式匹配 URL(以及其他字串)。
示例
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()
語法
weekdayRange(wd1, wd2, [gmt])
注意:(在 Firefox 49 之前)如果要將這些引數評估為範圍,則 wd1 必須小於 wd2。請參閱下面的警告。
引數
只有第一個引數是必需的。第二個、第三個或兩個都可以省略。
如果只有一個引數,則該函式在引數代表的星期幾返回真值。如果字串 "GMT" 作為第二個引數指定,則時間被認為是 GMT。否則,它們被認為是本地時區。
如果同時定義了wd1 和wd2,則如果當前星期幾介於這兩個有序星期幾之間,則該條件為真。邊界是包含的,但邊界是有序的。如果指定了 "GMT" 引數,則時間被認為是 GMT。否則,將使用本地時區。
警告:日期的順序很重要。在 Firefox 49 之前,weekdayRange("SUN", "SAT") 將始終評估為 true。現在,weekdayRange("WED", "SUN") 將僅在當前日期為星期三或星期天時評估為 true。
示例
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()
語法
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"
如果只指定了一個值(來自每個類別:日、月、年),則該函式僅在與該規範匹配的日期返回真值。如果同時指定了兩個值,則結果在這些時間之間為真,包括邊界,但邊界是有序的。
警告:日期、月份和年份的順序很重要;在 Firefox 49 之前,dateRange("JAN", "DEC") 將始終評估為 true。現在 dateRange("DEC", "JAN") 僅在當前月份為 12 月或 1 月時才評估為 true。
示例
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()
語法
// 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。
示例
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()
語法
alert(message)
引數
- message
-
要記錄的字串
在瀏覽器控制檯中記錄訊息。
示例
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 連線。如果代理出現故障,連線將自動變為直接連線。
function FindProxyForURL(url, host) {
if (isPlainHostName(host) || dnsDomainIs(host, ".mozilla.org")) {
return "DIRECT";
} else {
return "PROXY w3proxy.mozilla.org:8080; DIRECT";
}
}
注意:對於只有一個代理的情況,這是最簡單、最有效的自動配置檔案。
示例 2
如上所述,但對位於防火牆外部的本地伺服器使用代理
如果有一些主機(例如主 Web 伺服器)屬於本地域,但位於防火牆外部,並且只能透過代理伺服器訪問,則可以使用 localHostOrDomainIs() 函式處理這些例外情況。
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.org 和 merchant.mozilla.org 主機將透過代理訪問。
注意:為了效率,上面的例外情況的順序:localHostOrDomainIs() 函式僅針對位於本地域中的 URL 執行,而不是針對每個 URL 執行。請注意 或 表示式之前的括號和 和 表示式,以實現上述高效的行為。
示例 3
僅在無法解析主機時使用代理
此示例將在內部 DNS 伺服器設定為只能解析內部主機名的環境中工作,目標是僅對無法解析的主機使用代理。
function FindProxyForURL(url, host) {
if (isResolvable(host)) {
return "DIRECT";
}
return "PROXY proxy.mydomain.com:8080";
}
上述操作需要每次都諮詢 DNS;它可以與其他規則智慧地組合在一起,以便僅在其他規則沒有產生結果時才諮詢 DNS。
function FindProxyForURL(url, host) {
if (
isPlainHostName(host) ||
dnsDomainIs(host, ".mydomain.com") ||
isResolvable(host)
) {
return "DIRECT";
}
return "PROXY proxy.mydomain.com:8080";
}
示例 4
基於子網的決策
在此示例中,給定子網中的所有主機都直接連線,其他主機透過代理連線。
function FindProxyForURL(url, host) {
if (isInNet(host, "192.0.2.172", "255.255.0.0")) {
return "DIRECT";
}
return "PROXY proxy.mydomain.com:8080";
}
同樣,可以透過在開頭新增冗餘規則來最大程度地減少對上述 DNS 伺服器的使用。
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 中的 + 運算子連線。
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() 函式。
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() 函式來實現相同的目的。
例如
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)。