個人利用のためのインターネット速度テストツールの紹介

皆さん、こんにちは！今日は、私が個人利用のために開発したインターネット速度テストツールを紹介したいと思います。このツールは、ユーザーのインターネット接続速度を簡単にテストできるように設計されています。また、リンクを皆さんと共有します。以下に、コードとその動作について詳しく説明します。

[SpeedTest] (https://speedtest.tehito.com)

![Screenshot 745.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801337/145470d6-39d4-5c0e-708f-e183078a3e1b.png)

### コードの説明

```speedtest.js```

```
// speedtest.js

var worker = new Worker('speedtest_worker.js');
worker.onmessage = function (event) {
    if (event.data.type === 'result') {
        // 結果の処理
        console.log('ダウンロード速度: ' + event.data.downloadSpeed + ' Mbps');
        console.log('アップロード速度: ' + event.data.uploadSpeed + ' Mbps');
    }
};

function startSpeedtest() {
    worker.postMessage('start');
}
```
このスクリプトは、Webワーカー（speedtest_worker.js）を作成し、速度テストの結果を処理します。startSpeedtest関数は、ワーカーにメッセージを送り、速度テストを開始します。

```speedtest_worker.js```

```
// speedtest_worker.js

self.onmessage = function (event) {
    if (event.data === 'start') {
        // ダウンロード速度とアップロード速度を計算するコード
        var downloadSpeed = calculateDownloadSpeed();
        var uploadSpeed = calculateUploadSpeed();
        self.postMessage({
            type: 'result',
            downloadSpeed: downloadSpeed,
            uploadSpeed: uploadSpeed
        });
    }
};

function calculateDownloadSpeed() {
    // ダウンロード速度を計算するロジック
    return Math.random() * 100; // プレースホルダーデータ
}

function calculateUploadSpeed() {
    // アップロード速度を計算するロジック
    return Math.random() * 100; // プレースホルダーデータ
}
```
このスクリプトは、メインスクリプトからメッセージを受け取ったときにダウンロード速度とアップロード速度を計算し、その結果をメインスクリプトに送信します。速度の計算はここではプレースホルダーデータを使用しています。

```getIP.php```

```
<?php

error_reporting(0);

define('API_KEY_FILE', 'getIP_ipInfo_apikey.php');
define('SERVER_LOCATION_CACHE_FILE', 'getIP_serverLocation.php');

require_once 'getIP_util.php';

function getLocalOrPrivateIpInfo($ip)
{
    if ('::1' === $ip) {
        return 'localhost IPv6 access';
    }
    if (stripos($ip, 'fe80:') === 0) {
        return 'link-local IPv6 access';
    }
    if (strpos($ip, '127.') === 0) {
        return 'localhost IPv4 access';
    }
    if (strpos($ip, '10.') === 0) {
        return 'private IPv4 access';
    }
    if (preg_match('/^172\.(1[6-9]|2\d|3[01])\./', $ip) === 1) {
        return 'private IPv4 access';
    }
    if (strpos($ip, '192.168.') === 0) {
        return 'private IPv4 access';
    }
    if (strpos($ip, '169.254.') === 0) {
        return 'link-local IPv4 access';
    }
    return null;
}

function getIpInfoTokenString()
{
    if (!file_exists(API_KEY_FILE) || !is_readable(API_KEY_FILE)) {
        return '';
    }

    require API_KEY_FILE;

    if (empty($IPINFO_APIKEY)) {
        return '';
    }

    return '?token=' . $IPINFO_APIKEY;
}

function getIspInfo($ip)
{
    $json = file_get_contents('https://ipinfo.io/' . $ip . '/json' . getIpInfoTokenString());
    if (!is_string($json)) {
        return null;
    }

    $data = json_decode($json, true);
    if (!is_array($data)) {
        return null;
    }

    return $data;
}

function getIsp($rawIspInfo)
{
    if (is_array($rawIspInfo)) {
        if (array_key_exists('org', $rawIspInfo) && is_string($rawIspInfo['org']) && !empty($rawIspInfo['org'])) {
            return preg_replace('/AS\\d+\\s/', '', $rawIspInfo['org']);
        }

        if (array_key_exists('asn', $rawIspInfo) && is_array($rawIspInfo['asn']) && !empty($rawIspInfo['asn']) && array_key_exists('name', $rawIspInfo['asn']) && is_string($rawIspInfo['asn']['name'])) {
            return $rawIspInfo['asn']['name'];
        }
    }

    return 'Unknown ISP';
}

function getServerLocation()
{
    $serverLoc = null;
    if (file_exists(SERVER_LOCATION_CACHE_FILE) && is_readable(SERVER_LOCATION_CACHE_FILE)) {
        require SERVER_LOCATION_CACHE_FILE;
    }
    if (is_string($serverLoc) && !empty($serverLoc)) {
        return $serverLoc;
    }

    $json = file_get_contents('https://ipinfo.io/json' . getIpInfoTokenString());
    if (!is_string($json)) {
        return null;
    }

    $details = json_decode($json, true);
    if (!is_array($details) || !array_key_exists('loc', $details) || !is_string($details['loc']) || empty($details['loc'])) {
        return null;
    }

    $serverLoc = $details['loc'];
    $cacheData = "<?php\n\n\$serverLoc = '" . addslashes($serverLoc) . "';\n";
    file_put_contents(SERVER_LOCATION_CACHE_FILE, $cacheData);

    return $serverLoc;
}

function distance($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo)
{
    $rad = M_PI / 180;
    $theta = $longitudeFrom - $longitudeTo;
    $dist = sin($latitudeFrom * $rad) * sin($latitudeTo * $rad) + cos($latitudeFrom * $rad) * cos($latitudeTo * $rad) * cos($theta * $rad);
    return acos($dist) / $rad * 60 * 1.853;
}

function getDistance($rawIspInfo)
{
    if (!is_array($rawIspInfo) || !array_key_exists('loc', $rawIspInfo) || !isset($_GET['distance']) || !in_array($_GET['distance'], ['mi', 'km'], true)) {
        return null;
    }

    $unit = $_GET['distance'];
    $clientLocation = $rawIspInfo['loc'];
    $serverLocation = getServerLocation();

    if (!is_string($serverLocation)) {
        return null;
    }

    return calculateDistance($clientLocation, $serverLocation, $unit);
}

function calculateDistance($clientLocation, $serverLocation, $unit)
{
    list($clientLatitude, $clientLongitude) = explode(',', $clientLocation);
    list($serverLatitude, $serverLongitude) = explode(',', $serverLocation);
    $dist = distance($clientLatitude, $clientLongitude, $serverLatitude, $serverLongitude);

    if ('mi' === $unit) {
        $dist /= 1.609344;
        $dist = round($dist, -1);
        if ($dist < 15) {
            $dist = '<15';
        }

        return $dist . ' mi';
    }

    if ('km' === $unit) {
        $dist = round($dist, -1);
        if ($dist < 20) {
            $dist = '<20';
        }

        return $dist . ' km';
    }

    return null;
}

function sendHeaders()
{
    header('Content-Type: application/json; charset=utf-8');

    if (isset($_GET['cors'])) {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: GET, POST');
    }

    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
    header('Cache-Control: post-check=0, pre-check=0', false);
    header('Pragma: no-cache');
}

function sendResponse($ip, $ipInfo = null, $distance = null, $rawIspInfo = null)
{
    $processedString = $ip;
    if (is_string($ipInfo)) {
        $processedString .= ' - ' . $ipInfo;
    }

    if (is_array($rawIspInfo) && array_key_exists('country', $rawIspInfo)) {
        $processedString .= ', ' . $rawIspInfo['country'];
    }

    if (is_string($distance)) {
        $processedString .= ' (' . $distance . ' distance to server)';
    }

    $response = [
        'rawIp' => $ip,
        'ip' => $processedString,
        'isp' => getIsp($rawIspInfo),
        'loc' => (is_array($rawIspInfo) && array_key_exists('loc', $rawIspInfo)) ? $rawIspInfo['loc'] : null,
        'distance' => $distance,
    ];

    echo json_encode($response);
}

sendHeaders();

$ip = getClientIp();

$ipInfo = getLocalOrPrivateIpInfo($ip);
if (is_string($ipInfo)) {
    sendResponse($ip, $ipInfo);
    return;
}

$rawIspInfo = getIspInfo($ip);

sendResponse($ip, $ipInfo, getDistance($rawIspInfo), $rawIspInfo);
?>
```

このスクリプトは、クライアントのIPアドレスを検出し、ipinfo.ioからISP情報を取得します。結果はJSON形式で返されます。

- `getLocalOrPrivateIpInfo`関数は、ローカルまたはプライベートIPアドレスに対して特別なメッセージを返します。

- `getIpInfoTokenString`関数は、APIキーが設定されている場合にトークン文字列を生成します。

- `getIspInfo関数は`、ipinfo.ioからISP情報を取得します。

- `getIsp関数は`、ISP名を解析します。

- `getServerLocation`関数は、サーバーの位置情報を取得します。

- `distance`関数は、2点間の距離を計算します。

- `getDistance`関数は、クライアントとサーバー間の距離を計算します。

- `sendHeaders`関数は、適切なヘッダーを送信します。

- `sendResponse`関数は、応答を生成し送信します。

```getIP_util.php```

```
<?php

/**
 * @return string
 */
function getClientIp() {
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        $ip = $_SERVER['HTTP_X_REAL_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        $ip = preg_replace('/,.*/', '', $ip); # ホストはコンマ区切りであり、クライアントは最初に位置します
    } else {
        $ip = $_SERVER['REMOTE_ADDR'];
    }

    return preg_replace('/^::ffff:/', '', $ip);
}
?>
```

このスクリプトは、クライアントのIPアドレスを取得する関数を提供します。

getClientIp関数は、クライアントのIPアドレスを取得します。複数のヘッダーをチェックし、最初に見つかった有効なIPアドレスを返します。

```garbage.php```

```
<?php

// 圧縮を無効にする
@ini_set('zlib.output_compression', 'Off');
@ini_set('output_buffering', 'Off');
@ini_set('output_handler', '');

/**
 * @return int
 */
function getChunkCount()
{
    if (
        !array_key_exists('ckSize', $_GET)
        || !ctype_digit($_GET['ckSize'])
        || (int) $_GET['ckSize'] <= 0
    ) {
        return 4;
    }

    if ((int) $_GET['ckSize'] > 1024) {
        return 1024;
    }

    return (int) $_GET['ckSize'];
}

/**
 * @return void
 */
function sendHeaders()
{
    header('HTTP/1.1 200 OK');

    if (isset($_GET['cors'])) {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: GET, POST');
    }

    // ファイルのダウンロードを示す
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename=random.dat');
    header('Content-Transfer-Encoding: binary');

    // キャッシュ設定: このリクエストをキャッシュしない
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
    header('Cache-Control: post-check=0, pre-check=0', false);
    header('Pragma: no-cache');
}

// 送信するデータ量を決定
$chunks = getChunkCount();

// データを生成
if (function_exists('random_bytes')) {
    $data = random_bytes(1048576);
} else {
    $data = openssl_random_pseudo_bytes(1048576);
}

// 1048576バイトのチャンクを送信
sendHeaders();
for ($i = 0; $i < $chunks; $i++) {
    echo $data;
    flush();
}
?>
```

このスクリプトは、ランダムなデータを生成し、クライアントに送信します。これは主にアップロード速度のテストに使用されます。

- `getChunkCount`関数は、送信するデータチャンクの数を決定します。
- `sendHeaders`関数は、適切なHTTPヘッダーを送信します。
- データ生成と送信のためのロジックが含まれています。

#### まとめ
このインターネット速度テストツールは、ユーザーが簡単に接続速度をテストできるように設計されています。各コード部分の理解が深まることで、ツールを最大限に活用できるようになります。