一、问题描述
今日遇到了一个问题,要去获取HTTP报文在请求和响应的时间,因为没有原生的API可以调用,所以需要一定的技巧~
- 下面主体的框架和代码,我使用了form表单去构造一个POST请求,然后在Servlet中重写doPost()方法,然后实现获取请求时间和响应时间的代码逻辑
<form action="print" method="post"> <input type="submit" value="打印信息"> </form>
@WebServlet("/print") public class printServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //... } }
二、抓包观察
我们可以先去观察一下在发起POST请求后这个HTTP请求报文中是否在这个请求时间,如果有的其实可以把它通过一定手段获取到
- 但遗憾的是,我并没有发现任何与时间相关的东西😥
- 那再到请求报文中去看看的话,就发现了与时间相关的内容,不过对照现在的时间仔细一看的话,却查了不少,网上一搜就发现GMT是格林尼治时间,要比现在的时候早上8个小时,但看到一个时间却非常激动,想着如何拿到它
三、查找文档
因为【HttpServletRequest】和【HttpServletResponse】给我们提供了许多原生API,所以我们可以去找找看有哪些API是我们可以用得上的,这里推荐直接进官网 ——> 链接
- 可以看到,无论是前者还是后者都未我们提供了很多的方法,这也我也会在代码中使用几个,不过找了一圈我发现完全没有获取时间的那种API,于是就没有再查找下去了
四、思考尝试
再找了很久还是一无所获,干脆就想着自己去手动获取一下这个时间
- 此时我就想到了Java里面的Date类,可以获取当前系统的时间,以及随之对应的是格式化解析
SimpleDateFormat()
,于是便立马写下了这几句代码
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEEE");//格式化日期 Date currentTime = new Date();//获取当前时间 String date = formatter.format(currentTime).toString();
💬 但是要怎么使用这个当前时间呢?去获取哪段时间呢?
- 首先我就提出了上面这个问题,思考了一下从Tomcat解析HTTP请求到将其交给Servlet做处理,那么能够最早获取到时间的地方就是一开始进入
doPost()
的时候,我们记录下当前的时间,就可以将其近似于HTTP的请求时间
💬 请求时间有了,那响应时间呢?也用这个去求吗?
- 这个的话不一定,因为刚才我们看了HTTP的响应报文,也就是服务端在处理完数据后给浏览器返回的一个响应,里面是存在【Date】这个时间的,你可以选择使用
getHeaderNames()
先获取到整个报文的头部,因为他的返回值是一个Collection集合,所以你可以通过遍历集合的方式与getHeader()
相配合格式化地输出里面的内容,这里我就不细说了 - 也是一样,我们可以通过上面这种方式去获取响应时间,何时获取才是最正确的呢?那就是在
resp.getWriter().write()
这个发还响应之前去获取时间,这是最后的机会了!
五、精益求精
通过返回我们在后端代码中获取到的时间,将其返还给浏览器时便看到这个【请求时间】和【响应时间】是一样的,这是为什么呢?
- 仔细观察我设置的日期格式化,是精确到秒的,如果你有一点计算机常识的话就可以知道计算机的运行速度是很快的,基本是以纳秒为单位,此时我们若是只精确到秒 的话可能还看不太出来,应该再多精确几位
yyyy-MM-dd HH:mm:ss
- 可以看到,此时我精确到了纳秒的级别,此时再去观察的话就会有所不同
yyyy-MM-dd HH:mm:ss.SSSSSSSSS
- 可以看到虽然精确了很多位数,但是二者之间的差距还是很小,毕竟计算机的处理速度也是非常快的
但是这么去做的只是钻了一个牛角尖🐂,后面我又想到了一个更聪明的办法
- 那就是让程序等待一段时间!
try { Thread.sleep(500); //让程序暂停0.5s } catch (InterruptedException e) { throw new RuntimeException(e); }
- 学习过Java多线程的同学一定马上就能反应过来了,可以使用Thread类中的
sleep()
方法让程序睡上几秒,然后再去获取时间。此时我们再去观察【请求时间】和【响应时间】的话就会有些差距了。如果你想让差距更加明显的话可以让程序多睡一会
六、源码解说
这里的话附上我们解决本体所用的源码
前端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>表单</title> </head> <body> <form action="print" method="post"> <input type="submit" value="打印信息"> </form> </body> </html>
后端:
@WebServlet("/print") public class printServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); // 在doPost()方法开始时获取一下请求时间 SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSSSS");//格式化日期 Date currentTime=new Date();//获取当前时间 String date=formatter.format(currentTime).toString(); StringBuilder stringBuilder = new StringBuilder(); System.out.println("-------------------------------------------------"); stringBuilder.append("请求的URL = " + req.getRequestURL()); //请求的URL stringBuilder.append("<br>"); stringBuilder.append("请求方法 = " + req.getMethod()); //请求方法 stringBuilder.append("<br>"); stringBuilder.append("请求时间 = " + date); //请求时间 stringBuilder.append("<br><br>"); try { Thread.sleep(500); //让程序暂停0.5s } catch (InterruptedException e) { throw new RuntimeException(e); } // 在doPost()方法快结束时获取一下响应时间 currentTime = new Date();//获取当前时间 String date2 = formatter.format(currentTime).toString(); stringBuilder.append("响应时间 = " + date2); stringBuilder.append("<br>"); stringBuilder.append("响应状态码 = " + resp.getStatus()); stringBuilder.append("<br>"); resp.getWriter().write(stringBuilder.toString()); } }
💬 稍微来讲一下后端的这块逻辑
- 这边我主要使用到的就是这个【StringBuilder】类,使用它构造出来的对象,我们需要在单线程的环境下连接多个字符串,此时用它最合适了,通过里面的
append()
方法去进行连接,这里我测试了Reques和Response两个类中的方法,打印出了HTTP报文的一些内容 - 还要注意,有些同学在看我这句代码的时候很疑惑,所我怎么把前端的代码写到后端来了
stringBuilder.append("<br>");
- 这里主要就是依赖于前面的这句代码,我将当前body中的数据格式设置为了HTML,代表可以识别HTML代码,所以这个
<br>
就会被当做换行了,若是不写这个的话所有的内容都会挤在一起的
resp.setContentType("text/html;charset=utf-8");
- 最后的这句话就是将拼接的StringBuilder类型字符串转换为String类型并发还给浏览器一个响应,将拼接的内容都打印展示出来
resp.getWriter().write(stringBuilder.toString());