日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

老板與秘書的故事理解CORS(跨域),真的超級簡單

來源: 責編: 時間:2024-02-01 12:50:12 196觀看
導讀背景一天下午,正認真的上(摸)班(魚)呢,一個前端開發同事找到運維團隊“后端服務是不是有什么異常啊,為什么我的訪問不通呢?”“接口地址拿來~”運維工程師使用本地的postman進行調用。結果是正常返回。“我這調用沒問題啊,你寫

背景

一天下午,正認真的上(摸)班(魚)呢,一個前端開發同事找到運維團隊“后端服務是不是有什么異常啊,為什么我的訪問不通呢?”“接口地址拿來~”運維工程師使用本地的postman進行調用。結果是正常返回。“我這調用沒問題啊,你寫的code的問題吧......”一場大戰一觸即發.......KGC28資訊網——每日最新資訊28at.com

KGC28資訊網——每日最新資訊28at.com

這天可以記為兩位工程師的歷史性時刻——發現了CORS!KGC28資訊網——每日最新資訊28at.com

那么什么是CORS呢?

跨源資源共享(Cross-Origin Resource Sharing,或通俗地譯為跨域資源共享)是一種基于 HTTP 頭的機制,該機制通過允許服務器標示除了它自己以外的其他源(域、協議或端口),使得瀏覽器允許這些源訪問加載自己的資源。跨源資源共享還通過一種機制來檢查服務器是否會允許要發送的真實請求,該機制通過瀏覽器發起一個到服務器托管的跨源資源的“預檢”請求。在預檢中,瀏覽器發送的頭中標示有 HTTP 方法和真實請求中會用到的頭。KGC28資訊網——每日最新資訊28at.com

看的有點懵,現在舉個現實中的例子:有一位公司的老板,他有一個秘書,秘書負責在辦公室接通各個客戶的電話后,會詢問是誰從什么地方打來的電話,然后通知老板是否愿意與他們通話。老板比較忙的時候會告訴秘書:“我今天只接受A公司XX人的電話同步的信息”。那么秘書就會按照老板的要求進行同步。但是也有特殊情況:比如B公司老板直接知道老板的電話。也會直接聯系老板KGC28資訊網——每日最新資訊28at.com

從現實生活到軟件工程訪問,我們做一個對應:KGC28資訊網——每日最新資訊28at.com

  • 給辦公室打電話的人——前端應用程序
  • 秘書-瀏覽器
  • 老板-后端應用程序

訪問的逐步順序如下:KGC28資訊網——每日最新資訊28at.com

  • 一旦前端應用程序嘗試向后端 API 發送請求,瀏覽器就會向后端 API 發出所謂的預請求,并詢問允許的選項:誰可以調用 API 以及可以發出什么類型的請求
  • API 發送帶有此類選項的響應,并且(可選)包括瀏覽器應緩存這些依賴設置
  • 如果前端應用程序及其嘗試發出的請求位于允許列表內,則瀏覽器會允許其通過
  • 否則,請求將被拒絕,并出現我們在本文開頭看到的錯誤

我們啟動一個后端和前端來模擬問題:KGC28資訊網——每日最新資訊28at.com

后端的Go代碼

package main import (  "encoding/json"  "errors"  "fmt"  "github.com/go-chi/chi/v5"  "net/http" ) var books = [] string { "指環王" , "霍比特人" , "精靈寶鉆" } type Book struct {標題字符串 `json:"title"` } func  main () { err := runServer()  if err != nil { iferrors.Is(   err , http.ErrServerClosed ) {    fmt.Println( "服務器關閉" )   } else {    fmt.Println( "服務器失敗" , err)   } } } func  runServer ()  error { httpRouter := chi.NewRouter() httpRouter.Route( "/api/ v1" , func (r chi.Router) {   r.Get( "/books" , getAllBooks)   r.Post( "/books" , addBook)   r.Delete( "/books" , deleteAllBooks) }) server := &http .Server{Addr: "localhost:8888" , Handler: httpRouter}  return server.ListenAndServe() } func  getAllBooks (w http.ResponseWriter, req *http.Request) { respBody, err := json.Marshal(books)  if err != nil {   w.WriteHeader(http.StatusInternalServerError)   return } w.Header().Set( "Content-Type" , "application/json" ) w.WriteHeader(http.StatusOK) w.Write(respBody) } func  addBook (w http.ResponseWriter, req *http.Request) {  var book Book err := json.NewDecoder(req.Body).Decode(&book)  if err != nil {   w.WriteHeader(http.StatusBadRequest)   return } books = append (books, book.Title) w.WriteHeader(http.StatusCreated) } func  deleteAllBooks (w http.ResponseWriter, req *http.Request) { books = [] string {} w.WriteHeader(http.StatusNoContent) }

運行這段代碼,服務器將運行為http://localhost:8888KGC28資訊網——每日最新資訊28at.com

前端

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Books</title>    <link  rel="stylesheet"          integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"            integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"            crossorigin="anonymous"></script></head><body><div class="container p-3">    <button type="button" class="btn btn-primary" id="getBooks">Get books</button>    <button type="button" class="btn btn-danger" id="deleteAllBooks">Delete all books</button>    <br>    <br>    <form>        <div class="mb-3">            <label for="inputBookTitle" class="form-label">Book title</label>            <input type="text" class="form-control" id="inputBookTitle" aria-describedby="emailHelp">        </div>        <button type="submit" class="btn btn-primary">Add</button>    </form></div><script>  function getBooks () {    fetch('http://localhost:8888/api/v1/books')      .then(response => response.json())      .then(data => {        const booksList = document.querySelector('.books-list')        if (booksList) {          booksList.remove()        }        const ul = document.createElement('ul')        ul.classList.add('books-list')        data.forEach(book => {          const li = document.createElement('li')          li.innerText = book          ul.appendChild(li)        })        document.body.appendChild(ul)      })  }  function deleteAllBooks () {    fetch('http://localhost:8888/api/v1/books', {      method: 'DELETE'    })      .then(response => {        if (response.status === 204) {          getBooks()        } else {          const div = document.createElement('div')          div.innerText = 'Something went wrong'          document.body.appendChild(div)        }      })  }  const getBooksButton = document.getElementById('getBooks')  const deleteAllBooksButton = document.getElementById('deleteAllBooks')  const input = document.querySelector('input')  const form = document.querySelector('form')  getBooksButton.addEventListener('click', () => getBooks())  deleteAllBooksButton.addEventListener('click', () => deleteAllBooks())  form.addEventListener('submit', (event) => {    event.preventDefault()    const title = input.value    fetch('http://localhost:8888/api/v1/books', {      method: 'POST',      headers: {        'Content-Type': 'application/json'      },      body: JSON.stringify({ title })    })      .then(response => {        if (response.status === 201) {          input.value = ''          getBooks()        } else {          const div = document.createElement('div')          div.innerText = 'Something wend wrong'          document.body.appendChild(div)        }      })  })</script></body></html>

一個Go 服務(與index.html放在一個文件夾下):KGC28資訊網——每日最新資訊28at.com

package mainimport ( "errors" "fmt" "github.com/go-chi/chi/v5" "net/http")func main() { err := runServer() if err != nil {  if errors.Is(err, http.ErrServerClosed) {   fmt.Println("client server shutdown")  } else {   fmt.Println("client server failed", err)  } }}func runServer() error { httpRouter := chi.NewRouter() httpRouter.Get("/", serveIndex) server := &http.Server{Addr: "localhost:3333", Handler: httpRouter} return server.ListenAndServe()}func serveIndex(w http.ResponseWriter, req *http.Request) { http.ServeFile(w, req, "./index.html")}

運行這段代碼,前端html將運行為http://localhost:3333KGC28資訊網——每日最新資訊28at.com

使用瀏覽器訪問,得到如下頁面,打開F12調試,在文本框中輸入書名,點擊Add:KGC28資訊網——每日最新資訊28at.com

KGC28資訊網——每日最新資訊28at.com

得到了與文章開始時類似的報錯。KGC28資訊網——每日最新資訊28at.com

您可能已經發現,我們的后端代碼根本沒有提及 CORS。確實如此,到目前為止我們還沒有實現任何 CORS 配置。但這對于瀏覽器來說并不重要:它無論如何都會嘗試發出預檢請求。(就像秘書一定要征求老板的意見,不會擅自決定)KGC28資訊網——每日最新資訊28at.com

如果我們單擊405這個報錯,會展開一些詳細信息,我們可以看到瀏覽器嘗試向與添加圖書端點相同的路徑發出 OPTIONS 請求,并收到響應405 Method Not Allowed,這是有道理的,因為我們還沒有定義我們后端的 OPTIONS 端點。KGC28資訊網——每日最新資訊28at.com

KGC28資訊網——每日最新資訊28at.com

問題解決

前端應用程序保持不變,但對于后端,我們需要進行一些更改:KGC28資訊網——每日最新資訊28at.com

引入一個新函數來啟用 CORS:KGC28資訊網——每日最新資訊28at.com

func  enableCors (w http.ResponseWriter) {  // 指定允許哪些域訪問此 API w.Header().Set( "Access-Control-Allow-Origin" , "http://localhost:3333" )    //指定允許哪些方法訪問此 API w.Header().Set( "Access-Control-Allow-Methods" , "GET, POST, DELETE" )    // 指定允許哪些標頭訪問此 API w.Header( ).Set( "Access-Control-Allow-Headers" , "Accept, Content-Type" )    // 指定瀏覽器可以緩存預檢請求結果的時間(以秒為單位) w.Header().Set( “訪問控制最大時間”,strconv.Itoa( 60 * 60 * 2))}

在現有端點旁邊引入一個 OPTIONS 端點以及一個處理它的函數:KGC28資訊網——每日最新資訊28at.com

... httpRouter.Route( "/api/v1" , func (r chi.Router) {   r.Options( "/books" , corsOptions)   r.Get( "/books" , getAllBooks)   r.Post( "/ books" , addBook)   r.Delete( "/books" , deleteAllBooks) }) ... func  corsOptions (w http.ResponseWriter, req *http.Request) { enableCors(w) w.WriteHeader(http.StatusOK) }

添加enableCors對其他端點現有函數的調用,例如:KGC28資訊網——每日最新資訊28at.com

func  getAllBooks (w http.ResponseWriter, req *http.Request) {respBody, err := json.Marshal(books)  if err != nil {  w.WriteHeader(http.StatusInternalServerError)   return }enableCors(w)w.Header( ).Set( "Content-Type" , "application/json" )w.WriteHeader(http.StatusOK)w.Write(respBody)}

最后的后端代碼如下:KGC28資訊網——每日最新資訊28at.com

package mainimport ( "encoding/json" "errors" "fmt" "github.com/go-chi/chi/v5" "net/http" "strconv")var books = []string{"The Lord of the Rings", "The Hobbit", "The Silmarillion"}type Book struct { Title string `json:"title"`}func main() { err := runServer() if err != nil {  if errors.Is(err, http.ErrServerClosed) {   fmt.Println("server shutdown")  } else {   fmt.Println("server failed", err)  } }}func runServer() error { httpRouter := chi.NewRouter() httpRouter.Route("/api/v1", func(r chi.Router) {  r.Options("/books", corsOptions)  r.Get("/books", getAllBooks)  r.Post("/books", addBook)  r.Delete("/books", deleteAllBooks) }) server := &http.Server{Addr: "localhost:8888", Handler: httpRouter} return server.ListenAndServe()}func corsOptions(w http.ResponseWriter, req *http.Request) { enableCors(w) w.WriteHeader(http.StatusOK)}func getAllBooks(w http.ResponseWriter, req *http.Request) { respBody, err := json.Marshal(books) if err != nil {  w.WriteHeader(http.StatusInternalServerError)  return } enableCors(w) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(respBody)}func addBook(w http.ResponseWriter, req *http.Request) { var book Book err := json.NewDecoder(req.Body).Decode(&book) if err != nil {  w.WriteHeader(http.StatusBadRequest)  return } books = append(books, book.Title) enableCors(w) w.WriteHeader(http.StatusCreated)}func deleteAllBooks(w http.ResponseWriter, req *http.Request) { books = []string{} enableCors(w) w.WriteHeader(http.StatusNoContent)}func enableCors(w http.ResponseWriter) { // specifies which domains are allowed to access this API w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3333") // specifies which methods are allowed to access this API (GET is allowed by default) w.Header().Set("Access-Control-Allow-Methods", "POST, DELETE") // specifies which headers are allowed to access this API w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // specifies for how long the browser can cache the results of a preflight request (in seconds) w.Header().Set("Access-Control-Max-Age", strconv.Itoa(60*60*2))}

重新啟動前端和后端,重新嘗試訪問會發現問題解決了~KGC28資訊網——每日最新資訊28at.com

KGC28資訊網——每日最新資訊28at.com

其中重要的部分是Response headersKGC28資訊網——每日最新資訊28at.com

如果嘗試改變后端配置。允許訪問的地址改為http://localhost:33333:KGC28資訊網——每日最新資訊28at.com

KGC28資訊網——每日最新資訊28at.com

此時再去訪問則發現:KGC28資訊網——每日最新資訊28at.com

KGC28資訊網——每日最新資訊28at.com

此時就是后端的配置導致的。當人你也可以更改其他的配置做一些嘗試。KGC28資訊網——每日最新資訊28at.com

我們到這就理解了CORS是一種允許當前域(domain)的資源(比如http://localhost:8888)被其他域(http://localhost:3333)的腳本請求訪問的機制,通常由于同域安全策略(the same-origin security policy)瀏覽器會禁止這種跨域請求。當瀏覽器發出PUT請求,OPTION(預檢)請求返回Access-Control-Allow-Origin:http://localhost:3333,Access-Control-Allow-Methods:’PUT’,服務器同意指定域的PUT請求,瀏覽器收到并繼續發出真正的PUT請求,服務器響應并再次返回Access-Control-Allow-Origin:http://localhost:3333,允許瀏覽器的腳本執行服務器返回的數據。KGC28資訊網——每日最新資訊28at.com

希望能對您有幫助!KGC28資訊網——每日最新資訊28at.com

參考:https://itnext.io/understanding-cors-4157bf640e11KGC28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-70437-0.html老板與秘書的故事理解CORS(跨域),真的超級簡單

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Go 語言實戰:構建強大的延遲任務隊列

下一篇: 預定義宏的神秘面紗:編程世界的隱藏利器

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 丽江市| 柏乡县| 易门县| 将乐县| 连平县| 德昌县| 偃师市| 合川市| 木兰县| 陈巴尔虎旗| 平阴县| 时尚| 容城县| 孝感市| 嫩江县| 渝中区| 延津县| 永登县| 徐汇区| 兰考县| 郯城县| 新乐市| 东至县| 札达县| 彭阳县| 武清区| 瑞金市| 桑植县| 雅安市| 桑日县| 天柱县| 历史| 宁南县| 昭通市| 潜山县| 莆田市| 南华县| 化州市| 鹤岗市| 玉溪市| 介休市|