简体   繁体   中英

Spring Boot : Count Page Views - Actuators

I have a requirement to count the views on each endpoint. The idea is to create one common Request Count Mapping for all endpoints which should return the view count based on a dynamically entred endpoint.

Let's say someone wants to check the view counts on http://localhost:8080/user/101 .

  1. RequestMappping path = /admin/count & RequestParam = url (Here /user/101)
  2. Then create the dynamic Request based on RequestParam http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/user/101
  3. Get and Return the Response of dynamic Request (JSON Object) and get the value of COUNT

I stuck on how to send a dynamic request to http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/user/101 and return the response of it and get the count value


@RequestMapping(path="/admin/count",method=RequestMethod.POST)
public JSONObject count(@RequestParam(name="url") final String url)//@PathVariable(name="url") final String url
{   
    String finalURL = "http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:" + url + "";
    return sendRequestToURL(finalURL);  
}

@RequestMapping(path="/{finalURL}",method=RequestMethod.GET)
public JSONObject sendRequestToURL(@PathVariable("finalURL") String url)
{
    //How to return the response Here
}

This is what I get when Directly fire the URL

GET: http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/user/101

  {
    "name": "http.server.requests",
    "description": null,
    "baseUnit": "seconds",
    "measurements": [
        {
            "statistic": "COUNT",
            "value": 1
        },
        {
            "statistic": "TOTAL_TIME",
            "value": 0.3229436
        },
        {
            "statistic": "MAX",
            "value": 0.3229436
        }
    ],
    "availableTags": [
        {
            "tag": "exception",
            "values": [
                "None"
            ]
        },
        {
            "tag": "method",
            "values": [
                "GET"
            ]
        },
        {
            "tag": "outcome",
            "values": [
                "SUCCESS"
            ]
        },
        {
            "tag": "status",
            "values": [
                "200"
            ]
        }
    ]
}

Environment:

    `spring boot 2.1.2.RELEASE`
    <java.version>1.8</java.version>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

So you want to encapsulate actuator/metrics with /admin/count

There are many ways and library for calling Rest API in Java

I will add the simplest one

Something like this

public JSONObject sendRequestToURL(@PathVariable("finalURL") String urlToRead)
{
      StringBuilder result = new StringBuilder();
      URL url = new URL(urlToRead);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("GET");
      BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
      String line;
      while ((line = rd.readLine()) != null) {
         result.append(line);
      }
      rd.close();
      return new JSONObject(result.toString());  // org.json
}

Edit 1:

You are almost there. Just need to parse String to JSONObject. Try this maybe

String strJson = result.toString().replace("\\\"","'");
JSONObject jo = new JSONObject(strJson.substring(1,json.length()-1));
return jo;

Edit 2:

I guess you have Spring Security in place.

And when you are calling an API internally, Spring is treating as an external call which requires Authentication.

As a workaround, you can exclude /actuator API from security context.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests()
     .antMatchers("/actuator*").permitAll()

     ...
}

or in XML

<security:http  auto-config="true"  use-expressions="true"   >
    <security:intercept-url pattern="/actuator*" access="permitAll"/>

    ...
</security:http>

And hopefully Spring security will ignore this URL and you will not get Login Form.

The idea is you will get the endPoint from user to display to show the view counts which will be done using @RequestParam. Based on the request endPoint create the URLtoMap according to your requirements

(ie methods, status, outcome, exception etc, eg http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/user/101&tag=method:GET).


@RequestMapping(path="/admin/count",method=RequestMethod.POST)
    public int count(@RequestParam(name="endPoint") final String endPoint) throws IOException, JSONException
    {
        final String URLtoMap = "http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:" + endPoint + "";
        return sendRequestToURL(URLtoMap);
    }

Now Based on the URLtoMap send Request using HttpURLConnection and get the output using BufferedReader . As I am using Spring Security I was redirected to Login Page. To solve the problem I have added antMatchers in SecurityConfig file as below. If you facing JSONException: Value of type java.lang.String cannot be converted to JSONObject then refer this

public int sendRequestToURL(@PathVariable("URLtoMap") String URLtoMap) throws IOException, JSONException
{
      int count = 0;
      StringBuilder result = new StringBuilder();
      URL url = new URL(URLtoMap);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("GET");
      BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
      String line;
      while ((line = rd.readLine()) != null) {
         result.append(line);
      }
      rd.close();

      try {
            JSONObject jsonObject =new JSONObject(result.toString().replace("\"", "")); 
            JSONObject jsonCountObject = new JSONObject(jsonObject.getJSONArray("measurements").get(0).toString());
            count =(int) jsonCountObject.get("value");
        }
        catch (JSONException e) {
            e.printStackTrace();
        }

      return count;
}

SecurityConfig

@Override
        protected void configure(HttpSecurity http) throws Exception{

             http
             .csrf().disable()
             .authorizeRequests().antMatchers("/login").permitAll()
             .antMatchers(HttpMethod.GET,"/actuator/**").permitAll() 
             .antMatchers(HttpMethod.POST,"/actuator/**").permitAll() 
}

pom.xml

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
  <groupId>org.json</groupId>
  <artifactId>json</artifactId>
  <version>20090211</version>
</dependency>

Import the Correct Packages

import org.json.JSONException;
import org.json.JSONObject;
import java.net.URL;
import java.net.HttpURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM