天天看點

從前後端的角度分析options預檢請求

作者:華為雲開發者聯盟

本文分享自華為雲社群《從前後端的角度分析options預檢請求——打破前後端聯調的了解障礙-雲社群-華為雲》,作者: 磚業洋__ 。

options預檢請求是幹嘛的?options請求一定會在post請求之前發送嗎?前端或者後端開發需要手動幹預這個預檢請求嗎?不用文檔定義堆砌名詞,從前後端角度單獨分析,大白話帶你了解!

從前端的角度看options——post請求之前一定會有options請求?信口雌黃!

你是否經常看到這種跨域請求錯誤?

從前後端的角度分析options預檢請求

這是因為伺服器不允許跨域請求,這裡會深入講一講OPTIONS請求。

隻有在滿足一定條件的跨域請求中,浏覽器才會發送OPTIONS請求(預檢請求)。這些請求被稱為“非簡單請求”。反之,如果一個跨域請求被認為是“簡單請求”,那麼浏覽器将不會發送OPTIONS請求。

簡單請求需要滿足以下條件:

  1. 隻使用以下HTTP方法之一:GET、HEAD或POST。
  2. 隻使用以下HTTP頭部:Accept、Accept-Language、Content-Language、Content-Type。
  3. 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請求。請求滿足以下簡單請求條件:

  1. 使用POST方法。
  2. 使用的HTTP頭部僅包括Content-Type。
  3. 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請求。請求不滿足簡單請求條件,因為:

  1. 使用了非允許範圍内的Content-Type值("application/json" 不屬于 application/x-www-form-urlencoded、multipart/form-data或text/plain)。
  2. 使用了一個自定義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預檢請求

2.從後端的角度看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

從前後端的角度分析options預檢請求
從前後端的角度分析options預檢請求

在Spring Boot中,配置允許某個請求方法(如POST、PUT或DELETE)時,OPTIONS請求通常會被自動允許。這意味着在大多數情況下,後端開發人員不需要特意考慮OPTIONS請求。這種自動允許OPTIONS請求的行為取決于使用的跨域處理庫或配置,最好還是顯式地允許OPTIONS請求。

關注#華為雲開發者聯盟# 點選下方,第一時間了解華為雲新鮮技術~

華為雲部落格_大資料部落格_AI部落格_雲計算部落格_開發者中心-華為雲

繼續閱讀