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

當(dāng)前位置:首頁(yè) > 科技  > 軟件

老板與秘書(shū)的故事理解CORS(跨域),真的超級(jí)簡(jiǎn)單

來(lái)源: 責(zé)編: 時(shí)間:2024-02-01 12:50:12 220觀看
導(dǎo)讀背景一天下午,正認(rèn)真的上(摸)班(魚(yú))呢,一個(gè)前端開(kāi)發(fā)同事找到運(yùn)維團(tuán)隊(duì)“后端服務(wù)是不是有什么異常啊,為什么我的訪問(wèn)不通呢?”“接口地址拿來(lái)~”運(yùn)維工程師使用本地的postman進(jìn)行調(diào)用。結(jié)果是正常返回?!拔疫@調(diào)用沒(méi)問(wèn)題啊,你寫(xiě)

背景

一天下午,正認(rèn)真的上(摸)班(魚(yú))呢,一個(gè)前端開(kāi)發(fā)同事找到運(yùn)維團(tuán)隊(duì)“后端服務(wù)是不是有什么異常啊,為什么我的訪問(wèn)不通呢?”“接口地址拿來(lái)~”運(yùn)維工程師使用本地的postman進(jìn)行調(diào)用。結(jié)果是正常返回?!拔疫@調(diào)用沒(méi)問(wèn)題啊,你寫(xiě)的code的問(wèn)題吧......”一場(chǎng)大戰(zhàn)一觸即發(fā).......sQL28資訊網(wǎng)——每日最新資訊28at.com

sQL28資訊網(wǎng)——每日最新資訊28at.com

這天可以記為兩位工程師的歷史性時(shí)刻——發(fā)現(xiàn)了CORS!sQL28資訊網(wǎng)——每日最新資訊28at.com

那么什么是CORS呢?

跨源資源共享(Cross-Origin Resource Sharing,或通俗地譯為跨域資源共享)是一種基于 HTTP 頭的機(jī)制,該機(jī)制通過(guò)允許服務(wù)器標(biāo)示除了它自己以外的其他源(域、協(xié)議或端口),使得瀏覽器允許這些源訪問(wèn)加載自己的資源??缭促Y源共享還通過(guò)一種機(jī)制來(lái)檢查服務(wù)器是否會(huì)允許要發(fā)送的真實(shí)請(qǐng)求,該機(jī)制通過(guò)瀏覽器發(fā)起一個(gè)到服務(wù)器托管的跨源資源的“預(yù)檢”請(qǐng)求。在預(yù)檢中,瀏覽器發(fā)送的頭中標(biāo)示有 HTTP 方法和真實(shí)請(qǐng)求中會(huì)用到的頭。sQL28資訊網(wǎng)——每日最新資訊28at.com

看的有點(diǎn)懵,現(xiàn)在舉個(gè)現(xiàn)實(shí)中的例子:有一位公司的老板,他有一個(gè)秘書(shū),秘書(shū)負(fù)責(zé)在辦公室接通各個(gè)客戶的電話后,會(huì)詢問(wèn)是誰(shuí)從什么地方打來(lái)的電話,然后通知老板是否愿意與他們通話。老板比較忙的時(shí)候會(huì)告訴秘書(shū):“我今天只接受A公司XX人的電話同步的信息”。那么秘書(shū)就會(huì)按照老板的要求進(jìn)行同步。但是也有特殊情況:比如B公司老板直接知道老板的電話。也會(huì)直接聯(lián)系老板sQL28資訊網(wǎng)——每日最新資訊28at.com

從現(xiàn)實(shí)生活到軟件工程訪問(wèn),我們做一個(gè)對(duì)應(yīng):sQL28資訊網(wǎng)——每日最新資訊28at.com

  • 給辦公室打電話的人——前端應(yīng)用程序
  • 秘書(shū)-瀏覽器
  • 老板-后端應(yīng)用程序

訪問(wèn)的逐步順序如下:sQL28資訊網(wǎng)——每日最新資訊28at.com

  • 一旦前端應(yīng)用程序嘗試向后端 API 發(fā)送請(qǐng)求,瀏覽器就會(huì)向后端 API 發(fā)出所謂的預(yù)請(qǐng)求,并詢問(wèn)允許的選項(xiàng):誰(shuí)可以調(diào)用 API 以及可以發(fā)出什么類(lèi)型的請(qǐng)求
  • API 發(fā)送帶有此類(lèi)選項(xiàng)的響應(yīng),并且(可選)包括瀏覽器應(yīng)緩存這些依賴設(shè)置
  • 如果前端應(yīng)用程序及其嘗試發(fā)出的請(qǐng)求位于允許列表內(nèi),則瀏覽器會(huì)允許其通過(guò)
  • 否則,請(qǐng)求將被拒絕,并出現(xiàn)我們?cè)诒疚拈_(kāi)頭看到的錯(cuò)誤

我們啟動(dòng)一個(gè)后端和前端來(lái)模擬問(wèn)題:sQL28資訊網(wǎng)——每日最新資訊28at.com

后端的Go代碼

package main import (  "encoding/json"  "errors"  "fmt"  "github.com/go-chi/chi/v5"  "net/http" ) var books = [] string { "指環(huán)王" , "霍比特人" , "精靈寶鉆" } type Book struct {標(biāo)題字符串 `json:"title"` } func  main () { err := runServer()  if err != nil { iferrors.Is(   err , http.ErrServerClosed ) {    fmt.Println( "服務(wù)器關(guān)閉" )   } else {    fmt.Println( "服務(wù)器失敗" , 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) }

運(yùn)行這段代碼,服務(wù)器將運(yùn)行為http://localhost:8888sQL28資訊網(wǎng)——每日最新資訊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>

一個(gè)Go 服務(wù)(與index.html放在一個(gè)文件夾下):sQL28資訊網(wǎng)——每日最新資訊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")}

運(yùn)行這段代碼,前端html將運(yùn)行為http://localhost:3333sQL28資訊網(wǎng)——每日最新資訊28at.com

使用瀏覽器訪問(wèn),得到如下頁(yè)面,打開(kāi)F12調(diào)試,在文本框中輸入書(shū)名,點(diǎn)擊Add:sQL28資訊網(wǎng)——每日最新資訊28at.com

sQL28資訊網(wǎng)——每日最新資訊28at.com

得到了與文章開(kāi)始時(shí)類(lèi)似的報(bào)錯(cuò)。sQL28資訊網(wǎng)——每日最新資訊28at.com

您可能已經(jīng)發(fā)現(xiàn),我們的后端代碼根本沒(méi)有提及 CORS。確實(shí)如此,到目前為止我們還沒(méi)有實(shí)現(xiàn)任何 CORS 配置。但這對(duì)于瀏覽器來(lái)說(shuō)并不重要:它無(wú)論如何都會(huì)嘗試發(fā)出預(yù)檢請(qǐng)求。(就像秘書(shū)一定要征求老板的意見(jiàn),不會(huì)擅自決定)sQL28資訊網(wǎng)——每日最新資訊28at.com

如果我們單擊405這個(gè)報(bào)錯(cuò),會(huì)展開(kāi)一些詳細(xì)信息,我們可以看到瀏覽器嘗試向與添加圖書(shū)端點(diǎn)相同的路徑發(fā)出 OPTIONS 請(qǐng)求,并收到響應(yīng)405 Method Not Allowed,這是有道理的,因?yàn)槲覀冞€沒(méi)有定義我們后端的 OPTIONS 端點(diǎn)。sQL28資訊網(wǎng)——每日最新資訊28at.com

sQL28資訊網(wǎng)——每日最新資訊28at.com

問(wèn)題解決

前端應(yīng)用程序保持不變,但對(duì)于后端,我們需要進(jìn)行一些更改:sQL28資訊網(wǎng)——每日最新資訊28at.com

引入一個(gè)新函數(shù)來(lái)啟用 CORS:sQL28資訊網(wǎng)——每日最新資訊28at.com

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

在現(xiàn)有端點(diǎn)旁邊引入一個(gè) OPTIONS 端點(diǎn)以及一個(gè)處理它的函數(shù):sQL28資訊網(wǎng)——每日最新資訊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對(duì)其他端點(diǎn)現(xiàn)有函數(shù)的調(diào)用,例如:sQL28資訊網(wǎng)——每日最新資訊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)}

最后的后端代碼如下:sQL28資訊網(wǎng)——每日最新資訊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))}

重新啟動(dòng)前端和后端,重新嘗試訪問(wèn)會(huì)發(fā)現(xiàn)問(wèn)題解決了~sQL28資訊網(wǎng)——每日最新資訊28at.com

sQL28資訊網(wǎng)——每日最新資訊28at.com

其中重要的部分是Response headerssQL28資訊網(wǎng)——每日最新資訊28at.com

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

sQL28資訊網(wǎng)——每日最新資訊28at.com

此時(shí)再去訪問(wèn)則發(fā)現(xiàn):sQL28資訊網(wǎng)——每日最新資訊28at.com

sQL28資訊網(wǎng)——每日最新資訊28at.com

此時(shí)就是后端的配置導(dǎo)致的。當(dāng)人你也可以更改其他的配置做一些嘗試。sQL28資訊網(wǎng)——每日最新資訊28at.com

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

希望能對(duì)您有幫助!sQL28資訊網(wǎng)——每日最新資訊28at.com

參考:https://itnext.io/understanding-cors-4157bf640e11sQL28資訊網(wǎng)——每日最新資訊28at.com

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

聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: Go 語(yǔ)言實(shí)戰(zhàn):構(gòu)建強(qiáng)大的延遲任務(wù)隊(duì)列

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

標(biāo)簽:
  • 熱門(mén)焦點(diǎn)
  • 鴻蒙OS 4.0公測(cè)機(jī)型公布:甚至連nova6都支持

    華為全新的HarmonyOS 4.0操作系統(tǒng)將于今天下午正式登場(chǎng),官方在發(fā)布會(huì)之前也已經(jīng)正式給出了可升級(jí)的機(jī)型產(chǎn)品,這意味著這些機(jī)型會(huì)率先支持升級(jí)享用。這次的HarmonyOS 4.0支持
  • 對(duì)標(biāo)蘋(píng)果的靈動(dòng)島 華為帶來(lái)實(shí)況窗功能

    繼蘋(píng)果的靈動(dòng)島之后,華為也在今天正式推出了“實(shí)況窗”功能。據(jù)今天鴻蒙OS 4.0的現(xiàn)場(chǎng)演示顯示,華為的實(shí)況窗可以更高效的展現(xiàn)出實(shí)時(shí)通知,比如鎖屏上就能看到外賣(mài)、打車(chē)、銀行
  • Rust中的高吞吐量流處理

    作者 | Noz編譯 | 王瑞平本篇文章主要介紹了Rust中流處理的概念、方法和優(yōu)化。作者不僅介紹了流處理的基本概念以及Rust中常用的流處理庫(kù),還使用這些庫(kù)實(shí)現(xiàn)了一個(gè)流處理程序
  • JVM優(yōu)化:實(shí)戰(zhàn)OutOfMemoryError異常

    一、Java堆溢出堆內(nèi)存中主要存放對(duì)象、數(shù)組等,只要不斷地創(chuàng)建這些對(duì)象,并且保證 GC Roots 到對(duì)象之間有可達(dá)路徑來(lái)避免垃 圾收集回收機(jī)制清除這些對(duì)象,當(dāng)這些對(duì)象所占空間超過(guò)
  • 梁柱接棒兩年,騰訊音樂(lè)闖出新路子

    文丨田靜 出品丨牛刀財(cái)經(jīng)(niudaocaijing)7月5日,企鵝FM發(fā)布官方公告稱(chēng)由于業(yè)務(wù)調(diào)整,將于9月6日正式停止運(yùn)營(yíng),這意味著騰訊音樂(lè)長(zhǎng)音頻業(yè)務(wù)走向消亡。騰訊在長(zhǎng)音頻領(lǐng)域還在摸索。為
  • ESG的面子與里子

    來(lái)源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之三伏大幕拉起,各地高溫預(yù)警不絕,但處于厄爾尼諾大&ldquo;烤&rdquo;之下的除了眾生,還有各大企業(yè)發(fā)布的ESG報(bào)告。ESG是&ldquo;環(huán)境保
  • 阿里瓴羊One推出背后,零售企業(yè)迎數(shù)字化新解

    作者:劉曠近年來(lái)隨著數(shù)字經(jīng)濟(jì)的高速發(fā)展,各式各樣的SaaS應(yīng)用服務(wù)更是層出不窮,但本質(zhì)上SaaS大多局限于單一業(yè)務(wù)流層面,對(duì)用戶核心關(guān)切的增長(zhǎng)問(wèn)題等則沒(méi)有提供更好的解法。在Saa
  • 自研Exynos回歸!三星Galaxy S24系列將提供Exynos和驍龍雙版本

    年初,全新的三星Galaxy S23系列發(fā)布,包含Galaxy S23、Galaxy S23+和Galaxy S23 Ultra三個(gè)版本,全系搭載超頻版驍龍8 Gen 2,雖同樣采用臺(tái)積電4nm工藝制
  • OPPO Reno10 Pro英雄聯(lián)盟定制禮盒公布:薩勒芬妮同款配色夢(mèng)幻十足

    5月24日,OPPO推出了全新的OPPO Reno 10系列,包含OPPO Reno10、OPPO Reno10 Pro和OPPO Reno10 Pro+三款新機(jī),全系標(biāo)配了超光影長(zhǎng)焦鏡頭,是迄今為止拍照
Top 主站蜘蛛池模板: 庄浪县| 静安区| 普宁市| 苏尼特右旗| 进贤县| 克山县| 宁强县| 交口县| 青海省| 垦利县| 山东| 淮滨县| 南乐县| 郑州市| 顺义区| 长岛县| 平南县| 贵南县| 怀宁县| 临澧县| 三台县| 都江堰市| 芜湖市| 无为县| 长阳| 东城区| 镇坪县| 华宁县| 邵东县| 黔江区| 东辽县| 长海县| 洛川县| 赤水市| 玉林市| 临夏市| 绍兴县| 沙田区| 烟台市| 攀枝花市| 马边|