`
丁丁豆
  • 浏览: 74651 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论
阅读更多
你可以在两种情况下使用本文:
·学习过滤器的功用,
·作为你写过滤器时的辅助。
我将从几个简单的例子开始然后继续更多高级的过滤器。最后,我将向你介绍我为了支持多路请求而写的一个文件上传过滤器。
 
Servlet 过滤器
也许你还不熟悉情况,一个过滤器是一个可以传送请求或修改响应的对象。过滤器并不是servlet,他们并不实际创建一个请求。他们是请求到达一个servlet前的预处理程序,和/或响应离开servlet后的后处理程序。就像你将在后面的例子中看到的,一个过滤器能够:
·在一个servlet被调用前截获该调用
·在一个servlet被调用前检查请求
·修改在实际请求中提供了可定制请求对象的请求头和请求数据
·修改在实际响应中提供了可定制响应对象的响应头和响应数据
·在一个servlet被调用之后截获该调用
 
    一个过滤器以作用于一个或一组servlet,零个或多个过滤器能过滤一个或多个servlet。一个过滤器需要实现java.servlet.Filter接口,并定义它的三个方法:
1.              void init(FilterConfig config) throws ServletException:在过滤器执行service前被调用,以设置过滤器的配置对象。
2.              void destroy();在过滤器执行service后被调用。
3.              Void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) throws IOException,ServletException;执行实际的过滤工作。
 
服务器调用一次init(FilterConfig)以为服务准备过滤器,然后在请求需要使用过滤器的任何时候调用doFilter()。FilterConfig接口检索过滤器名、初始化参数以及活动的servlet上下文。服务器调用destory()以指出过滤器已结束服务。过滤器的生命周期和servelt的生命周期非常相似 ——在Servlet API 2.3 最终发布稿2号 中最近改变的。先前得用setFilterConfig(FilterConfig)方法来设置生命周期。
 
doFilter()方法中,每个过滤器都接受当前的请求和响应,而FilterChain包含的过滤器则仍然必须被处理。doFilter()方法中,过滤器可以对请求和响应做它想做的一切。(就如我将在后面讨论的那样,通过调用他们的方法收集数据,或者给对象添加新的行为。)过滤器调用
chain.doFilter()将控制权传送给下一个过滤器。当这个调用返回后,过滤器可以在它的doFilter()方法的最后对响应做些其他的工作;例如,它能记录响应的信息。如果过滤器想要终止请求的处理或得对响应的完全控制,则他可以不调用下一个过滤器。
 
循序渐进
如果想要真正理解过滤器,则应该看它们在实际中的应用。我们将看到的第一个过滤器是简单而有用的,它记录了所有请求的持续时间。在Tomcat 4.0发布中被命名为ExampleFilter。代码如下:
java 代码
  1. import java.io.*;   
  2. import javax.servlet.*;   
  3. import javax.servlet.http.*;   
  4.     
  5. public class TimerFilter implements Filter {   
  6.     
  7.  private FilterConfig config = null;   
  8.     
  9.  public void init(FilterConfig config) throws ServletException {   
  10.     this.config = config;   
  11.  }   
  12.     
  13.  public void destroy() {   
  14.     config = null;   
  15.  }   
  16.     
  17.  public void doFilter(ServletRequest request, ServletResponse response,   
  18.                      FilterChain chain) throws IOException, ServletException {   
  19.     long before = System.currentTimeMillis();   
  20.     chain.doFilter(request, response);   
  21.     long after = System.currentTimeMillis();   
  22.     
  23.     String name = "";   
  24.     if (request instanceof HttpServletRequest) {   
  25.       name = ((HttpServletRequest)request).getRequestURI();   
  26.     }   
  27.     config.getServletContext().log(name + ": " + (after - before) + "ms");   
  28.  }   
  29. }   
 
当服务器调用init()时,过滤器用config变量来保存配置类的引用,这将在后面的doFilter()方法中被使用以更改ServletContext。当调用doFilter()时,过滤器计算请求发出到该请求执行完毕之间的时间。该过滤器很好的演示了请求之前和之后的处理。注意doFilter()方法的参数并不是HTTP对象,因此要调用HTTP专用的getRequestURI()方法时必须将request转化为HttpServletRequest类型。
 
使用此过滤器,你还必须在web.xml文件中用<filter></filter>标签部署它,见下:
  1. <filter>  
  2.        <filter-name>timerFilterfilter-name>  
  3.        <filter-class>TimerFilterfilter-class>  
  4. /filter>  
 
这将通知服务器一个叫timerFiter的过滤器是从TimerFiter类实现的。你可以使用确定的URL模式或使用<filter-mapping></filter-mapping>标签命名的servelt 来注册一个过滤器,如:
  1. <filter-mapping>  
  2.     <filter-name>timerFilterfilter-name>  
  3.     <url-pattern>/*url-pattern>  
  4. filter-mapping>  
 
这种配置使过滤器操作所有对服务器的请求(静态或动态),正是我们需要的计时过滤器。如果你连接一个简单的页面,记录输出可能如下:
2001-05-25 00:14:11 /timer/index.html: 10ms
 
Tomcat 4.0 beta 5中,你可以在server_root/logs/下找到该记录文件。
 
此过滤器的WAR文件从此下载:
 
谁在你的网站上?他们在做什么?
我们下一个过滤器是由OpenSymphony成员写的clickstream过滤器。这个过滤器跟踪用户请求(比如:点击)和请求队列(比如:点击流)以向网络管理员显示谁在她的网站上以及每个用户正在访问那个页面。这是个使用LGPL的开源库。
 
clickstream包中你将发现一个捕获请求信息的ClickstreamFilter类,一个像操作结构一样的Clickstream类以保存数据,以及一个保存会话和上下文事件的ClickstreamLogger类以将所有东西组合在一起。还有个BotChecker类用来确定客户端是否是一个机器人(简单的逻辑,像“他们是否是从robots.txt来的请求?”)。该包中提供了一个clickstreams.jsp摘要页面和一个viewstream.jsp详细页面来查看数据。
 
我们先看ClickstreamFilter类。所有的这些例子都做了些轻微的修改以格式化并修改了些可移植性问题,这我将在后面将到。
  1. import java.io.IOException;   
  2. import javax.servlet.*;   
  3. import javax.servlet.http.*;   
  4.     
  5. public class ClickstreamFilter implements Filter {   
  6.  protected FilterConfig filterConfig;   
  7.  private final static String FILTER_APPLIED = "_clickstream_filter_applied";   
  8.     
  9.  public void init(FilterConfig config) throws ServletException {   
  10.     this.filterConfig = filterConfig;   
  11.  }   
  12.     
  13.  public void doFilter(ServletRequest request, ServletResponse response,   
  14.                    FilterChain chain) throws IOException, ServletException {   
  15.     // 确保该过滤器在每次请求中只被使用一次   
  16.     if (request.getAttribute(FILTER_APPLIED) == null) {   
  17.       request.setAttribute(FILTER_APPLIED, Boolean.TRUE);   
  18.       HttpSession session = ((HttpServletRequest)request).getSession();   
  19.       Clickstream stream = (Clickstream)session.getAttribute("clickstream");   
  20.       stream.addRequest(((HttpServletRequest)request));   
  21.     }   
  22.     
  23.     // 传递请求   
  24.     chain.doFilter(request, response);   
  25.  }   
  26.     
  27.  public void destroy() { }   
  28. }   
 
doFilter()方法取得用户的session,从中获取Clickstream,并将当前请求数据加到Clickstream中。其中使用了一个特殊的FILTER_APPLIED标记属性来标注此过滤器是否已经被当前请求使用(可能会在请求调度中发生)并且忽略所有其他的过滤器行为。你可能疑惑过滤器是怎么知道当前session中有clickstream属性。那是因为ClickstreamLogger在会话一开始时就已经设置了它。ClickstreamLogger代码:
  1. import java.util.*;   
  2. import javax.servlet.*;   
  3. import javax.servlet.http.*;   
  4.     
  5. public class ClickstreamLogger implements ServletContextListener,   
  6.                                           HttpSessionListener {   
  7.  Map clickstreams = new HashMap();   
  8.     
  9.  public ClickstreamLogger() { }   
  10.     
  11.  public void contextInitialized(ServletContextEvent sce) {   
  12.     sce.getServletContext().setAttribute("clickstreams", clickstreams);   
  13.  }   
  14.     
  15.  public void contextDestroyed(ServletContextEvent sce) {   
  16.     sce.getServletContext().setAttribute("clickstreams"null);   
  17.  }   
  18.     
  19.  public void sessionCreated(HttpSessionEvent hse) {   
  20.     HttpSession session = hse.getSession();   
  21.     Clickstream clickstream = new Clickstream();   
  22.     session.setAttribute("clickstream", clickstream);   
  23.     clickstreams.put(session.getId(), clickstream);   
  24.  }   
  25.     
  26.  public void sessionDestroyed(HttpSessionEvent hse) {   
  27.     HttpSession session = hse.getSession();   
  28.     Clickstream stream = (Clickstream)session.getAttribute("clickstream");   
  29.     clickstreams.remove(session.getId());   
  30.  }   
  31. }   
 
     logger(记录器)获取应用事件并将使用他们将所有东西帮定在一起。当context创建中,logger在context中放置了一个共享的流map。这使得clickstream.jsp页面知道当前活动的是哪个流。而在context销毁中,logger则移除此map。当一个新访问者创建一个新的会话时,logger将一个新的Clickstream实例放入此会话中并将此Clickstream加入到中心流map中。在会话销毁时,由logger从中心map中移除这个流。
 
下面的web.xml部署描述片段将所有东西写在一块:
  1. <filter>  
  2.          <filter-name>clickstreamFilterfilter-name>  
  3.          <filter-class>ClickstreamFilterfilter-class>  
  4.     filter>  
  5.     <filter-mapping>  
  6.          <filter-name>clickstreamFilterfilter-name>  
  7.          <url-pattern>*.jspurl-pattern>  
  8.     filter-mapping>  
  9.     <filter-mapping>  
  10.          <filter-name>clickstreamFilterfilter-name>  
  11.          <url-pattern>*.htmlurl-pattern>  
  12.     filter-mapping>  
  13.     <listener>  
  14.          <listener-class>ClickstreamLoggerlistener-class>  
  15.     listener>  
   
 
    这注册了ClickstreamFilter并设置其处理*.jsp和*.html来的请求。这也将ClickstreamLogger注册为一个监听器以在应用事件发生时接受他们。
 
   两个JSP页面从会话中取clickstream数据和context对象并使用HTML界面来显示当前状态。下面的clickstream.jsp文件显示了个大概:
js 代码
  1. <%@ page import="java.util.*" %>   
  2. <%@ page import="Clickstream" %>   
  3. <%   
  4. Map clickstreams = (Map)application.getAttribute("clickstreams");   
  5. String showbots = "false";   
  6. if (request.getParameter("showbots") != null) {   
  7.   if (request.getParameter("showbots").equals("true"))   
  8.     showbots = "true";   
  9.   else if (request.getParameter("showbots").equals("both"))   
  10.     showbots = "both";   
  11. }   
  12. %>   
  13. <font face="Verdana" size="-1">   
  14. <h1>All Clickstreams</h1>   
  15. <a href="clickstreams.jsp?showbots=false">No Bots</a> |   
  16. <a href="clickstreams.jsp?showbots=true">All Bots</a> |   
  17. <a href="clickstreams.jsp?showbots=both">Both</a> <p>   
  18. <% if (clickstreams.keySet().size() == 0) { %>   
  19.         No clickstreams in progress   
  20. <% } %>   
  21. <%   
  22. Iterator it = clickstreams.keySet().iterator();   
  23. int count = 0;   
  24. while (it.hasNext()) {   
  25.   String key = (String)it.next();   
  26.   Clickstream stream = (Clickstream)clickstreams.get(key);   
  27.   if (showbots.equals("false") && stream.isBot()) {   
  28.     continue;   
  29.   }else if (showbots.equals("true") && !stream.isBot()) {   
  30.     continue;   
  31.   }   
  32.   count++;   
  33.   try {   
  34. %>   
  35.   
  36. <%= count %>.    
  37. <a href="viewstream.jsp?sid=<%= key %>"><b>   
  38. <%= (stream.getHostname() != null && !stream.getHostname().equals("") ?   
  39.      stream.getHostname() : "Stream") %>   
  40. </b></a> <font size="-1">[<%= stream.getStream().size() %> reqs]</font><br>   
  41.   
  42. <%   
  43.   }catch (Exception e) {   
  44. %>   
  45.   An error occurred - <%= e %><br>   
  46. <%   
  47.   }   
  48. }   
  49. %>  

WEB-INF/classes下,将JSP文件放到Web应用路径下,按帮助修改web.xml文件。为防止在这些工作前的争论,你可以从这个包很容易从OpenSymphony下载并安装。将Java文件编译并放在

 
为能让此过滤器能在Tomcat 4.0 beta 5下工作,我发现我不得不做一些轻微的改动。我做的改动显示了一些在servlet和过滤器的可移植性中通常容易犯的错误,所以我将他们列在下面:
·我不得不将在JSP中添加一个额外的导入语句:<!---->。在Java中你并不需要导入在同一包下的类,而在服务器上JSP被编译到默认包中,你并不需要这句导入行。但在像Tomcat这样的服务器上,JSP被编译到一个自定义的包中,你不得不明确地从默认包中导入类。
·我不得不将 <listener></listener> 元素移动到web.xml文件中的<filter></filter>和<filter-mapping></filter-mapping>元素之后,就像部署描述DTD要求的那样。并不是所有服务器对元素都要求固定的顺序。但Tomcat必须要。
·我不得不将web.xml中的映射由/*.html和/*.jsp改成正确的*.html和*.jsp。一些服务器会忽略开头的/,但Tomcat强硬的规定开头不能有/。
·最后,我得将ClickstreamFilter类升级到最新的生命周期API,将setFilterConfig()改成新的init()和destory()方法。
 
可下载的WAR文件已经包含了这些修改并能通过服务器在包外运行,虽然我并没有广泛的进行测试。
 
压缩响应
第三个过滤器是自动压缩响应输出流,以提高带宽利用率并提供一个很好的包装响应对象的示例。这个过滤器是由来自SUN的Amy Roh编写的,他为Tomcat 4.0 的“examples”Web程序做出过贡献。你将从webapps/examples/WEB-INF/classes/compressionFilters下找到原始代码。这里的例子代码以及WAR下的都已经为了更清晰和更简单而编辑过了。
 
CompressionFilter类的策略是检查请求头以判定客户端是否支持压缩,如果支持,则将响应对象用自定义的响应来打包,它的getOutputStream()和getWriter()方法已经被定义为可以利用压缩过的输出流。使用过滤器允许如此简单而有效的解决问题。
 
我们将从init()开始看代码:
  1. public void init(FilterConfig filterConfig) {   
  2.    config = filterConfig;   
  3.    compressionThreshold = 0;   
  4.    if (filterConfig != null) {   
  5.      String str = filterConfig.getInitParameter("compressionThreshold");   
  6.      if (str != null) {   
  7.        compressionThreshold = Integer.parseInt(str);   
  8.      }   
  9.      else {   
  10.        compressionThreshold = 0;   
  11.      }   
  12.    }   
  13. }  
 
注意在检索请求头前必须把request转化为HttpServletRequest,就想在第一个例子里那样。过滤器使用wrapper类CompressResponseWrapper,一个从
HttpServletResponseWrapper类继承下来的自定义类。这个wrapper的代码相对比较简单:
  1. public class CompressionResponseWrapper extends HttpServletResponseWrapper {   
  2.     
  3.  protected ServletOutputStream stream = null;   
  4.  protected PrintWriter writer = null;   
  5.  protected int threshold = 0;   
  6.  protected HttpServletResponse origResponse = null;   
  7.     
  8.  public CompressionResponseWrapper(HttpServletResponse response) {   
  9.     super(response);   
  10.     origResponse = response;   
  11.  }   
  12.     
  13.  public void setCompressionThreshold(int threshold) {   
  14.     this.threshold = threshold;   
  15.  }   
  16.     
  17.  public ServletOutputStream createOutputStream() throws IOException {   
  18.     return (new CompressionResponseStream(origResponse));   
  19.  }   
  20.     
  21.  public ServletOutputStream getOutputStream() throws IOException {   
  22.     if (writer != null) {   
  23.       throw new IllegalStateException("getWriter() has already been " +   
  24.                                       "called for this response");   
  25.     }   
  26.     
  27.     if (stream == null) {   
  28.       stream = createOutputStream();   
  29.     }   
  30.     ((CompressionResponseStream) stream).setCommit(true);   
  31.     ((CompressionResponseStream) stream).setBuffer(threshold);   
  32.     return stream;   
  33.  }   
  34.     
  35.  public PrintWriter getWriter() throws IOException {   
  36.     if (writer != null) {   
  37.       return writer;   
  38.     }   
  39.     
  40.     if (stream != null) {   
  41.       throw new IllegalStateException("getOutputStream() has already " +   
  42.                                       "been called for this response");   
  43.     }   
  44.     
  45.     stream = createOutputStream();   
  46.     ((CompressionResponseStream) stream).setCommit(true);   
  47.     ((CompressionResponseStream) stream).setBuffer(threshold);   
  48.     writer = new PrintWriter(stream);   
  49.     return writer;   
  50.  }   
  51. }   
 
所有调用getOutputStream() 或者getWriter()都返回一个使用
CompressResponseStream类的对象。CompressionResponseStrteam类没有显示在这个例子中,因为它继承于ServletOutputStream并使用java.util.zip.GZIPOutputStream类来压缩流。
 
Tomcat的”examples”Web程序中已经预先配置了这个压缩过滤器并加载了一个示例servlet。示例servlet响应/CompressionTestURL(确定先前的路径是/examples)。使用我制作的有用的WAR文件,你可以用/servlet/compressionTest(再次提醒,别忘了适当的前导路径)访问此测试servlet。你可以使用如下的web.xml片段来配置这个测试:
<filter></filter>
xml 代码
  1. <filter>  
  2.     <filter-name>compressionFilterfilter-name>  
  3.     <filter-class>CompressionFilterfilter-class>  
  4.     <init-param>  
  5.       <param-name>compressionThresholdparam-name>  
  6.       <param-value>10param-value>  
  7.     init-param>  
  8. filter>  
  9.   
  10. <filter-mapping>  
  11.     <filter-name>compressionFilterfilter-name>  
  12.     <servlet-name>compressionTestservlet-name>  
  13. filter-mapping>  
  14.   
  15. <servlet>  
  16.   <servlet-name>  
  17.     compressionTest   
  18.   servlet-name>  
  19.   <servlet-class>  
  20.     CompressionTestServlet   
  21.   servlet-class>  
  22. servlet>  
 
CompressionTestServlet(这里没有显示)输出压缩是否可用,如果可用,则输出压缩响应成功!
分享到:
评论

相关推荐

    jsp servlet中的过滤器Filter配置总结

    当你想让一个Filter作用于应用中的所有资源时,你需要在`web.xml`配置文件中定义一个Filter,并设置`&lt;url-pattern&gt;`为`/*`。这样,任何通过HTTP请求访问的应用资源都会先经过这个Filter。例如: ```xml &lt;filter&gt;...

    MATLAB-BOXFILTER

    MATLAB 滤波 用于图像处理 噪声处理 图像增强等

    Java-filter测试程序

    在这个"Java-filter测试程序"中,我们可以深入理解Filter的工作原理及其在实际应用中的作用。 Filter在Java Web应用程序中扮演着中间件的角色,它可以对请求进行预处理,也可以对响应进行后处理。例如,我们可以...

    java 中如何使用filter过滤器

    这里,`filter-name`是过滤器的唯一标识,`filter-class`是实现Filter接口的类全名,`url-pattern`则定义了过滤器作用的URL路径。 ### 3. 编写Filter 创建一个类实现Filter接口,并重写上述三个方法。例如,我们...

    matlab中filter conv impz用法

    在MATLAB中,filter、conv和impz是三个非常重要的函数,它们主要用于信号处理和系统分析。下面将详细解释这三个函数的用法及其在不同情况下的应用。 首先,`filter`函数是基于离散时间差分方程来模拟信号处理的。它...

    用 Filter 作为控制器的 MVC

    首先,理解Filter在Java Web中的作用至关重要。Filter是Servlet规范的一部分,它允许开发者在请求到达目标Servlet或JSP之前以及响应离开之后对其进行拦截和处理。通过实现javax.servlet.Filter接口,我们可以定义...

    Filter1源代码

    Filter在Java Web中的主要作用包括:数据校验、安全控制、字符编码转换、性能监控等。它的工作基于Servlet规范中的Filter链,每个Filter可以按需设置拦截规则,并将请求传递给下一个Filter或直接转发到目标Servlet。...

    Filter简介和工作原理

    Filter的主要作用是在Servlet处理请求之前和之后进行一系列定制化的操作,比如身份验证、日志记录、数据压缩、编码转换等。 **Filter的工作原理** Filter的工作机制基于Filter Chain(过滤器链)。在web应用中,当...

    利用Filter拦截用户登录

    总的来说,Filter在Web应用中起着至关重要的作用,它们可以用来拦截、修改和增强请求与响应。在这个示例中,我们展示了如何利用Filter来保护资源,确保只有已登录的用户才能访问特定的页面,从而提高应用的安全性。...

    filter pro 有源滤波器设计软件 TI公司

    **Filter Pro 有源滤波器设计...结合TI的丰富IC资源,Filter Pro使得复杂滤波器设计变得简单易行,对于提升电子产品的信号处理能力具有重要作用。无论是初学者还是经验丰富的设计师,都可以从中受益,提高工作效率。

    Bloom Filter概念和原理

    Bloom Filter是一种非常有用且高效的数据结构,在许多场景中都能发挥重要作用。通过合理设计Bloom Filter的参数,可以在保持较低误报率的同时实现存储空间的有效利用。随着技术的发展,Bloom Filter也在不断地演变和...

    Intellij IDEA中使用maven filter遇到的问题

    在IntelliJ IDEA中使用Maven Filter功能时,开发者可能会遇到一些挑战,这些挑战主要涉及到配置、资源过滤以及与项目构建的交互。首先,我们来深入理解Maven Filter及其在IntelliJ IDEA中的应用。 Maven Filter是...

    java中的filter

    通过上述介绍,我们可以看出`Filter`在Java Web开发中的重要作用,它为我们提供了灵活的请求处理机制,使得我们能够方便地实现诸如安全控制、性能优化等高级功能。在实际项目中,合理利用`Filter`可以极大地提高应用...

    RF_Filter.rar_RF-FILT_RFFilter怎么用_RF_Filt_rf filt_rffilter

    4. **射频前端**:在射频接收机前端,滤波器起到保护后级电路、防止过载和提高选择性的作用。 总之,"RF-FILT_RFFilter"是一款强大且实用的射频滤波器设计工具,通过其灵活的参数设定和直观的仿真结果,可以帮助...

    简单的servlet+Filter的例子

    Servlet和Filter是Java Web开发中的两个重要概念,它们在构建动态Web应用程序中起着关键作用。Servlet是一种Java类,用于扩展服务器的功能,处理客户端(如浏览器)的请求,并向其发送响应。而Filter则是在Servlet...

    spring MVC所需jar包和filter的配置

    在开发基于Spring MVC的Web应用程序时,正确配置所需的jar包和Filter是至关...理解每个组件的作用和配置方式,能够帮助开发者更好地管理和优化应用。通过提供的文件,你可以参考其中的说明,进一步完善自己的项目配置。

    Java_Filter过滤机制详解.doc

    Filter技术是Servlet 2.3版本中新引入的一个功能,它的主要作用在于对Web应用中的请求和响应进行预处理和后处理。Filter本身并不是一个Servlet,因此它不能直接生成响应,但它可以在请求到达目标Servlet之前对其进行...

    一个支持读取H264视频流(.264)的Source Filter

    Directshow source filter是一种在DirectShow框架中用于读取和处理媒体数据的组件,它在多媒体播放和处理系统中起着核心作用。在这个特定的情况下,我们关注的是一个专门设计用来读取H264视频流的source filter。H...

    Duanxx的OpenCV学习:filter2D使用说明

    ### Duanxx的OpenCV学习:filter2D使用说明 #### 概述 在计算机视觉领域,OpenCV(开源计算机视觉库)是一个广泛使用的库,它提供了大量的算法和功能来处理图像和视频数据。其中,`filter2D`函数是一个非常重要的...

    Filter以及Interceptor的区别

    2. **作用范围**:Filter的作用范围是全局的,它可以拦截所有符合配置路径的请求。 3. **配置**:Filter的配置是在`web.xml`文件中进行的,通过`&lt;filter&gt;`和`&lt;filter-mapping&gt;`元素来实现。 4. **执行顺序**:多个...

Global site tag (gtag.js) - Google Analytics