`
酷的飞上天空
  • 浏览: 517796 次
  • 性别: Icon_minigender_1
  • 来自: 无锡
社区版块
存档分类
最新评论

sessionDestroyed执行时间的奇怪问题

    博客分类:
  • J2EE
阅读更多

问题描述:

用户登陆后转到list.jsp页面,此页面不停向服务器请求数据。用户的登录名保存到session和servletContext的用户列表中一个list。当用户session失效的时候从用户列表中删除用户。

问题是:

虽然不停向服务器发送数据,但还是会在不到一分钟的时间内调用sessionDestroyed方法,难道此方法不是在session失效的时候才被调用?

 

下面贴出完整代码。

login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>login</title>
</head>
<body style="text-align: center;">

	<p style="margin-top: 200px;" />
	<p>${error }</p>
	<form action="login" method="post">
		用户名:<input type="text" name="name"/>
		<p/>
		<input type="submit"/>
	</form>
</body>
</html>

 

 LoginServlet

public class LoginServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		req.setCharacterEncoding("UTF-8");
		String name = req.getParameter("name");
		List<String> list;
		if(name==null||name.equals("")){
			req.setAttribute("error", "登录名不能为空!");
			req.getRequestDispatcher("login.jsp").forward(req, resp);
			return;
		}
		//从ServletContext中取得已登录用户信息的容器,一个list
		Object o = getServletContext().getAttribute("list");
		if(o==null){
			//如果没有,则创建一个并放入ServletContext
			list = new ArrayList();
			getServletContext().setAttribute("list", list);
		}
		else {
			//有则返回
			list = (List<String>)o ;
		}
		//如果当前登陆用户有重名的则,无法登陆
		if(list.contains(name)){
			req.setAttribute("error", "用户已存在!");
			req.getRequestDispatcher("login.jsp").forward(req, resp);
			return;
		}
		//将登陆用户放入ServletContext中的list
		list.add(name);
		getServletContext().setAttribute("list", list);
		//将当前用户的用户名放入session
		HttpSession session = req.getSession();
		Logger.getLogger(this.getClass().getName()).info("原始会话超时时间为:"+session.getMaxInactiveInterval());
		session.setMaxInactiveInterval(5); //设置当前会话的失效时间,单位是秒。即5秒不回应则判定离线
		session.setAttribute("name", name);
		Logger.getLogger(this.getClass().getName()).info("设定会话超时时间为:"+session.getMaxInactiveInterval());
		
		req.setAttribute("list", list);

		req.getRequestDispatcher("list.jsp").forward(req, resp);
		
	}
}

 list.jsp ,用到了jquery进行Ajax调用

  

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>login</title>
<style type="text/css">
	.left{
		width: 200px;
		height: 600px;
		float: left;
		border: dashed 1px black;
	}
	.right{
		width: 600px;
		height: 600px;	
		margin-left: 220px;
		border: dotted 1px black;
		overflow: scroll;
	}
</style>
<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<script type="text/javascript">
	//如果返回类型定义为json,则无法重复执行,所以只能自己eval了。原因是jquery1.4解析json格式比较严格,这里返回的是不严格的json格式,致使解析失败。解决方法:1.返回严格的json格式数据2.自己eval生成json对象
	$(function(){
		function getText(){
			$.post("control",function(data){
				var json = eval("("+data+")");
				//返回在线用户数组
				var list = json.list;
				var date = json.date; //服务器端返回的日期
				var $left = $(".left");
				var nameList = "";  //构造用户在线字符串形式,方便添加进html
				for(var i in list){
					nameList = nameList+"<li>"+list[i]+"</li>"
				}
				$left.empty().html("当前用户列表:<br/><ul>"+nameList+"</ul>");
				$(".right").append("服务器端传来数据:"+date+'<br/>');
				//继续请求服务器
				getText();
			});
		}
		//开始执行
		getText();
	});
</script>
</head>
<body>
<div class="left">
当前用户列表:
<p></p>
<ul>
	<%
		List<String> list = (List<String>)request.getAttribute("list");
		for(String s:list){
	%>
		<li><%=s %></li>
		<%} %>
</ul>
</div>
<div class="right">

</div>
</body>
</html>

   ControlServlet

 

public class ControlServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String text;
		List<String> list;
		StringBuilder sb = new StringBuilder();
		text = simpleDateFormat.format(new Date());
		//取得在线的列表
		Object o =getServletContext().getAttribute("list");
		if(o==null) list = null;
		else list = (List<String>)o;
		
		//构造返回的String, json格式
		sb.append("{list:[");
		if(list==null){
			//一个空的数组
			sb.append("],");
		}else{
			for(int i=0;i<list.size();i++){
				sb.append("\"");
				sb.append(list.get(i));
				sb.append("\"");
				//如果是最后一个则不添加 ','
				if(i!=list.size()-1) sb.append(",");
			}
			sb.append("],");
		}
		//把服务器返回的时间添加入json
		sb.append("date:").append("\"").append(text).append("\"");
		sb.append("}");
		
		resp.setCharacterEncoding("UTF-8");
		PrintWriter out = resp.getWriter();
		//停留一秒后,然后再向客户端输出。如果不停留,则浏览器会不停处理,可能崩溃。
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			//
		}
		//返回组装好的json字符串形式
		out.print(sb.toString());
		//Logger.getLogger(this.getClass().getName()).info("服务器段返回json的字符串为:\n"+sb.toString());
	}

}

 

SessionListener

public class SessionListening implements HttpSessionListener {

	@Override
	public void sessionCreated(HttpSessionEvent se) {
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		//从session中取得登陆的用户名
		Object o = se.getSession().getAttribute("name");
		//如果未登录则返回
		if(o==null) return;
		String name = (String)o;
		//从ServletContext中获得登陆用户的容器
		Object ob = se.getSession().getServletContext().getAttribute("list");
		//如果容器为null,则返回
		if(ob==null) return;
		List<String> list = (List<String>)ob;
		//从list中移除当前用户
		if(list.contains(name)) list.remove(name);
		//把改变后的list重新放入ServletContext
		se.getSession().getServletContext().setAttribute("list", list);
		Logger.getLogger("session").info("用户"+name+",退出!");
		
	}

}

 

web.xml

  <listener>
  		<listener-class>test.servlet.SessionListening</listener-class>
  </listener>
  
  <servlet>
  		<servlet-name>login</servlet-name>
  		<servlet-class>test.servlet.LoginServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  		<servlet-name>login</servlet-name>
  		<url-pattern>/login</url-pattern>
  </servlet-mapping>
  <servlet>
  		<servlet-name>control</servlet-name>
  		<servlet-class>test.servlet.ControlServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  		<servlet-name>control</servlet-name>
  		<url-pattern>/control</url-pattern>
  </servlet-mapping>

 

虽然设置session的失效时间在5秒,但是总是在大概1分钟的时候调用sessionDestroyed方法。

以上为全部工程文件,

等待高手解答

分享到:
评论
3 楼 paker1989 2014-07-09  
session会在你设置的失效时间的基础上延迟1分钟才会真正失效。
2 楼 酷的飞上天空 2010-07-10  
tonyzzp 写道
我有个疑问
在sessionDestroyed方法里面还可以使用
session.getAttribute()方法吗?
这个时候session是否已经注销掉了呢?。


Notification that a session is about to be invalidated.
此为文档上面对这个方法的说明,此方法被调用时session尚未失效。

现在问题是sessionDestroyed方法,到底都在什么情况下被调用。
我一直向后台发送请求,为什么还会调用此方法
1 楼 tonyzzp 2010-07-08  
我有个疑问
在sessionDestroyed方法里面还可以使用
session.getAttribute()方法吗?
这个时候session是否已经注销掉了呢?。

相关推荐

    HttpSessionListener监听session的创建及销毁,实现在线用户统计

    用户登录时,调用LoginServlet,将用户名写入application的在线用户列表中,用户退出时,调用LogoutServlet,调用session.invalidate(),交给HttpSessionListener的sessionDestroyed()方法,将用户从在线列表中删除。...

    java 同一用户不能同时登陆问题

    public void sessionDestroyed(HttpSessionEvent event) { // HttpSession session = getSessionByID(UserVo); if (userIds != null && userIds.size()&gt;0) { for (String userid : userIds) { UserVo u ...

    前端Mock的使用,用于构造动态数据

    适用于前端开发,前端构建动态数据

    SQL语句的基本用法案例.pdf

    “SQL语句的基本用法案例”的文档,是一份非常实用的学习资料,为初学者和进阶者提供了丰富的SQL操作示例。通过这份文档,读者可以系统地了解SQL语言在数据库管理中的应用,掌握从创建数据库到删除数据库的整个流程。 文档开篇便介绍了如何创建一个新的数据库,并详细说明了选择数据库、创建表以及插入数据的具体步骤。这不仅为读者展示了SQL语句的基础用法,也为后续的查询、更新和删除操作打下了坚实的基础。 在查询数据部分,文档通过多个示例展示了SQL查询的灵活性和强大功能。无论是查询所有学生信息,还是根据特定条件筛选数据,都能通过简单的SQL语句实现。此外,文档还介绍了如何计算学生的总数、平均年龄等统计信息,以及如何对数据进行排序和限制结果。 除了基础的增删改查操作,文档还深入介绍了子查询、连接表以及窗口函数等高级用法。这些功能在实际应用中非常常见,能够帮助用户解决更为复杂的数据处理问题。 此外,文档还提供了清晰的代码示例和注释,使得读者能够轻松理解每个步骤的含义和目的。同时,文档的语言简洁明了,逻辑清晰,使得学习过程更加轻松愉快。

    node-v7.7.4-sunos-x86.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于QT+C++开发的球球大作战游戏+源码(毕业设计&课程设计&项目开发)

    基于QT+C++开发的球球大作战游戏+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于QT+C++开发的球球大作战游戏+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于QT+C++开发的球球大作战游戏+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于QT+C++开发的球球大作战游戏+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    浪潮CH5280H2海光服务器兼容vSphere集群服务方法

    hygon-vmware_patch_v2.7

    PHP开发虚拟资源在线交易平台程序源码 含多接口 支付功能.rar

    PHP开发虚拟资源在线交易平台程序源码 含多接口 支付功能.rarPHP开发虚拟资源在线交易平台程序源码 含多接口 支付功能.rar

    智慧工厂数字工厂三维数据可视化方案.pptx

    智慧工厂数字工厂三维数据可视化方案.pptx

    node-v7.3.0-linux-armv6l.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v6.11.2-sunos-x64.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v12.20.0-linux-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    计算机毕业设计参考 仿豆瓣电影小程序app 微信小程序源码下载

    数据库课程设计豆瓣电影小程序是一个专为电影爱好者设计的微信小程序,它以豆瓣电影数据库为基础,为用户提供了一个移动观影指南。该小程序结合了豆瓣丰富的电影资源和用户评价,允许用户在微信平台上快速浏览电影信息、阅读影评、查看评分,并参与到社区讨论中。 作为一个计算机毕业设计参考项目,豆瓣电影小程序不仅涉及到前端展示和用户交互设计,还涉及到后端数据接口的集成和处理。开发者需要掌握微信小程序的开发框架,熟悉前后端数据交互流程,并能够实现用户认证、数据缓存、内容展示等功能。 该小程序的设计与实现,可以培养学生在Web开发、数据库操作、API调用和用户体验设计等方面的实践能力。通过微信小程序源码下载,学生可以获取项目的基础代码,进行深入分析和学习,进而在此基础上进行创新和功能扩展,比如增加个性化推荐算法、社交分享功能等,以提升小程序的用户体验和实用性。 此项目适合作为计算机及相关专业学生的毕业设计选题,不仅因其紧密结合当前流行的移动应用开发趋势,更因其能够锻炼学生解决实际问题的能力,同时完成的项目作品也具有实际应用价值。

    客户关系管理(CRM).pdf

    客户关系管理(CRM).pdf

    自适应多引擎搜索单页源码.rar

    自适应多引擎搜索单页源码.rar自适应多引擎搜索单页源码.rar自适应多引擎搜索单页源码.rar

    node-v7.3.0-linux-x86.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于Python的木鸟民宿价格预测项目源代码+民宿信息爬虫

    在木鸟民宿平台作为房东发布房源或作为房客预订房源的过程中,信息采集涉: 房源信息:包括但不限于房源地址、房源照片、房间类型、房间布局、设施设备、床位数、可容纳人数、房源特色介绍、周边环境(如交通、餐饮、景点等 安装教程 pip install requests pip install parsel pip install tqdm 使用说明 命令行中cd到muniao-price-prediction/source_code路径,然后运行命令 python homestay_list.py 程序会生成一个名为homestay.csv的文件,里面是房源信息

    node-v10.13.0-win-x86.zip

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v8.1.1-x86.msi

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    计算机毕业设计参考 豆瓣电影小程序app 微信小程序源码下载

    豆瓣电影小程序是一个专为电影爱好者设计的微信小程序,它以豆瓣电影数据库为基础,为用户提供了一个移动观影指南。该小程序结合了豆瓣丰富的电影资源和用户评价,允许用户在微信平台上快速浏览电影信息、阅读影评、查看评分,并参与到社区讨论中。 作为一个计算机毕业设计参考项目,豆瓣电影小程序不仅涉及到前端展示和用户交互设计,还涉及到后端数据接口的集成和处理。开发者需要掌握微信小程序的开发框架,熟悉前后端数据交互流程,并能够实现用户认证、数据缓存、内容展示等功能。 该小程序的设计与实现,可以培养学生在Web开发、数据库操作、API调用和用户体验设计等方面的实践能力。通过微信小程序源码下载,学生可以获取项目的基础代码,进行深入分析和学习,进而在此基础上进行创新和功能扩展,比如增加个性化推荐算法、社交分享功能等,以提升小程序的用户体验和实用性。 此项目适合作为计算机及相关专业学生的毕业设计选题,不仅因其紧密结合当前流行的移动应用开发趋势,更因其能够锻炼学生解决实际问题的能力,同时完成的项目作品也具有实际应用价值。

Global site tag (gtag.js) - Google Analytics