options預檢請求是幹嘛的?options請求一定會在post請求之前發送嗎?前端或者後端開發需要手動干預這個預檢請求嗎?不用文檔定義堆砌名詞,從前後端角度單獨分析,大白話帶你瞭解! ...
摘要:options預檢請求是幹嘛的?options請求一定會在post請求之前發送嗎?前端或者後端開發需要手動干預這個預檢請求嗎?不用文檔定義堆砌名詞,從前後端角度單獨分析,大白話帶你瞭解!
本文分享自華為雲社區《從前後端的角度分析options預檢請求——打破前後端聯調的理解障礙》,作者: 磚業洋__ 。
options預檢請求是幹嘛的?options請求一定會在post請求之前發送嗎?前端或者後端開發需要手動干預這個預檢請求嗎?不用文檔定義堆砌名詞,從前後端角度單獨分析,大白話帶你瞭解!
從前端的角度看options——post請求之前一定會有options請求?信口雌黃!
你是否經常看到這種跨域請求錯誤?
這是因為伺服器不允許跨域請求,這裡會深入講一講OPTIONS請求。
只有在滿足一定條件的跨域請求中,瀏覽器才會發送OPTIONS請求(預檢請求)。這些請求被稱為“非簡單請求”。反之,如果一個跨域請求被認為是“簡單請求”,那麼瀏覽器將不會發送OPTIONS請求。
簡單請求需要滿足以下條件:
- 只使用以下HTTP方法之一:GET、HEAD或POST。
- 只使用以下HTTP頭部:Accept、Accept-Language、Content-Language、Content-Type。
- Content-Type的值僅限於:application/x-www-form-urlencoded、multipart/form-data或text/plain。
如果一個跨域請求不滿足以上所有條件,那麼它被認為是非簡單請求。對於非簡單請求,瀏覽器會在實際請求(例如PUT、DELETE、PATCH或具有自定義頭部和其他Content-Type的POST請求)之前發送OPTIONS請求(預檢請求)。
舉個例子吧,口嗨半天是看不懂的,讓我們看看 POST請求在什麼情況下不發送OPTIONS請求
提示:當一個跨域POST請求滿足簡單請求條件時,瀏覽器不會發送OPTIONS請求(預檢請求)。以下是一個滿足簡單請求條件的POST請求示例:
// 使用Fetch API發送跨域POST請求 fetch("https://example.com/api/data", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "key1=value1&key2=value2" }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error("Error:", error));
在這個示例中,我們使用Fetch API發送了一個跨域POST請求。請求滿足以下簡單請求條件:
- 使用POST方法。
- 使用的HTTP頭部僅包括Content-Type。
- Content-Type的值為"application/x-www-form-urlencoded",屬於允許的三種類型之一(application/x-www-form-urlencoded、multipart/form-data或text/plain)。
因為這個請求滿足了簡單請求條件,所以瀏覽器不會發送OPTIONS請求(預檢請求)。
我們再看看什麼情況下POST請求之前會發送OPTIONS請求,同樣用代碼說明,進行對比
提示:在跨域請求中,如果POST請求不滿足簡單請求條件,瀏覽器會在實際POST請求之前發送OPTIONS請求(預檢請求)。
// 使用Fetch API發送跨域POST請求 fetch("https://example.com/api/data", { method: "POST", headers: { "Content-Type": "application/json", "X-Custom-Header": "custom-value" }, body: JSON.stringify({ key1: "value1", key2: "value2" }) }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error("Error:", error));
在這個示例中,我們使用Fetch API發送了一個跨域POST請求。請求不滿足簡單請求條件,因為:
- 使用了非允許範圍內的Content-Type值("application/json" 不屬於 application/x-www-form-urlencoded、multipart/form-data或text/plain)。
- 使用了一個自定義HTTP頭部 “X-Custom-Header”,這不在允許的頭部列表中。
因為這個請求不滿足簡單請求條件,所以在實際POST請求之前,瀏覽器會發送OPTIONS請求(預檢請求)。
你可以按F12直接在Console輸入查看Network,儘管這個網址不存在,但是不影響觀察OPTIONS請求,對比一下我這兩個例子。
總結:當進行非簡單跨域POST請求時,瀏覽器會在實際POST請求之前發送OPTIONS預檢請求,詢問伺服器是否允許跨域POST請求。如果伺服器不允許跨域請求,瀏覽器控制台會顯示跨域錯誤提示。如果伺服器允許跨域請求,那麼瀏覽器會繼續發送實際的POST請求。而對於滿足簡單請求條件的跨域POST請求,瀏覽器不會發送OPTIONS預檢請求。
後端可以通過設置Access-Control-Max-Age來控制OPTIONS請求的發送頻率。OPTIONS請求沒有響應數據(response data),這是因為OPTIONS請求的目的是為了獲取伺服器對於跨域請求的配置信息(如允許的請求方法、允許的請求頭部等),而不是為了獲取實際的業務數據,OPTIONS請求不會命中後端某個介面。因此,當伺服器返回OPTIONS響應時,響應中主要包含跨域配置信息,而不會包含實際的業務數據
本地調試一下,前端發送POST請求,後端在POST方法裡面打斷點調試時,也不會阻礙OPTIONS請求的返回
從後端的角度看options——post請求之前一定會有options請求?胡說八道!
在配置跨域時,伺服器需要處理OPTIONS請求,以便在響應頭中返回跨域配置信息。這個過程通常是由伺服器的跨域中間件(Node.js—Express框架的cors中間件、Python—Flask框架的flask_cors擴展)或過濾器(Java—SpringBoot框架的跨域過濾器)自動完成的,而無需開發人員手動處理。
以下是使用Spring Boot的一個跨域過濾器,供參考
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration public class CorsConfig { public CorsConfig() { } @Bean public CorsFilter corsFilter() { // 1. 添加cors配置信息 CorsConfiguration config = new CorsConfiguration(); // Response Headers裡面的Access-Control-Allow-Origin: http://localhost:8080 config.addAllowedOrigin("http://localhost:8080"); // 其實不建議使用*,允許所有跨域 config.addAllowedOrigin("*"); // 設置是否發送cookie信息,在前端也可以設置axios.defaults.withCredentials = true;表示發送Cookie, // 跨域請求要想帶上cookie,必須要請求屬性withCredentials=true,這是瀏覽器的同源策略導致的問題:不允許JS訪問跨域的Cookie /** * withCredentials前後端都要設置,後端是setAllowCredentials來設置 * 如果後端設置為false而前端設置為true,前端帶cookie就會報錯 * 如果後端為true,前端為false,那麼後端拿不到前端的cookie,cookie數組為null * 前後端都設置withCredentials為true,表示允許前端傳遞cookie到後端。 * 前後端都為false,前端不會傳遞cookie到服務端,後端也不接受cookie */ // Response Headers裡面的Access-Control-Allow-Credentials: true config.setAllowCredentials(true); // 設置允許請求的方式,比如get、post、put、delete,*表示全部 // Response Headers裡面的Access-Control-Allow-Methods屬性 config.addAllowedMethod("*"); // 設置允許的header // Response Headers裡面的Access-Control-Allow-Headers屬性,這裡是Access-Control-Allow-Headers: content-type, headeruserid, headerusertoken config.addAllowedHeader("*"); // Response Headers裡面的Access-Control-Max-Age:3600 // 表示下回同一個介面post請求,在3600s之內不會發送options請求,不管post請求成功還是失敗,3600s之內不會再發送options請求 // 如果不設置這個,那麼每次post請求之前必定有options請求 config.setMaxAge(3600L); // 2. 為url添加映射路徑 UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource(); // /**表示該config適用於所有路由 corsSource.registerCorsConfiguration("/**", config); // 3. 返回重新定義好的corsSource return new CorsFilter(corsSource); } }
這裡setMaxAge方法來設置預檢請求(OPTIONS請求)的有效期,當瀏覽器第一次發送非簡單的跨域POST請求時,它會先發送一個OPTIONS請求。如果伺服器允許跨域,並且設置了Access-Control-Max-Age頭(設置了setMaxAge方法),那麼瀏覽器會緩存這個預檢請求的結果。在Access-Control-Max-Age頭指定的時間範圍內,瀏覽器不會再次發送OPTIONS請求,而是直接發送實際的POST請求,不管POST請求成功還是失敗,在設置的時間範圍內,同一個介面請求是絕對不會再次發送OPTIONS請求的。
後端需要註意的是,我這裡設置允許請求的方法是config.addAllowedMethod("*"),*表示允許所有HTTP請求方法。如果未設置,則預設只允許“GET”和“HEAD”。你可以設置的HTTPMethod為GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
經過我的測試,OPTIONS無需手動設置,因為單純只設置OPTIONS也無效。如果你設置了允許POST,代碼為config.addAllowedMethod(HttpMethod.POST); 那麼其實已經預設允許了OPTIONS,如果你只允許了GET,嘗試發送POST請求就會報錯。
舉個例子,這裡只允許了GET請求,當我們嘗試發送一個POST非簡單請求,預檢請求返回403,伺服器拒絕了OPTIONS類型的請求,因為你只允許了GET,未配置允許OPTIONS請求,那麼瀏覽器將收到一個403 Forbidden響應,表示伺服器拒絕了該OPTIONS請求,POST請求的狀態顯示CORS error
在Spring Boot中,配置允許某個請求方法(如POST、PUT或DELETE)時,OPTIONS請求通常會被自動允許。這意味著在大多數情況下,後端開發人員不需要特意考慮OPTIONS請求。這種自動允許OPTIONS請求的行為取決於使用的跨域處理庫或配置,最好還是顯式地允許OPTIONS請求。