自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 網絡
跨源資源共享是一種基于 HTTP 頭的機制,該機制通過允許服務器標示除了它自己以外的其他源(域、協(xié)議或端口),使得瀏覽器允許這些源訪問加載自己的資源。

背景

一天下午,正認真的上(摸)班(魚)呢,一個前端開發(fā)同事找到運維團隊“后端服務是不是有什么異常啊,為什么我的訪問不通呢?”“接口地址拿來~”運維工程師使用本地的postman進行調用。結果是正常返回?!拔疫@調用沒問題啊,你寫的code的問題吧......”一場大戰(zhàn)一觸即發(fā).......

這天可以記為兩位工程師的歷史性時刻——發(fā)現(xiàn)了CORS!

那么什么是CORS呢?

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

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

從現(xiàn)實生活到軟件工程訪問,我們做一個對應:

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

訪問的逐步順序如下:

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

我們啟動一個后端和前端來模擬問題:

后端的Go代碼

package main 

import ( 
 "encoding/json" 
 "errors" 
 "fmt" 
 "github.com/go-chi/chi/v5" 
 "net/http"
 ) 

var books = [] string { "指環(huán)王" , "霍比特人" , "精靈寶鉆" } 

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:8888

前端

<!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放在一個文件夾下):

package main

import (
 "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:3333

使用瀏覽器訪問,得到如下頁面,打開F12調試,在文本框中輸入書名,點擊Add:

得到了與文章開始時類似的報錯。

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

如果我們單擊405這個報錯,會展開一些詳細信息,我們可以看到瀏覽器嘗試向與添加圖書端點相同的路徑發(fā)出 OPTIONS 請求,并收到響應405 Method Not Allowed,這是有道理的,因為我們還沒有定義我們后端的 OPTIONS 端點。

問題解決

前端應用程序保持不變,但對于后端,我們需要進行一些更改:

引入一個新函數(shù)來啟用 CORS:

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))
}

在現(xiàn)有端點旁邊引入一個 OPTIONS 端點以及一個處理它的函數(shù):

... 
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對其他端點現(xiàn)有函數(shù)的調用,例如:

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)
}

最后的后端代碼如下:

package main

import (
 "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))
}

重新啟動前端和后端,重新嘗試訪問會發(fā)現(xiàn)問題解決了~

其中重要的部分是Response headers

如果嘗試改變后端配置。允許訪問的地址改為http://localhost:33333:

此時再去訪問則發(fā)現(xiàn):

此時就是后端的配置導致的。當人你也可以更改其他的配置做一些嘗試。

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

希望能對您有幫助!

參考:https://itnext.io/understanding-cors-4157bf640e11

責任編輯:趙寧寧 來源: 云原生運維圈
相關推薦

2019-04-10 10:32:16

CORSNginx反向代理

2021-06-10 18:11:02

Cors跨域Web開發(fā)Cors

2020-08-31 19:20:33

瀏覽器CORS跨域

2023-12-20 14:42:59

2021-06-15 07:32:59

Cookie和Sess實現(xiàn)跨域

2014-08-19 10:36:02

AngularCORS

2021-06-25 09:04:39

Cors跨域JSONP vs CO

2022-04-29 09:11:14

CORS瀏覽器

2019-03-13 14:15:25

CORS跨域資源前端

2013-11-27 10:23:23

2021-06-17 07:15:36

Cors跨域多域名

2021-04-27 15:20:41

人工智能機器學習技術

2023-11-20 08:02:49

2018-12-12 15:50:13

2020-08-13 07:04:45

跨域CORS瀏覽器

2022-03-01 09:31:06

JWTSession跨域

2020-06-02 10:43:54

Kubernetes容器服務

2024-02-27 08:14:51

Nginx跨域服務

2016-11-04 20:02:37

Apache

2011-02-22 17:14:20

點贊
收藏

51CTO技術棧公眾號