Golang 跨域

跨域指的是瀏覽器不能執行其餘網站或域名下的腳本。之因此造成跨域,是由於瀏覽器的同源策略形成的,是瀏覽器對javascript程序作的安全限制,如今全部支持JavaScript 的瀏覽器都會使用這個策略。javascript

在實際應用中會遇到須要跨域的場景,好比先後端分離,先後端不在同域(這裏的同域指的是同一協議,同一域名,同一端口),那麼,它們之間相互通訊如何解決呢?php

跨域解決有如下幾種方法:css

jsonp跨域

這裏jsonp跨域實際上是利用iframe、img、srcipt,link標籤的src或href屬性來實現的,這些標籤均可以發送一個get請求資源,src 和href 並無受同源策略的限制。html

這裏咱們拿懶人教程示例前端

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JSONP 實例</title>
    <script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>    
</head>
<body>
<div id="divCustomers"></div>
<script>
$.getJSON("https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=?", function(data) {
    
    var html = '<ul>';
    for(var i = 0; i < data.length; i++)
    {
        html += '<li>' + data[i] + '</li>';
    }
    html += '</ul>';
    
    $('#divCustomers').html(html); 
});
</script>
</body>
</html>

jsonp主要站在前端的角度去解決問題,這種方式有必定的侷限性,就是僅適用get請求。vue

nginx代理跨域

一、nginx配置解決iconfont跨域

衆所周知js、css、img等經常使用資源不受瀏覽器同源策略限制,但一些特殊資源如iconfont字體文件(eot|otf|ttf|woff|svg)除外,這裏經過修改nginx配置就能夠解決。java

location / {
  add_header Access-Control-Allow-Origin *;
}

二、nginx 反向代理

同源策略是瀏覽器的安全策略,不屬於http協議一部分,限制的是js腳本。而服務器端調用的http接口,不受同源策略限制,也不存在跨域問題。node

實現思路:nginx服務器做爲中間代理(或跳起色),實現從域名A訪問域名B,像訪問同域同樣。jquery

示例nginx

server {
           listen 80;
           server_name http://domain1; 

           location / {
               proxy_pass http://domain2:8081/;
               proxy_set_header Host $host;
               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
               proxy_set_header X-Forwarded-Proto $scheme;
               proxy_set_header X-Forwarded-Port $server_port;
            }
}

nodejs 代理

nodejs實現原理和nginx基本相似。

修改app.js

var express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();
app.set('port', '809');

app.all('*', function (req, res, next) {    // 解決跨域問題
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");    
    if (req.method == "OPTIONS") {
        res.send(200);       
    } else {
        next();
    }
});

var options = {
        target: 'http://localhost:8090',
        changeOrigin: true,
    };
var exampleProxy = proxy(options);
app.use('/', exampleProxy);
app.listen(app.get('port'), () => {
 console.log(`server running @${app.get('port')}`);
});

如是vue+nodejs環境

經過只修改vue.config.js,不用修改nodejs也能夠實現代理跨域。

devServer: {
        host: '0.0.0.0',
        port: 8080,
        disableHostCheck: true,
        proxy: {
            '/*': {
                target: 'https://www.runoob.com',
                secure: false,
                changeOrigin: true
            }
        }
    }

cors

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求

好比,站點 http://domain-a.com 的某 HTML 頁面經過 的 src 請求 http://domain-b.com/image.jpg。網絡上的許多頁面都會加載來自不一樣域的CSS樣式表,圖像和腳本等資源。

出於安全緣由,瀏覽器限制從腳本內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味着使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。

前面扯了不少方法,其實歸根結底是圍繞cors機制來實現(除了nginx反向代理)的,具體就是服務端發送 Access-Control-Allow-Origin 以及相關響應頭,來通知瀏覽器有權訪問資源。

前面講了 nodejs 或nginx服務器端經過設置Access-Control-Allow-Origin,能夠實現跨域,這裏講一下golang實現方式,固然php、java等也能夠實現、原理相同。

示例1

package main

import (
    "net/http"
)

func cors(f http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")  // 容許訪問全部域,能夠換成具體url,注意僅具體url才能帶cookie信息
        w.Header().Add("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token") //header的類型
        w.Header().Add("Access-Control-Allow-Credentials", "true") //設置爲true,容許ajax異步請求帶cookie信息
        w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") //容許請求方法
        w.Header().Set("content-type", "application/json;charset=UTF-8")             //返回數據格式是json
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        f(w, r)
    }
}
func index(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello Golang"))
}
func main() {
    http.HandleFunc("/", cors(index))
    http.ListenAndServe(":8000", nil)
}

示例2

gin框架跨域中間件

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)


func main() {
    r := gin.Default()
    r.Use(Cors())//默認跨域
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run(":8090") 
}
func Cors() gin.HandlerFunc {

    return func(c *gin.Context) {
        method := c.Request.Method
        origin := c.Request.Header.Get("Origin")
        if origin != "" {
            c.Header("Access-Control-Allow-Origin", "*")
            c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
            c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization")
            c.Header("Access-Control-Allow-Credentials", "true")
            c.Set("content-type", "application/json")
        }
        //放行全部OPTIONS方法
        if method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
        }
        c.Next()
    }
}

gin有個官方的跨域中間件

https://github.com/gin-contri...

注意 :

某些簡單請求不會觸發 CORS 預檢請求

Content-Type 的值僅限於下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded 默認

如今應用中愈來愈多前端和服務端都採用json通信,如vue等。

要求前端Content-Type設置爲 application/json,且是post請求,這屬於複雜請求,將觸發CORS 預檢請求。即瀏覽器會先發送一次options請求,贊成後才繼續發送post請求。

當發送這種請求時,在瀏覽器的network會發現兩條請求。同時在服務端接收前端參數時須要注意,之前經過get 、post方法會失效。

具體接收參數方法,php語言爲 file_get_contents('php://input') 。

golang語言

net/http
package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

func cors(f http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")  // 容許訪問全部域,能夠換成具體url,注意僅具體url才能帶cookie信息
        w.Header().Add("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token") //header的類型
        w.Header().Add("Access-Control-Allow-Credentials", "true") //設置爲true,容許ajax異步請求帶cookie信息
        w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") //容許請求方法
        w.Header().Set("content-type", "application/json;charset=UTF-8")             //返回數據格式是json
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        f(w, r)
    }
}
type User struct {
    Username string `json:"username"`
    Password  string  `json:"password"`
}

func index(w http.ResponseWriter, r *http.Request) {
    body, _ := ioutil.ReadAll(r.Body)
    fmt.Println(string(body))
    var user User
    if err := json.Unmarshal(body, &user); err == nil {
        fmt.Println(user)
    } else {
        fmt.Println(err)
    }
    w.Write([]byte("Hello Golang"))
}
func main() {
    http.HandleFunc("/", cors(index))
    http.ListenAndServe(":8000", nil)
}
gin 框架

對於gin框架咱們就須要bind來解決這個問題

示例

type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Password   string `form:"password" json:"password" binding:"required"`
}
func Login(c *gin.Context) {
    var u User
    err :=c.BindJSON(&u)
    fmt.Println(err)
    fmt.Println(u)
}

先建一個結構體user,再使用BindJSON綁定,將request中的Body中的數據按照JSON格式解析到User結構體中。

須要注意:

  • binding:"required" 字段對應的參數未必傳沒有會拋出錯誤,非banding的字段,對於客戶端沒有傳,User結構會用零值填充。對於User結構沒有的參數,會自動被忽略。
  • 結構體字段類型和所傳參數類型要一致。

Bind的實現都在gin/binding裏面. 這些內置的Bind都實現了Binding接口, 主要是Bind()函數.

  • context.BindJSON() 支持MIME爲application/json的解析
  • context.BindXML() 支持MIME爲application/xml的解析
  • context.BindYAML() 支持MIME爲application/x-yaml的解析
  • context.BindQuery() 只支持QueryString的解析, 和Query()函數同樣
  • context.BindUri() 只支持路由變量的解析
  • Context.Bind() 支持全部的類型的解析, 這個函數儘可能仍是少用(當QueryString, PostForm, 路由變量在一塊同時使用時會產生意想不到的效果), 目前測試Bind不支持路由變量的解析, Bind()函數的解析比較複雜, 這部分代碼後面再看
  • 一般在解決跨域問題時,經過在服務端設置head請求的方式比較便利。
  • 跨域須要帶cookie信息,則必須知足服務端 設置"Access-Control-Allow-Origin"爲固定url,且Access-Control-Allow-Credentials: true,前端js 也要設置withCredentials: true
  • 前端Content-Type設置爲 application/json時,服務端在接收參數數據方式不一樣。

參考:

https://developer.mozilla.org...

https://blog.csdn.net/qq_3796...

http://www.okyes.me/2016/05/0...

https://www.cnblogs.com/CyLee...

更多golang開發資料,請點擊下方目錄。

相關文章
相關標籤/搜索