跨域问题以及关于Nginx的一些内容

为什么有跨域问题

什么是跨域

当前URl 请求URL 是否跨域 原因
http://www.zhihu.com/a.html http://www.zhihu.com/b.html 本域(协议、域名、端口号相同)
http://www.zhihu.com https://www.zhihu.com 协议不同(http、https)
http://www.zhihu.com http://www.baidu.com 域名不同(zhihu、baidu)
http://www.zhihu.com:8080 http://www.zhihu.com:9090 端口号不同(8080、9090)

根据上面的表,总的来说就是协议主机端口号三者任意一个不同就是跨域。
在这里插入图片描述

同源策略

跨域问题的源头就是因为浏览器遵循了同源策略(same-origin policy),即只有在满足协议、主机、端口号相同的情况下,才能共享信息。比如说在http://www.zhihu.com向浏览器添加了一个cookie,那么http://www.baidu.com就无法拿到这个cookie。

同源策略主要限制了JS的能力:

1.无法读取非同源的 cookie、Storage、indexDB的内容

2.无法读取非同源的DOM

3.无法发送非同源的AJAX,更加准确的说应该是发送了请求但被浏览器拦截了,也就是说浏览器会拦截非同源的请求。

为什么会有同源策略?简单来说是为了安全:

1.为了防止恶意网页可以获取其他网站的本地数据。

2.为了防止恶意网站iframe(一个HTML嵌入到另一个HTML)其他网站的时候,获取数据。

3.为了防止恶意网站在自已网站有访问其他网站的权利,以免通过cookie免登,拿到数据。

主流解决方式

JSONP

我们会发现,Script标签引用了别的源又不会出现问题,比如<script src="https://unpkg.com/[email protected]/src/httpVueLoader.js"></script>,这个很明显是跨域的。至于为什么可以,而且为什么Script只支持GET的方式,人家就是这么设计的,可能就是为了方便。至于为什么只支持GET,Script怎么写POST?本身就不支持,而且也没意义,这个JSONP,其实就是请求一段JS脚本,也就是直接拉过来JS脚本,把执行这段脚本的结果当做数据,它根本不和后端交互。

JSONP就是JSON和Padding,也就是服务端要在返回的数据外层包裹一个客户端已经定义好的函数。

具体就不实操了,因为也用不上,大概的流程就是下面这样:

// 前端调用格式
<script src="http://www.example.net/sample.aspx?callback=mycallback"></script>

在这个地址你实现这个回调函数,这样也就可以跨域了。
比如你想返回下面这样的数据:

mycallback({
    
     foo: 'bar' });

你只需要给他包一层函数,这样就能返回这个数据。所以我感觉这个JSONP其实就是返回了一个“静态”的JS脚本,我指的这个“静态”是指依然无法和后端交互。

mycallback = function(data) {
    
    
  alert(data.foo);
};

CORS

全称为Cross-origin resource sharing。它的流程就是首先当浏览器要跨域请求时,先像服务器发送一个Preflight Request(预检请求);服务器收到后,如果允许就会返回一个添加了Access-Control-Allow-Origin: true的响应(得自己配),还有像Access-Control-Allow-MethodAccess-Control-Allow-CredentialsAccess-Control-Max-Age等等这种对方法以及其他的限制;浏览器收到后,当看到Access-Control-Allow-Origin满足时,以及其他条件满足时,就像服务器再次发送请求,去拿数据;最终成功拿到数据。(这个图片上的optional,我觉得意思可能是不跨域的时候,就不需要)
https://medium.com/@x24mak/cross-origin-resource-sharing-cors-275e52b021b8
下面这个是SpringBoot的实现,只需要加个过滤器,无需其它配置;前端无需任何配置。实现起来比较容易:

@Configuration
public class CorsFilterConfig implements Filter {
    
    
   @Override
   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    
    
      HttpServletResponse response = (HttpServletResponse) res;
      HttpServletRequest request = (HttpServletRequest) req;

      // 不使用*,自动适配跨域域名,避免携带Cookie时失效
      String origin = request.getHeader("Origin");
      if(StringUtils.isNotBlank(origin)) {
    
    
         response.setHeader("Access-Control-Allow-Origin", origin);
      }

      // 自适应所有自定义头
      String headers = request.getHeader("Access-Control-Request-Headers");
      if(StringUtils.isNotBlank(headers)) {
    
    
         response.setHeader("Access-Control-Allow-Headers", headers);
         response.setHeader("Access-Control-Expose-Headers", headers);
      }

      // 允许跨域的请求方法类型
      response.setHeader("Access-Control-Allow-Methods", "*");
      // 预检命令(OPTIONS)缓存时间,单位:秒
      response.setHeader("Access-Control-Max-Age", "3600");
      // 明确许可客户端发送Cookie,不允许删除字段即可
      response.setHeader("Access-Control-Allow-Credentials", "true");

      chain.doFilter(request, response);
   }

   @Override
   public void init(FilterConfig filterConfig) {
    
    

   }

   @Override
   public void destroy() {
    
    

   }
}

服务器代理

这里就是指Nginx,首先先说明一点,跨域是因为浏览器请求的原因,服务器之间请求是没有关系的。下面介绍一下Nginx常用的使用场景,其中第一条“静态资源服务”中解释了如何处理跨域。
https://www.bilibili.com/video/BV1db411F74p/?spm_id_from=333.337.search-card.all.click&vd_source=92e059cc3f2abb4a9d79be75e6365393

1)静态资源服务

首先,先搞清一个问题,前后端分离时,前端是如何部署的?
如果非前后端分离的项目,我们直接把Java打包成一个Jar包,直接扔到Linux上运行即可,之后就可以通过设置好的端口号访问了。
如果前后端分离,后端还是同样的方式,而对于前端最直接的,就是把前端代码放到后端的资源目录里,一起发布即可,利用了Tomcat;比较“分离”的方式,就是利用Nginx,把前端部署到Nginx上,让它可以以IP加端口号的方式进行访问(没服务器,你直接把前端仍在Linux上,怎么访问。),形成下图的效果。这类似于Tomcat,至于有什么不同就不讨论了,最直接的Tomcat能跑Java,Nginx只能静态资源。
在这里插入图片描述

补充一下,Tomcat是什么:
SUN公司推出了Java语言,然后又推出JavaEE规范(用来开发B/S架构软件,也就是Web。包含JDBC,JNDI,EJB,RMI,JSP,Servlets,XML,JMS,Java IDL,JTS,JTA,JavaMail,JAF)。之后又一家公司Apache推出了Tomcat服务器,不过只实现了Servlet(专门用来接收客户端的请求,调用对应方法执行请求,然后返回数据)和JSP(底层实现也是Servlet,只是他更关注Web展示层,使用JSTL标签本质还是在访问Servlet)规范,所以大家称他为轻量级服务器(所以我们在连接数据库的时候还需要导入JDBC)。

为什么用它来做前端服务器:
问就是性能好,它底层采用了多进程和I/O多路复用,可以接受高并发的请求,速度也更快;配置简单,而且本身就是由很多模块组成的,扩展性强;有很高的可靠性,它采用多进程模式,其中有一个master主进程和N多个worker进程,每个worker都是独立工作的,其中某一个worker进程出错时,可以快速拉起新的worker进程提供服务;可以热部署;开源。
还有这篇文章写的很清楚,直接复制过来:

原文链接:https://zhuanlan.zhihu.com/p/143700821
总的来说,Apache或者Nginx 是HTTP Server,Tomcat 则是一个Application Server也有人说是Web Server,但本质上它是一个Servlet/JSP应用的容器,一个 HTTP Server 关心的是HTTP协议层面的传输和访问控制,所以在Apache/Nginx上你可以看到代理、负载均衡等功能。客户端通过HTTP Server访问服务器上存储的资源(HTML 文件、图片文件等等)。
而应用服务器,顾名思义是一个应用的容器。它首先需要支持开发语言的Runtime环境(对于Tomcat来说,就是Java),保证应用能够在应用服务器上正常运行。其次,需要支持应用相关的规范,例如类库、安全方面的特性。对于 Tomcat 来说,就是需要提供 JSP/Sevlet 运行需要的标准类库、Interface等。为了方便,应用服务器往往也会集成 HTTP Server的功能,但是不如专业的 HTTP Server那么强大,所以应用服务器往往是运行在 HTTP Server 的背后,执行应用,将动态的内容转化为静态的内容之后,通过 HTTP Server 分发到客户端。
这也是为什么Nginx往往与Tomcat配合使用的原因。

Nginx能存储图片?
上面一个问题提到了“客户端通过HTTP Server访问服务器上存储的资源(HTML 文件、图片文件等等)。”,Nginx存储不了图片,对象存储这种东西需要引入第三方。说到这里,突然想到Tomcat,或者说Servlet能存储图像。Servlet其实本身不能存储,比如下面是我几年前JSP上传文件的代码(把一些无关的代码删了),它主要了org.apache.commons.fileupload这里面的包,所以就实现了可以在对应目录下进行存储。

import com.zust.ysc012.dao.IAllDao;
import com.zust.ysc012.dao.impl.CommonDaoImpl;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.List;
@WebServlet(urlPatterns = "/uploadServlet")
public class UploadServlet  extends HttpServlet {
    
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        //省略//
        boolean multipartContent = ServletFileUpload.isMultipartContent(req);
        if (multipartContent) {
    
    
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            try {
    
    
                List<FileItem> items = upload.parseRequest(req);
                Iterator<FileItem> iter = items.iterator();
                while (iter.hasNext()) {
    
    
                    FileItem item = iter.next();
                    if (item.isFormField()) {
    
    
                        if (item.getFieldName().equals("name")) {
    
    
                            name = item.getString("UTF-8");
                        } 
                        //省略//
                    } else {
    
    
                        String fileName = item.getName();
                        System.out.println("file" + fileName);
                        String ext = fileName.substring(fileName.indexOf(".") + 1);
                        String path = "C:\\C\\new_design\\src\\main\\webapp\\WEB-INF\\upload";
                        onlyID2 = commonDao.select_max_ID_sql();
                        onlyID2++;
                        fileName = onlyID2 + "." + ext;
                        File file = new File(path, fileName);
                        item.write(file);
                    }
                }
                //省略//
            resp.sendRedirect("/view/teacher/tea_pre_subject.jsp");
        }
    }
}
2)反向代理服务

首先区分一下正向代理和反向代理,正向代理是为客户端做事,隐藏客户端,去请求服务器,最终服务器是不知道这个请求一开始是哪个客户端发起的。而反向代理是为服务器做事,隐藏服务器,客户端直接请求反向代理服务器就拿到数据就行,客户端不知道这个数据是哪个最终服务器处理的。
所以正向代理是帮客户端做事情,反向代理是帮服务器做事情。但无论正向还是反向都是处于客户端和服务器之间,做一个桥梁作用,从功能来讲是一样的,但正向代理一般都和服务端没关系,而反向代理通常是服务端为自己的服务器布的。
在这里插入图片描述
在这里插入图片描述
对于正向代理常举的例子就是VPN,它的原理大概是,比如访问google.com,我们肯定是访问不了的,我们可以连上VPN服务器将我们的IP地址变成美国的IP地址,这样就可以顺利访问google.com了。

那反向代理能做VPN吗?肯定不行。虽然说正向代理和反向代理本质上是一样的,都是中间件,但是它实现的逻辑是不一样的。反向代理目的是要隐藏服务端的,也就是客户端直接访问Nginx服务器,然后Nginx服务器帮你把你的请求分到特定的服务器,然后拿到结果后在返回给你。而正向代理是你直接访问google.com,而不是Nginx服务器,Nginx服务器捕获了你的请求,然后它帮你请求google.com,拿到数据后再给你。

而反向代理常用的功能就是负载均衡,那这里提一下Nginx的负载均衡和像Spring Cloud里Ribbon的负载均衡的差别。Nginx负载均衡的是服务器IP,而Ribbon负载均衡的是实例,也就是不同的端口。
同一个服务,建多个实例有什么用,首先就是提高可靠性,如果只有一个实例,当该实例发生故障,那整个服务就会宕机,如果有多个,一个挂了,其它的还可以继续提供服务;提高系统并发能力,请求量增大时,可以将请求分散到多个实例中处理。
在这里插入图片描述

CSDN好的解释:https://blog.csdn.net/baidu_41267789/article/details/119379996
举例说明:
用户下单,假设存在两个模块订单模块和商品模块,两个模块均部署了多个服务用于实现负载均衡,并注册在同一个注册中心。用户发送下单请求后,请求先到达nginx,由nginx进行负载均衡将请求转发给订单服务,订单服务处理业务后,需要调用商品服务扣减库存,内部服务一般使用rpc通信,订单服务调用商品服务扣减库存的时候,通过Ribbon先从注册中心获取所有商品服务,然后通过负载均衡算法选取一个可用服务进行调用,过程结束,这个过程是发生在订单服务内的。
综合上面的例子
一、为什么说Nginx是服务端的负载均衡,Ribbon是客户端的负载均衡呢?
1.用户发送请求到nginx,nginx是服务端。
2.Ribbon是微服务之间通信的负载均衡,订单服务调用商品服务时,订单服务就是客户端,商品服务就是服务端。
二、为什么说Nginx是集中式的负载均衡,Ribbon是消费者内部线程实现的负载均衡呢?
1.nginx是所有请求都会通过nginx进行分发,所以是集中式的负载均衡。
2.订单服务在调用商品服务时,在订单服务内部通过Ribbon的负载均衡算法选出商品服务后进行调用,所以是消费者内部线程实现的负载均衡。

上图还提到了缓存加速,这其实就和它提供的静态资源服务差不多,把常用的一些静态资源缓存起来,省的每次还要请求后端服务器,好处也就是上面做静态资源服务提到的好处。

3)API服务

OpenResty,是一个基于Nginx和Lua的高性能Web平台,其内部集成了大量精良的Lua库、第三方模块以及大多数依赖项。用于方便地搭建起能够处理超高并发,扩展性极高的动态Web应用、Web服务和动态网管。

简单来讲,就是比Java处理高并发的能力更强,但是你如果要使用这个,需要把业务逻辑从原来的java改成Lua。额,比较高级,不去过多学习了。

其他

如果不是通过Ajax、Axios等这些方式访问后端,只是在浏览器端互相拿Cookie、传些信息等,这篇文章也提到了这些场景的解决方式,比如说两个网页一级域名相同,只是二级域名不同(比如http://www.member.bilibili.com和http://www.mail.bilibili.com),JS可以通过document.domain这种方式共享Cookie,还有通过LocalStorage等等,由于我不太写前端,这些就不总结了。

猜你喜欢

转载自blog.csdn.net/Fishermen_sail/article/details/129476596