前言
大家好,我是老馬。很高興遇到你。
我們為 java 開發者實現了 java 版本的 nginx
https://github.com/houbb/nginx4j
如果你想知道 servlet 如何處理的,可以參考我的另一個專案:
手寫從零實現簡易版 tomcat minicat
手寫 nginx 系列
如果你對 nginx 原理感興趣,可以閱讀:
從零手寫實現 nginx-01-為什麼不能有 java 版本的 nginx?
從零手寫實現 nginx-02-nginx 的核心能力
從零手寫實現 nginx-03-nginx 基於 Netty 實現
從零手寫實現 nginx-04-基於 netty http 出入參最佳化處理
從零手寫實現 nginx-05-MIME型別(Multipurpose Internet Mail Extensions,多用途網際網路郵件擴充套件型別)
從零手寫實現 nginx-06-資料夾自動索引
從零手寫實現 nginx-07-大檔案下載
從零手寫實現 nginx-08-範圍查詢
從零手寫實現 nginx-09-檔案壓縮
從零手寫實現 nginx-10-sendfile 零複製
從零手寫實現 nginx-11-file+range 合併
從零手寫實現 nginx-12-keep-alive 連線複用
從零手寫實現 nginx-13-nginx.conf 配置檔案介紹
從零手寫實現 nginx-14-nginx.conf 和 hocon 格式有關係嗎?
從零手寫實現 nginx-15-nginx.conf 如何透過 java 解析處理?
從零手寫實現 nginx-16-nginx 支援配置多個 server
從零手寫實現 nginx-17-nginx 預設配置最佳化
從零手寫實現 nginx-18-nginx 請求頭+響應頭操作
從零手寫實現 nginx-19-nginx cors
從零手寫實現 nginx-20-nginx 佔位符 placeholder
從零手寫實現 nginx-21-nginx modules 模組資訊概覽
從零手寫實現 nginx-22-nginx modules 分模組載入最佳化
從零手寫實現 nginx-23-nginx cookie 的操作處理
從零手寫實現 nginx-24-nginx IF 指令
從零手寫實現 nginx-25-nginx map 指令
從零手寫實現 nginx-26-nginx rewrite 指令
從零手寫實現 nginx-27-nginx return 指令
從零手寫實現 nginx-28-nginx error_pages 指令
從零手寫實現 nginx-29-nginx try_files 指令
nginx try_files 指令是什麼?
Nginx 的 try_files
指令用於嘗試一個或多個檔案路徑或 URI,以處理客戶端請求。如果找到一個檔案或 URI 存在,則返回該檔案或執行該 URI。如果沒有找到,則返回一個指定的錯誤碼或重定向到一個預設的處理程式。try_files
指令通常用於靜態檔案服務、動態內容處理和錯誤處理。
語法
try_files file1 [file2 ... filen] uri|=code;
- file1, file2, ... filen: 依次檢查這些檔案或目錄的存在性。
- uri: 如果前面的檔案或目錄都不存在,重定向到指定的 URI。
- =code: 如果前面的檔案或目錄都不存在,返回指定的 HTTP 狀態碼。
示例
靜態檔案服務
優先返回靜態檔案,如果檔案不存在,則返回 404 錯誤。
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
try_files $uri $uri/ =404;
}
}
動態內容處理
優先返回靜態檔案,如果檔案不存在,則將請求重定向到一個 PHP 處理程式。
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
root /var/www/html;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
單頁應用(SPA)
所有路徑都指向入口的 index.html
檔案,以支援前端路由。
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
}
工作原理
- 嘗試多個檔案路徑:按順序檢查指定的檔案或目錄是否存在。如果找到一個存在的檔案或目錄,則立即返回該檔案或目錄。
- 重定向到 URI:如果所有指定的檔案或目錄都不存在,則重定向到一個指定的 URI。這通常用於將請求傳遞給動態處理程式。
- 返回指定的 HTTP 狀態碼:如果所有指定的檔案或目錄都不存在,並且未指定 URI,則返回指定的 HTTP 狀態碼(如
404
)。
優點
- 靈活性高:可以輕鬆處理靜態檔案、動態內容和錯誤處理。
- 配置簡潔:透過一條指令即可實現多種路徑檢查和處理邏輯。
- 效能最佳化:優先處理靜態檔案,避免不必要的動態處理,提高響應速度。
缺點
- 除錯困難:由於會依次檢查多個路徑,除錯和排查問題時可能比較複雜。
- 靈活性有限:無法進行條件判斷或複雜的重寫規則處理,需要結合其他指令使用。
- 錯誤處理簡單:只能指定簡單的錯誤處理方式,無法進行復雜的錯誤處理邏輯。
總結
Nginx 的 try_files
指令是一種非常強大和靈活的工具,適用於處理多種請求路徑和檔案檢查需求。
透過適當的配置,可以顯著提高伺服器的效能和響應速度,同時簡化配置檔案的編寫和維護。
java 實現
整體思路
1)遍歷非最後一個的 uri,替換佔位符,判斷檔案是否存在,存在則返回。
2)判斷最後一個 uri 或者 code,直接處理對應的邏輯。
核心邏輯
/**
* try_files path1 path2 ... final;
*
* - `path1`, `path2`, ...:要檢查的檔案或 URI 列表。可以是相對路徑或絕對路徑。
*
* - `final`:如果前面的所有路徑都不存在,最後一個引數可以是一個 URI,Nginx 將內部重定向到該 URI,或者是一個 HTTP 狀態碼(如 404),用於返回相應的錯誤。
*
* @see INginxPlaceholder 佔位符
* @see com.github.houbb.nginx4j.support.request.dispatch.http.NginxRequestDispatchHttpReturn 設定對應的返回碼 =xxx
*/
public class NginxTryFilesDefault implements INginxTryFiles{
private static final Log log = LogFactory.getLog(NginxTryFilesDefault.class);
/**
* 處理 try_files 指令
*
* @param request 請求
* @param nginxConfig 配置
* @param context 上下文
*/
public void tryFiles(FullHttpRequest request,
final NginxConfig nginxConfig,
NginxRequestDispatchContext context) {
// 獲取當前的 location
List<NginxCommonConfigEntry> directiveList = InnerNginxContextUtil.getLocationDirectives(context);
Map<String, List<NginxCommonConfigEntry>> directiveMap = InnerNginxContextUtil.getLocationDirectiveMap(directiveList);
List<NginxCommonConfigEntry> tryFiles = directiveMap.get(NginxDirectiveEnum.TRY_FILES.getCode());
if(CollectionUtil.isEmpty(tryFiles)) {
return;
}
NginxCommonConfigEntry firstEntry = tryFiles.get(0);
// 遍歷非 final 的檔案資訊
String notFinalUri = getNotFinalMatchedFileUri(firstEntry, context);
if(StringUtil.isNotEmpty(notFinalUri)) {
request.setUri(notFinalUri);
return;
}
// 判斷 final 變數
final String lastUri = firstEntry.getValues().get(firstEntry.getValues().size()-1);
if(lastUri.startsWith("=")) {
// 拆分為 return
NginxReturnResult result = new NginxReturnResult();
result.setCode(Integer.parseInt(lastUri.substring(1)));
result.setValue("try_files final");
context.setNginxReturnResult(result);
return;
}
String lastReplaceUri = replacePlaceholders(lastUri, context.getPlaceholderMap());
request.setUri(lastReplaceUri);
}
/**
* 獲取匹配的檔案 url
* @param entry 實體
* @param context 上下文
* @return 結果
*/
private String getNotFinalMatchedFileUri(final NginxCommonConfigEntry entry,
NginxRequestDispatchContext context) {
List<String> values = entry.getValues();
for(int i = 0; i < values.size()-1; i++) {
String replacedUri = getMatchedFileUri(values.get(i), context);
if(StringUtil.isNotEmpty(replacedUri)) {
return replacedUri;
}
}
return null;
}
private String getMatchedFileUri(final String requestUri,
NginxRequestDispatchContext context) {
final Map<String, Object> replaceMap = context.getPlaceholderMap();
String replacedUri = replacePlaceholders(requestUri, replaceMap);
// 判斷檔案是否存
File file = InnerFileUtil.getTargetFile(replacedUri, context);
if(file.exists()) {
log.info("Nginx getMatchedFileUri file={}", file.getAbsolutePath());
return replacedUri;
}
return null;
}
/**
* 替換字串中的佔位符
*
* @param input 使用者輸入的字串,包含佔位符
* @param variables 儲存佔位符及其替換值的 Map
* @return 替換後的字串
*/
public static String replacePlaceholders(String input, Map<String, Object> variables) {
// 使用 StringBuilder 來構建替換後的字串
StringBuilder result = new StringBuilder(input);
// 遍歷 Map 進行替換
for (Map.Entry<String, Object> entry : variables.entrySet()) {
String placeholder = entry.getKey();
String replacement = String.valueOf(entry.getValue());
// 使用 String 的 replace 方法替換所有佔位符
int start = result.indexOf(placeholder);
while (start != -1) {
result.replace(start, start + placeholder.length(), replacement);
start = result.indexOf(placeholder, start + replacement.length());
}
}
return result.toString();
}
}
小結
Nginx 的 try_files
指令是一個強大且靈活的工具,用於處理靜態檔案、友好 URL 重寫和自定義錯誤處理。
它透過按順序檢查多個路徑並執行相應操作,使配置檔案更加簡潔和高效。
主要用途
- 靜態檔案服務:首先嚐試提供靜態檔案,如果找不到則執行其他操作。
- 友好 URL 重寫:用於支援偽靜態 URL,將請求重寫到實際的檔案路徑或動態指令碼。
- 錯誤處理:如果所有嘗試的檔案都不存在,則返回一個特定的錯誤頁面。
工作機制
try_files
按順序檢查列出的每個路徑。- 如果找到了一個存在的檔案或目錄,則立即停止進一步檢查並使用該檔案或目錄響應請求。
- 如果所有列出的路徑都不存在,則執行最後指定的 URI,這通常是一個錯誤頁面或其他處理邏輯。
優點
- 高效靜態檔案處理:減少了不必要的動態請求處理。
- 靈活的錯誤處理:可以輕鬆配置自定義錯誤頁面或重寫規則。
- 簡潔配置:使複雜的檔案查詢和重寫邏輯更加直觀和易於管理。