All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.matomo.java.tracking.Java8Sender Maven / Gradle / Ivy

The newest version!
/*
 * Matomo Java Tracker
 *
 * @link https://github.com/matomo/matomo-java-tracker
 * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
 */

package org.matomo.java.tracking;

import static java.util.Collections.singleton;
import static java.util.Objects.requireNonNull;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URI;
import java.net.URL;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * A {@link Sender} implementation that uses Java 8 {@link HttpURLConnection} to send requests to the Matomo.
 *
 * 

This implementation uses a thread pool to send requests asynchronously. The thread pool is configured using * {@link TrackerConfiguration#getThreadPoolSize()}. The thread pool uses daemon threads. This means that the JVM will * exit even if the thread pool is not shut down. * *

If you use a newer Java version, please use the newer Java implementation from the Matomo Java Tracker for * Java 11. */ @Slf4j @RequiredArgsConstructor class Java8Sender implements Sender { private static final TrustManager[] TRUST_ALL_MANAGERS = {new TrustingX509TrustManager()}; private static final HostnameVerifier TRUSTING_HOSTNAME_VERIFIER = new TrustingHostnameVerifier(); private final TrackerConfiguration trackerConfiguration; private final QueryCreator queryCreator; private final ExecutorService executorService; @Override @NonNull public CompletableFuture sendSingleAsync( @NonNull MatomoRequest request ) { return CompletableFuture.supplyAsync(() -> { sendSingle(request); return request; }, executorService); } @Override public void sendSingle( @NonNull MatomoRequest request ) { String authToken = AuthToken.determineAuthToken(null, singleton(request), trackerConfiguration); RequestValidator.validate(request, authToken); HttpURLConnection connection; URI apiEndpoint = trackerConfiguration.getApiEndpoint(); try { connection = openConnection(apiEndpoint .resolve(String.format("%s?%s", apiEndpoint.getPath(), queryCreator.createQuery(request, authToken))) .toURL()); } catch (MalformedURLException e) { throw new InvalidUrlException(e); } applyTrackerConfiguration(connection); setUserAgentProperty(connection, request.getHeaderUserAgent(), request.getHeaders()); addHeaders(connection, request.getHeaders()); addCookies(connection, request.getSessionId(), request.getCookies()); log.debug("Sending single request using URI {} asynchronously", apiEndpoint); try { connection.connect(); checkResponse(connection); } catch (IOException e) { throw new MatomoException("Could not send request via GET", e); } finally { connection.disconnect(); } } private HttpURLConnection openConnection(URL url) { HttpURLConnection connection; try { if (isEmpty(trackerConfiguration.getProxyHost()) || trackerConfiguration.getProxyPort() <= 0) { log.debug("Proxy host or proxy port not configured. Will create connection without proxy"); connection = (HttpURLConnection) url.openConnection(); } else { connection = openProxiedConnection(url); } } catch (IOException e) { throw new MatomoException("Could not open connection", e); } if (connection instanceof HttpsURLConnection) { applySslConfiguration((HttpsURLConnection) connection); } return connection; } private void applyTrackerConfiguration(HttpURLConnection connection) { connection.setUseCaches(false); if (trackerConfiguration.getConnectTimeout() != null) { connection.setConnectTimeout((int) trackerConfiguration.getConnectTimeout().toMillis()); } if (trackerConfiguration.getSocketTimeout() != null) { connection.setReadTimeout((int) trackerConfiguration.getSocketTimeout().toMillis()); } } private void setUserAgentProperty( @NonNull HttpURLConnection connection, @Nullable String headerUserAgent, @Nullable Map headers ) { String userAgentHeader = null; if ((headerUserAgent == null || headerUserAgent.trim().isEmpty()) && headers != null) { TreeMap caseInsensitiveMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); caseInsensitiveMap.putAll(headers); userAgentHeader = caseInsensitiveMap.get("User-Agent"); } if ((userAgentHeader == null || userAgentHeader.trim().isEmpty()) && ( headerUserAgent == null || headerUserAgent.trim().isEmpty()) && trackerConfiguration.getUserAgent() != null && !trackerConfiguration.getUserAgent().isEmpty()) { connection.setRequestProperty("User-Agent", trackerConfiguration.getUserAgent()); } } private void addHeaders(@NonNull HttpURLConnection connection, @Nullable Map headers) { if (headers != null) { for (Map.Entry header : headers.entrySet()) { connection.setRequestProperty(header.getKey(), header.getValue()); } } } private static void addCookies( HttpURLConnection connection, String sessionId, Map cookies ) { StringBuilder cookiesValue = new StringBuilder(); if (sessionId != null && !sessionId.isEmpty()) { cookiesValue.append("MATOMO_SESSID=").append(sessionId); if (cookies != null && !cookies.isEmpty()) { cookiesValue.append("; "); } } if (cookies != null) { for (Iterator> iterator = cookies.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); cookiesValue.append(entry.getKey()).append("=").append(entry.getValue()); if (iterator.hasNext()) { cookiesValue.append("; "); } } } if (cookiesValue.length() > 0) { connection.setRequestProperty("Cookie", cookiesValue.toString()); } } private void checkResponse(HttpURLConnection connection) throws IOException { int responseCode = connection.getResponseCode(); if (responseCode > 399) { if (trackerConfiguration.isLogFailedTracking()) { log.error("Received HTTP error code {} for URL {}", responseCode, connection.getURL()); } throw new MatomoException(String.format("Tracking endpoint responded with code %d", responseCode)); } } private static boolean isEmpty( @Nullable String str ) { return str == null || str.isEmpty() || str.trim().isEmpty(); } private HttpURLConnection openProxiedConnection( @NonNull @lombok.NonNull URL url ) throws IOException { requireNonNull(trackerConfiguration.getProxyHost(), "Proxy host must not be null"); if (trackerConfiguration.getProxyPort() <= 0) { throw new IllegalArgumentException("Proxy port must be configured"); } InetSocketAddress proxyAddress = new InetSocketAddress(trackerConfiguration.getProxyHost(), trackerConfiguration.getProxyPort()); Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddress); if (!isEmpty(trackerConfiguration.getProxyUsername()) && !isEmpty(trackerConfiguration.getProxyPassword())) { Authenticator.setDefault(new ProxyAuthenticator(trackerConfiguration.getProxyUsername(), trackerConfiguration.getProxyPassword() )); } log.debug("Using proxy {} on port {}", trackerConfiguration.getProxyHost(), trackerConfiguration.getProxyPort()); return (HttpURLConnection) url.openConnection(proxy); } private void applySslConfiguration( @NonNull @lombok.NonNull HttpsURLConnection connection ) { if (trackerConfiguration.isDisableSslCertValidation()) { try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, TRUST_ALL_MANAGERS, new SecureRandom()); connection.setSSLSocketFactory(sslContext.getSocketFactory()); } catch (Exception e) { throw new MatomoException("Could not disable SSL certification validation", e); } } if (trackerConfiguration.isDisableSslHostVerification()) { connection.setHostnameVerifier(TRUSTING_HOSTNAME_VERIFIER); } } @Override public void sendBulk( @NonNull @lombok.NonNull Iterable requests, @Nullable String overrideAuthToken ) { String authToken = AuthToken.determineAuthToken(overrideAuthToken, requests, trackerConfiguration); Collection queries = new ArrayList<>(); Map headers = new LinkedHashMap<>(); String headerUserAgent = null; String sessionId = null; Map cookies = null; for (MatomoRequest request : requests) { RequestValidator.validate(request, authToken); if (request.getHeaders() != null && !request.getHeaders().isEmpty()) { headers.putAll(request.getHeaders()); } if (headerUserAgent == null && request.getHeaderUserAgent() != null && !request .getHeaderUserAgent() .trim() .isEmpty()) { headerUserAgent = request.getHeaderUserAgent(); } queries.add(queryCreator.createQuery(request, null)); if (request.getSessionId() != null && !request.getSessionId().isEmpty()) { sessionId = request.getSessionId(); } if (request.getCookies() != null && !request.getCookies().isEmpty()) { cookies = request.getCookies(); } } sendBulk(queries, authToken, headers, headerUserAgent, sessionId, cookies); } private void sendBulk( @NonNull @lombok.NonNull Collection queries, @Nullable String authToken, Map headers, String headerUserAgent, String sessionId, Map cookies ) { if (queries.isEmpty()) { throw new IllegalArgumentException("Queries must not be empty"); } HttpURLConnection connection; try { connection = openConnection(trackerConfiguration.getApiEndpoint().toURL()); } catch (MalformedURLException e) { throw new InvalidUrlException(e); } preparePostConnection(connection); applyTrackerConfiguration(connection); setUserAgentProperty(connection, headerUserAgent, headers); addHeaders(connection, headers); addCookies(connection, sessionId, cookies); log.debug("Sending bulk request using URI {} asynchronously", trackerConfiguration.getApiEndpoint()); OutputStream outputStream = null; try { connection.connect(); outputStream = connection.getOutputStream(); outputStream.write(BulkRequest.builder().queries(queries).authToken(authToken).build().toBytes()); outputStream.flush(); checkResponse(connection); } catch (IOException e) { throw new MatomoException("Could not send requests via POST", e); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { // ignore } } connection.disconnect(); } } private static void preparePostConnection(HttpURLConnection connection) { try { connection.setRequestMethod("POST"); } catch (ProtocolException e) { throw new MatomoException("Could not set request method", e); } connection.setDoOutput(true); connection.setRequestProperty("Accept", "*/*"); connection.setRequestProperty("Content-Type", "application/json"); } @Override @NonNull public CompletableFuture sendBulkAsync( @NonNull Collection requests, @Nullable String overrideAuthToken ) { String authToken = AuthToken.determineAuthToken(overrideAuthToken, requests, trackerConfiguration); Map headers = new LinkedHashMap<>(); String headerUserAgent = findHeaderUserAgent(requests); String sessionId = findSessionId(requests); Map cookies = findCookies(requests); List queries = new ArrayList<>(requests.size()); for (MatomoRequest request : requests) { RequestValidator.validate(request, authToken); if (request.getHeaders() != null && !request.getHeaders().isEmpty()) { headers.putAll(request.getHeaders()); } queries.add(queryCreator.createQuery(request, null)); } return CompletableFuture.supplyAsync(() -> sendBulkAsync(queries, authToken, headers, headerUserAgent, sessionId, cookies), executorService); } @Nullable private Void sendBulkAsync( List queries, @Nullable String authToken, Map headers, String headerUserAgent, String sessionId, Map cookies ) { sendBulk(queries, authToken, headers, headerUserAgent, sessionId, cookies); return null; } @Nullable private static String findHeaderUserAgent(@NonNull Iterable requests) { for (MatomoRequest request : requests) { if (request.getHeaderUserAgent() != null && !request.getHeaderUserAgent().trim().isEmpty()) { return request.getHeaderUserAgent(); } } return null; } private String findSessionId(Iterable requests) { for (MatomoRequest request : requests) { if (request.getHeaderUserAgent() != null && !request.getHeaderUserAgent().trim().isEmpty()) { return request.getHeaderUserAgent(); } } return null; } private Map findCookies(Iterable requests) { for (MatomoRequest request : requests) { if (request.getCookies() != null && !request.getCookies().isEmpty()) { return request.getCookies(); } } return null; } @Override public void close() { ExecutorServiceCloser.close(executorService); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy