跨域问题
1.ajax请求什么时候会引发跨域问题?
在当前域名请求网站中,默认不允许通过ajax请求发送其他域名。
1.2:发生环境模拟
当在a服务器的页面中发生ajax请求其他域(这里指http://eureka7002.com:6061/)的资源时,浏览器的控制台就会抛出异常信息
发送的ajax请求
$.ajax({ type : "get", url : "http://eureka7002.com:6061/myServlet",
dataType : "JSON" data: {"userName":"天雁"}, success : function(data) { alert(data); },error: function () { alert("请求错误") } });
引发的错误信息
这种情况之下,b服务器可以接受到a服务器的ajax请求以及数据的,但是无法成功的响应
2.解决方案
2.1在b服务器端添加响应头信息,表示支持所有网站的请求
response.setHeader("Access-Control-Allow-Origin", "*");
2.2使用JSONP
在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,即一般的ajax是不能进行跨域请求的。但 img、iframe 、script等标签是个例外,这些标签可以通过src属性请求到其他服务器上的数据。利用 script标签的开放策略,我们可以实现跨域请求数据,当然这需要服务器端的配合。Jquery中ajax 的核心是通过 XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加 <script>标签来调用服务器提供的 js脚本。
当我们正常地请求一个JSON数据的时候,服务端返回的是一串 JSON类型的数据,而我们使用 JSONP模式来请求数据的时候服务端返回的是一段可执行的 JavaScript代码。因为jsonp 跨域的原理就是用的动态加载 script的src ,所以我们只能把参数通过 url的方式传递,所以jsonp的 type类型只能是get!
使用JSONP模式来请求数据的整个流程:
客户端发送一个请求,规定一个可执行的函数名(这里就是 jQuery做了封装的处理,自动帮你生成回调函数并把数据取出来供success属性方法来调用,而不是传递的一个回调句柄),服务器端接受了这个 backfunc函数名,然后把数据通过实参的形式发送出去
第一步:修改a服务器的ajax请求:
$.ajax({ type : "get", //jsonp方式只支持get方式,就是写post他也会自动转换为get url : "http://eureka7002.com:6061/myServlet", dataType : "jsonp", jsonp: "callback", //默认为callback,可修改,保持前后端一致就可以 data: {"userName":"天雁"}, success : function(data) { alert(data); },error: function () { alert("请求错误") } });
第二步:修改b服务器端代码
@WebServlet("/myServlet") public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName = req.getParameter("userName"); String callback = req.getParameter("callback"); System.out.println("接受到的数据:"+userName); String json="成功响应"; resp.getWriter().write(callback+"("+JSON.toJSONString(json)+")"); //callback里响应的结果要是JSON类型的数据 } }
2.3 使用HttpClinet进行请求转发
这种解决方式的思路就是,不让ajax直接发送跨域请求,而是请求自己服务器的Servlet,在Servlet中使用HttpClient进行请求转发
1.修改请求路径
$.ajax({ type : "get", url : "/myServlet", data: {"userName":"hhh"}, success : function(data) { alert(data); },error: function () { alert("请求错误") } });
2.添加Servlet
package com.ty.servlet; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; 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 java.io.IOException; @WebServlet("/myServlet") public class MyServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName = req.getParameter("userName"); //建立连接请求 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建get请求 HttpGet get=new HttpGet("http://eureka7002.com:6061/myServlet?userName="+userName); CloseableHttpResponse response = httpClient.execute(get); String result = EntityUtils.toString(response.getEntity()); //将A工程响应结果给页面 resp.getWriter().write(result); } }
4.还可以通过Nginx和SpringCloud的zuul来解决跨域问题
表单重复提交
1.引发表单重复提交的事件
网络延时
在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。
重新刷新
表单提交后用户点击【刷新】按钮导致表单重复提交
点击浏览器的【后退】按钮回退到表单页面后进行再次提交
用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交
2.解决方案
2.1使用JS解决网络延迟情况(不推荐)
<script type="text/javascript"> var isFlag = false; //表单是否已经提交标识,默认为false function submitFlag() { if (!isFlag) { isFlag = true; return true; } else { return false; } } </script> </head> <body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return submitFlag()"> 用户名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body>
思路就是当第一次点击提交按钮的将表单提交同时将判断值更改为true,知道再次点击提交按钮通过判断属性值就不可以提交了,
也可在第一次点击完提交按钮之后将提交按钮禁用
2.2使用Session保存Token令牌解决重新刷新
使用js的方式只可以解决网络延迟的问题
使用Token可以同时解决以上三种问题
当然,如果你允许用户在提交完后退之后可以重新提交的话,你可以使用下面这种方式
在表单页面中发送ajax请求,当加载这个页面的时候就会请求后台,后台会将一个Token值保存到Session中,然后绑定在表单中的隐藏域中,
表单提交的时候token的值也会随着表单的提交而提交,
之后就会在后台进行判断,如果是第一次提交的话就会通过验证,并情况Session,在点击刷新请求的时候就不会提交了
token.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>防止表单重复提交</title> </head> <body> <form action="/FromServlet" method="post"> <input type="text" name="userName"> <input type="hidden" name="token" value=${token}> <input type="submit" value="提交"> </form> <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script> <script> $(function () { //生成令牌 $.ajax({ url:"TokenServlet", type:"POST", success:function (token) { $("input[name=token]").val(token); } }) }) </script> </body> </html>
ajax请求地址TokenServlet
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //生成令牌 String token = UUID.randomUUID().toString(); //令牌保存到session当中 request.getSession().setAttribute("sessionToken",token); //响应 response.getWriter().write(token); }
表单提交地址
package com.ty.servlet; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; 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 java.io.IOException; @WebServlet("/myServlet") public class MyServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName = req.getParameter("userName"); //建立连接请求 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建get请求 HttpGet get=new HttpGet("http://eureka7002.com:6061/myServlet?userName="+userName); CloseableHttpResponse response = httpClient.execute(get); String result = EntityUtils.toString(response.getEntity()); //将A工程响应结果给页面 resp.getWriter().write(result); } }
2.3使用Session保存Token令牌解决后退之后重新提交
这种方式上面一种方式的区别就是,当你提交完成之后,点击后退再提交的话也会被当成重复提交
这种情况就不用发送ajax请求了,访问地址也由直接访问页面变为访问Servlet之后转发到页面,但是将token值放入session中的时机也就变为了
转发之前,在页面中使用el表达式获取token值绑定到隐藏域中
直接访问TokenServler,只有刷新这个请求的时候,才会重新生成token
package com.ty.servlet; 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 java.io.IOException; import java.util.UUID; @WebServlet("/TokenServlet") public class TokenServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("--------------------"); //生成令牌 String token = UUID.randomUUID().toString(); //令牌保存到session当中 req.getSession().setAttribute("token",token); req.getRequestDispatcher("token.jsp").forward(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } }
页面上也不在需要ajax
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>防止表单重复提交</title> </head> <body> <form action="/FromServlet" method="post"> <input type="text" name="userName"> <input type="hidden" name="token" value=${token}> <input type="submit" value="提交"> </form>
后台FromServlet代码不用做更改