前后端交互

2023-01-30 00:00:00

技术

一、后端写好需要调用的函数
二、前端使用axios进行调用
三、跨域问题与同源策略

一、后端写好需要调用的函数

package main

import (
    "fmt"
    "net/http"
)
//定义函数NewConnection
//http.Response:后端返回给前端的响应
// *http.Request:前端传给后端的请求
func NewConnection(response http.ResponseWriter,request *http.Request){
    str:="asdfghjkl"
    //写入需要返回响应的内容,[]byte类型
    response.Write([]byte(str))
}
func main() {
    //注册路由,即定义访问的路径及其调用的函数
    http.HandleFunc("/connect",NewConnection)
    //启动服务并指定监听端口
    err:=http.ListenAndServe(":9090",nil)
    if err!=nil{
        fmt.Println("启动服务失败:",err)
        return
    }
}
//执行 go run main.go 启动服务
//在浏览器访问localhost:9090/connect
//可以看到 asdfghjkl字符串

二、前端使用axios进行调用

下载axios

npm i axios

import导入

import axios from 'axios'

使用axios

function newConnection(){
    msg.body="2",
    reply.body="2"
    alert("1111111")
    axios.get("http://127.0.0.1:9090/connect").then(res=>{
        console.log("222222")
        alert("000000")
        reply.body=string(res)
    })
}

点击连接按钮,newConnection没有弹窗,F12查看控制台发现报错

 GET http://127.0.0.1:9090/connect net::ERR_FAILED 200

但是后端控制台成功打印

请求:&{GET /connect HTTP/1.1 1 1 map[Accept:[application/json, text/plain, */*] Accept-Encoding:[gzip, 
deflate, br] Accept-Language:[zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6] Cache-Control:[no-cache] Connection:[keep-alive] Origin:[http://127.0.0.1:5173] Pragma:[no-cache] Referer:[http://127.0.0.1:5173/] Sec-Ch-Ua:["Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"] Sec-Ch-Ua-Mobile:[?0] Sec-Ch-Ua-Platform:["Windows"] Sec-Fetch-Dest:[empty] Sec-Fetch-Mode:[cors] Sec-Fetch-Site:[same-site] User-Agent:[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76]] {} <nil> 0 [] false 127.0.0.1:9090 map[] map[] <nil> map[] 127.0.0.1:52006 /connect <nil> <nil> <nil> 0xc0001042c0}     

通过对代码以及报错信息分析,问题出现在跨域请求上。

跨域是指浏览器不允许当前所在的源请求去访问另一个不一样的源请求,源是指请求协议、域名、端口号,这三个如果有一个不一致就是跨域请求。请自行搜索关键词:跨域请求,同源策略

使用spring boot的时候应该是加注解后自动处理了,但无法知晓原理,不过现在遇到这个问题可以深入了解一下

首先将发送请求的前端作为客户端,接收请求的作为服务端, 而从我们上面的测试已经可以看出,跨域是客户端单方面拒绝响应,服务端是接收请求并处理了的。

spring boot后端处理: 在 每个 Controller 类上加入 @CrossOrigin 注解, 或者在 Controller的基类中加上 @CrossOrigin 注解然后其他 Controller 类就有了这个 @Controller, 此时跨域访问就不会报错了。

三、跨域问题与同源策略

三种方法:JSONP、cors、代理,本文使用代理方法解决

1.JSONP

JSONP 是一种非官方的跨域数据交互协议 原理:利用<script> <img> <iframe> 等标签可以引入不同域资源的特性,将需要发送的请求的路径作为src参数,告知服务端回调函数的函数名 例如:

<script src="http://example.com/data.php?callback=dosomething"></script>

只支持 GET 请求,而不支持 POST 请求等其他类型的 HTTP 请求,现在一般不用,不需要浪费时间去学

2.cors

Cross-origin resource sharing 跨域资源共享。 它是一个新的 W3C 标准,它新增的一组HTTP首部字段,允许服务端其声明哪些源站有权限访问哪些资源

后端允许跨域:

https://blog.csdn.net/guoer9973/article/details/54341637

func LDNS(w http.ResponseWriter, req *http.Request) {

    if origin := req.Header.Get("Origin"); origin != "" {
        w.Header().Set("Access-Control-Allow-Origin", origin)
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers",
            "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    }
    if req.Method == "OPTIONS" {
        return
    }
    // 响应http code
    w.WriteHeader(200)
    query := strings.Split(req.Host, ".")
    value, err := ldns.RAMDBMgr.Get(query[0])
    fmt.Println("Access-Control-Allow-Origin", "*")
    if err != nil {
        io.WriteString(w, `{"message": ""}`)
        return
    }

    io.WriteString(w, value)
}
//本人没测试过,只是拿来了解原理用的

go的跨域有现成的库可以使用

go get github.com/gin-contrib/cors

我在go的多线程-实时数据采集路由设置里有使用

@Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest) req).getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Disposition,Origin, X-Requested-With, Content-Type, Accept,Authorization,id_token");
        response.setHeader("Access-Control-Allow-Credentials","true");
        response.setHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline'; script-src 'self'; frame-ancestors 'self'; object-src 'none'");
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-XSS-Protection", "1; mode=block");
        chain.doFilter(req, res);
    }
    //本人没测试过,只是拿来了解原理用的

请参考这篇文章

需要设置请求返回的响应头Header,详细设置内容讲解可以见这篇文章

前端设置跨域并允许携带cookie:

前端请求库一般有两种:流行框架下react或者vue使用axios、fetch这两者都可以,设置允许跨域的方式有点不一样。

原生fetch

fetch('localhost:3000',{
      /*允许携带cookies,默认情况没写这个是不会携带的*/
      credentials: 'include',
      /*允许跨域**/
      mode: 'cors'
})

axios

import axios from 'axios'
// 对所有 axios 请求允许携带cookie
axios.defaults.withCredentials = true;

// 对单独的 axios 请求允许携带cookie
 axios.get('localhost:3000', {
  withCredentials: true    
})

3.使用代理(本文使用的方法)

https://juejin.cn/post/7112373669594136612

已知:

  1. 同源策略
  2. 跨域是请求方单方面拒绝响应,接收方是接收请求并处理了。
  3. 代理服务器不是浏览器,它没有同源策略的限制

所以使用代理的解决方法是:

1)请求方发送请求给代理服务器,代理成功接收请求
2)代理将请求做了一点修改,把请求方的域名换装成与接收方同源的域名,然后再发送请求给接收方
3)接收方成功接收并处理数据,返回响应给代理,代理成功接收响应
4)代理将响应做了一点修改,把接收方的域名换装成与请求方同源的域名,然后再发送给请求方
5)请求方成功接收响应

vue配置代理的方法:
https://blog.csdn.net/qq_45787691/article/details/128074655

这里我使用vite构建的项目,故使用vite配置代理

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  //以上为其他配置
  //代理配置
  server:{
    proxy:{
        '/api':{
            //https: true, // 默认是false, 默认就是http协议,true将http协议转换为https协议
            // 配置需要代理的路径 --> 这里的意思是代理 http://localhost:默认端口/api/后的所有路由
            target:'http://127.0.0.1:9090',//目标服务器地址
            changeOrigin:true,//允许跨域
            ws:true,//允许websocket跨域
            // 把路径中的 /api都替换为空的字符串
            // 因为服务端地址里面是没有api字段的,api只是为了区别需要代理的路径,如果服务端有api字段则不需要替换    
            rewrite:(path)=>path.replace(/^\/api/,"")
        }
    }
  }
})

使用代理与axios

function newConnection() {
    msg.body = "2",
        reply.body = "2"
    alert("1111111")
    axios.get("/api/connect").then(res => {
        //console.log("222222")
        //alert("000000")
        reply.body = JSON.stringify(res)
    })
}

成功了,注意res是一个对象,我们用JSON.stringify将它转换为字符串

{"data":"asdfghjkl","status":200,"statusText":"OK","headers":{"access-control-allow-origin":"*","connection":"close","content-length":"9","content-type":"text/plain; charset=utf-8","date":"Mon, 09 Jan 2023 08:41:30 GMT"},"config":{"transitional":{"silentJSONParsing":true,"forcedJSONParsing":true,"clarifyTimeoutError":false},"adapter":["xhr","http"],"transformRequest":[null],"transformResponse":[null],"timeout":0,"xsrfCookieName":"XSRF-TOKEN","xsrfHeaderName":"X-XSRF-TOKEN","maxContentLength":-1,"maxBodyLength":-1,"env":{},"headers":{"Accept":"application/json, text/plain, */*"},"method":"get","url":"/api/connect"},"request":{}}

这个返回是完整的http响应,我们只需要响应的数据data即可

reply.body = res.data

同样后端只需要请求的内容

fmt.Printf("请求:%v", request.Body)

当然我们只是调用了方法,请求内容为空,控制台打印

{}