本文参与「少数派读书笔记征文活动」https://sspai.com/post/45653
参考文章:https://blog.csdn.net/jiangwei0910410003/article/details/23337043
遇到的问题:
1、jstl包的导入(Idea 2018.02):首先我通过File->Project Structure->Libraries直接添加jstl的jar包失败,报错:org.apache.jasper.JasperException: This absolute uri http://java.sun.com/jsp/jstl/core) cannot be resolved in either web.xml or the jar files deployed with this application;解决方法:在web/WEB-INF下新建lib,然后将jstl 1.2(http://tomcat.apache.org/taglibs/standard/)的四个jar包全部导入lib中,右键lib->Add as Library
记得在jsp中添加:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
注意:不同jstl的版本对servlet和jsp版本的要求也不同,查看servlet和jsp版本的方法:找到tomcat解压文件夹->lib->jsp-api.jar/servlet-api.jar分别解压->META-INF->MANIFEST.MF
2、EL表达式取值过程:
EL表达式取值必需是servlet四大作用域(servletContext>session>request>pageContext)中有的值,这四个域都有setAttribute()和getAttribute(); EL表达式会自动从四大域中按作用范围从小到大搜寻对应名字的值,其内部调用的就是pageContext的findAttribute()
3、提交表单方式(Post/Get):
get方式效率高但安全性低,post是封装后进行提交安全性高。get方式常用于搜索、查询;post方法常用于用户注册登陆等
4、servlet页面跳转(转发/重定向):
HttpServletResponse.sendRedirect(url)——请求重定向
RequestDispatcher.forward(url)——请求转发
无论是forward方法还是sendRedirect方法调用前面都不能有PrintWriter输出到客户端
forward方法报错: java.lang.IllegalStateException: Cannot forward after response has been committed
sendRedirect报错:java.lang.IllegalStateException
5、web.xml加载过程:
当启动一个WEB项目时,容器包括(JBoss、Tomcat等)首先会读取项目web.xml配置文件里的配置,当这一步骤没有出错并且完成之后,项目才能正常地被启动起来。后续加载顺序是:
6、meta:
例子:
Metadata元数据是关于数据的数据(信息);元数据不会显示在页面上,但可以进行机器分析
例子(每30秒刷新一次页面):
http-equiv属性为content属性的information/value提供http header;http-equiv属性可用于模拟http响应头
7、设置无缓存:
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", -10);
以上设置方式有一个缺点:本地浏览器可以read,但是HTTP代理无法read
HTTP代理参考:https://imququ.com/post/web-proxy.html
8、抓包:
除了使用google或火狐自带的开发者工具抓包外,可使用wireshark;但是一般附带安装的WinPcap无法抓本地包,所以建议用Npcap(https://nmap.org/npcap/)替换WinPcap,安装时记得勾选Use DLT NULL as the loopback interface...和Install Npcap in WinPcap API...
启动wireshark,就会发现网络接口列表中的Npcap Loopback adapter,这个就是用来抓本地回环包的网络接口(如果是ipv6,localhost解析为::1)
注意:安装Npcap后可能出现无法连接网络的状况,此时只需通过控制面板->网络和Internet->网络和共享中心->更改适配器设置->右键禁用Npcap Loopback adapter即可;连接网络后在再启用
Cookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器,当用户使用浏览器再去访问服务器中Web资源时,就会带着各自的数据去。这样,web资源处理的就是各自的数据了。下面这张图就体现了Cookie技术原理:
javax.servlet.http.Cookie类用于创建一个Cookie,HttpServletResponse接口也定义了一个addCookie(Cookie cookie),用于在响应头中增加一个相应的Set-Cookie头字段;同样,HttpServletRequest接口也定义了一个getCookies(),它用于获取客户端提交的Cookie
Cookie API:
1、构造函数: Cookie(String name, String value):通过这个构造函数可以指定一个键值对,将信息存入到Cookie中,相同的name的值会被覆盖
2、常用方法:
例子1:使用Cookie技术查看客户端上次访问的时间
package com.lovebaobao.cookieconfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Date;
@WebServlet(name="Test",
urlPatterns = "/servlet/Test")
public class Test extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException{
test(request,response);
}
public void test(HttpServletRequest request,HttpServletResponse response)throws IOException {
//设置编码
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter out=response.getWriter();
out.print("您上一次访问的时间是:");
//获取用户的时间cookie,并且获取值,如果第一次的话,没有Cookie信息(Cookie数组为null)
Cookie[] cookies=request.getCookies();
for(int i=0;cookies!=null&&i
第一次运行结果:
Cookie信息
Header信息
再次运行结果:
Cookie信息
Header信息
删除Cookie信息,只需将有效期设置为0:cookie.setMaxAge(0);(cookie名称和路径path必须与要覆盖的cookie相同!)
第一次运行结果:
Cookie信息
Header信息
再次运行结果:
Cookie信息
Header信息
总结:
注意:很多Web浏览器支持会话恢复功能,这个功能可以使浏览器保留所有tab标签,然后在重新打开浏览器的时候将其还原,与此同时,cookie也会恢复
另一种设置cookie有效期的方式(在特定的日期(Expires)才会失效):
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
Session是服务器端技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它Web资源时,其它Web资源再从用户各自的session中取出数据为用户服务
Session技术原理如图:
同时javax.servlet.http.HttpSession接口也是一个域对象;Session的生命周期是:
1、创建
某server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建(如果当前请求存在session则不会重新创建)
2、销毁
(1)程序调用HttpSession.invalidate()
(2)距离上一次客户端发送的session id时间间隔超过了session的最大有效时间
(3)服务器进程被停止
注意:关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效
Session API:
例子2:
TestServlet1
package com.lovebaobao.cookieconfig;
import javax.servlet.RequestDispatcher;
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.IOException;
@WebServlet(name = "TestServlet1",
urlPatterns = "/servlet/TestServlet1")
public class TestServlet1 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
HttpSession session=request.getSession();
System.out.println("SessionObject:"+session);
System.out.println("SessionId:"+session.getId());
//request.getRequestDispatcher("/servlet/TestServlet2").forward(request,response);
response.sendRedirect("/servlet/TestServlet2");
}
}
TestServlet2
package com.lovebaobao.cookieconfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet(name = "TestServlet2",
urlPatterns = "/servlet/TestServlet2")
public class TestServlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
HttpSession session=request.getSession();
System.out.println("SessionObject:"+session);
String sessionId=session.getId();
System.out.println("SessionId:"+sessionId);
Cookie cookie=new Cookie("JSESSIONID",sessionId); //把系统的sessionid覆盖掉
cookie.setPath("/");
//session.setMaxInactiveInterval(30*60);
cookie.setMaxAge(30*60);//因为Session的默认生命周期是30分钟
response.addCookie(cookie);//回写到客户端浏览器
}
}
运行结果:
控制台
SessionObject:org.apache.catalina.session.StandardSessionFacade@2e3e251a
SessionId:71DF387678DFE2615BE749FD35D84A48
SessionObject:org.apache.catalina.session.StandardSessionFacade@2e3e251a
SessionId:71DF387678DFE2615BE749FD35D84A48
Cookie信息
Header信息
总结:
30
(2)通过session.setMaxInactiveInterval(30*60);备注:Session的实现原理
其实Session的实现原理是基于Cookie的技术实现,每次创建session都会对应生成一个session id,然后将这个session id信息写入到cookie中,但是这个cookie的有效时间是这个浏览器的进程,即cookie是在服务器的内存中,没有写到客户端磁盘。我们会在浏览器中使用同一个session,当我们关闭浏览器的时候,session id也就没有了,当我们打开浏览器的时候,服务器端又会重新创建一个session,此时方法二setMaxInactiveInterval(30*60)就无效了。所以为了解决这个问题,我们需要修改这个cookie的有效期,并将这个cookie回写到客户端浏览器中,即方法三cookie.setMaxAge(30*60)。
但是现在还有一个问题,如果浏览器禁止Cookie,那我们写给客户端的Cookie,客户端是不接受的,所以这样也会丢失数据,于是一项新技术产生了——URL重写:其实这个技术很简单,就是将网站中的网址后面携带一个session id,这样就可以保证当禁止Cookie的时候,仍然可以得到session id,然后找到相应的session
例子3:
IndexServlet
package com.lovebaobao.cookieconfig;
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(name = "IndexServlet",
urlPatterns = "/servlet/IndexServlet")
public class IndexServlet extends HttpServlet {
public final static long serialVersionUID=1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
request.getSession();
//URL重写
String buyUrl=response.encodeURL("/servlet/BuyServlet");
String payUrl=response.encodeURL("/servlet/PayServlet");
// String buyUrl="/servlet/BuyServlet";
// String payUrl="/servlet/PayServlet";
response.getWriter().print("Buy
");
response.getWriter().print("Pay
");
}
}
BuyServlet
package com.lovebaobao.cookieconfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet(name = "BuyServlet",
urlPatterns = "/servlet/BuyServlet")
public class BuyServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
HttpSession session=request.getSession();
session.setAttribute("store","volleyball");
Cookie cookie=new Cookie("JSESSIONID",session.getId());
cookie.setMaxAge(30*60);
cookie.setPath("/");
response.addCookie(cookie);
}
}
PayServlet
package com.lovebaobao.cookieconfig;
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.IOException;
import java.util.Enumeration;
@WebServlet(name = "PayServlet",
urlPatterns = "/servlet/PayServlet")
public class PayServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
HttpSession session=request.getSession();
Enumeration cart=session.getAttributeNames();
response.getWriter().print("你买的东西有:
");
while(cart.hasMoreElements()){
String name= cart.nextElement();
response.getWriter().print(session.getAttribute(name)+"
");
}
}
}
注意:先禁用浏览器Cookie
运行结果:
->Buy->退回->Pay
但是URL重写也存在弊端,对于每个url都必须重写,而且当我们关闭浏览器后再去访问的时候,会再重新创建一个session,而这个session id会重写到url中,所以不是之前的session了,至此,也暂时没有其他办法挽救了。
注意:如果同时保留Cookie和URL重写这两种技术,我们会发现当前用户携带Cookie过来的时候,url链接之后是没有jsessionid字段的,所以Cookie中的JSESSIONID优先级高
方法一:前端使用JS技术
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
String contextpath = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+contextpath+"/"; //获取当前项目路径(http://localhost:8085/studytest/)
%>
index.jsp
很明显上述方法是有问题的,因为当此页面重新加载刷新后,就又可以提交了;所以前端技术不能彻底防止表单的重复提交,我们还需加入服务器端的处理
方法二:
IndexServlet2
package com.lovebaobao.cookieconfig;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
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.security.MessageDigest;
import java.util.Random;
@WebServlet(name = "IndexServlet2",
urlPatterns = "/servlet/IndexServlet2")
public class IndexServlet2 extends HttpServlet {
public final static long serialVersionUID=1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setDateHeader("expires",0); //无缓存
try{
test(request,response);
}catch (Exception e){
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
//产生表单
public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
//产生表单号
String token=TokenProcessor.generateToken();
request.getSession().setAttribute("token",token);
request.getRequestDispatcher("/index.jsp").forward(request,response);
}
}
//单例模式,一个人报数比多个人报数重复率低
class TokenProcessor{
private static final TokenProcessor instance=new TokenProcessor(); //创建实例
private TokenProcessor(){
}
public static TokenProcessor getInstance(){
return instance;
}
//产生随机数
public static String generateToken()throws Exception{
String token=System.currentTimeMillis()+new Random().nextInt()+"";
try{
/*
static MessageDigest getInstance(String algorithm)
返回实现指定摘要算法的MessageDigest对象
*/
MessageDigest md=MessageDigest.getInstance("md5");
byte[] md5=md.digest(token.getBytes()); //通过执行填充等操作来完成哈希计算
BASE64Encoder encoder=new BASE64Encoder();
return encoder.encode(md5);
}catch (Exception e){
return null;
}
}
}
FormSubmitServlet
package com.lovebaobao.cookieconfig;
import sun.font.TrueTypeFont;
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(name = "FormSubmitServlet",
urlPatterns = "/servlet/FormSubmitServlet")
public class FormSubmitServlet extends HttpServlet {
public final static long serialVersion=1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
boolean flag=isTokenValid(request);
if(!flag){
System.out.println("请不要重复提交表单");
return;
}
System.out.println("向数据库中注册用户");
request.getSession().removeAttribute("token");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
//验证表单提交是否有效,返回TRUE,表示表单可以提交
public boolean isTokenValid(HttpServletRequest request){
//首先判断表单传递过来的随机数是否有效
String clientToken=request.getParameter("token");
if(clientToken==null){
return false;
}
//然后再判断服务器端session域中的随机数是否有效
String serverToken=(String)request.getSession().getAttribute("token");
if(serverToken==null){
return false;
}
//再比较表单携带过来的随机数和session域中的随机数是否一致
if(!clientToken.equals(serverToken)){
return false;
}
return true;
}
}
运行结果:
(第一次提交)控制台
向数据库中注册用户
->退回(第二次提交)控制台
请不要重复提交表单
备注:实现原理
服务器端首先产生一个随机数,将这个随机数写入到session域中保存,然后利用隐藏标签标记一个唯一的表单(利用EL表达式获取session中的随机数存入value属性)。如果隐藏标签中的value值与之前写入到session域中的那个随机数都存在且相同,说明是第一次提交,之后就将那个随机数从session域中删除;当用户再一次点击提交表单的时候,进行相同的判断,此时session域中已没有对应的随机数,所以提交失败。
但是有一个问题就是:使用Random类产生的随机数的长度是不一定的,我们现在想让每次得到的随机数的长度是一样的,此时我们会想到MD5,首先每个数据的MD5是唯一的,还有MD5的长度是一定的128位(16Bytes),但是还有一个问题那个MD5是一个字节数组,当我们将其转化成String类型的时候,不管使用什么码表,都会出现乱码的问题,在随机数中出现了乱码,那就等于是死了,所以我们需要解决这个问题,这时候我们想到了BASE64编码,因为我们知道任何字节数据通过BASE64编码之后生成的字节数据的大小是在0~63之间的(原理很简单,就是将字节数组从左到右,每六位是一组,然后再高位补足两个0,那么这个字节大小的范围就是在0~63了),同时BASE64有一套自己的码表,里面只有大小写字母和数字,这样就不会产生乱码了。