Merge pull request #39 from xtrime-ru/db

MadelineProto database support
This commit is contained in:
Alexander Pankratov 2020-06-09 00:46:15 +03:00 committed by GitHub
commit 7824c5bce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 414 additions and 73 deletions

View File

@ -1,3 +1,7 @@
# ENV file version
# Check for outdated .env files
VERSION=1
SERVER_ADDRESS=0.0.0.0
SERVER_PORT=9503
@ -17,4 +21,27 @@ LOGGER_LEVEL=2
TELEGRAM_PROXY_ADDRESS=
TELEGRAM_PROXY_PORT=
TELEGRAM_PROXY_USERNAME=
TELEGRAM_PROXY_PASSWORD=
TELEGRAM_PROXY_PASSWORD=
# DB
# memory
# Keep all data in memory/session file.
# This is default behavior
# mysql
# Keep part of data in mysql database.
# Reduce memory consumption and session size. Beta function.
# MariaDb + InnoDb Preffered.
# Tables and DB will be created if not exists.
# Change this type to convert session:
DB_TYPE=mysql
# MYSQL Settings. Required, when DB_TYPE=mysql
MYSQL_HOST='mysql'
MYSQL_PORT=3306
MYSQL_USER='root'
MYSQL_PASSWORD=''
MYSQL_DATABASE='MadelineProto'
MYSQL_MAX_CONNECTIONS=10
MYSQL_IDLE_TIMEOUT=60
# Recent data will be stored in memory this amount of time:
MYSQL_CACHE_TTL='+5 minutes'

View File

@ -1,3 +1,7 @@
# ENV file version
# Check for outdated .env files
VERSION=1
SERVER_ADDRESS=127.0.0.1
SERVER_PORT=9503
@ -17,4 +21,27 @@ LOGGER_LEVEL=2
TELEGRAM_PROXY_ADDRESS=
TELEGRAM_PROXY_PORT=
TELEGRAM_PROXY_USERNAME=
TELEGRAM_PROXY_PASSWORD=
TELEGRAM_PROXY_PASSWORD=
# DB
# memory
# Keep all data in memory/session file.
# This is default behavior
# mysql
# Keep part of data in mysql database.
# Reduce memory consumption and session size. Beta function.
# MariaDb + InnoDb Preffered.
# Tables and DB will be created if not exists.
# Change this type to convert session:
DB_TYPE=memory
# MYSQL Settings. Required, when DB_TYPE=mysql
MYSQL_HOST='127.0.0.1'
MYSQL_PORT=3306
MYSQL_USER='root'
MYSQL_PASSWORD=''
MYSQL_DATABASE='MadelineProto'
MYSQL_MAX_CONNECTIONS=10
MYSQL_IDLE_TIMEOUT=60
# Recent data will be stored in memory this amount of time:
MYSQL_CACHE_TTL='+5 minutes'

View File

@ -3,6 +3,8 @@ FROM php:7.4-cli
COPY . /app
WORKDIR /app
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /usr/local/bin/docker-compose-wait
RUN apt-get update && apt-get upgrade -y \
&& apt-get install apt-utils -y \
&& apt-get install git zip vim libzip-dev libgmp-dev libevent-dev libssl-dev libnghttp2-dev libffi-dev -y \
@ -11,6 +13,7 @@ RUN apt-get update && apt-get upgrade -y \
&& docker-php-ext-enable ev event \
&& docker-php-source delete \
&& apt-get autoremove --purge -y && apt-get autoclean -y && apt-get clean -y \
&& chmod +x /usr/local/bin/docker-compose-wait \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer global require hirak/prestissimo \
&& composer install -o --no-dev \
@ -24,4 +27,4 @@ RUN touch '/app/sessions/.env.docker' && \
EXPOSE 9503
ENTRYPOINT php server.php --docker -s=*
ENTRYPOINT docker-compose-wait && php server.php --docker -s=*

View File

@ -1,58 +1,59 @@
# TelegramApiServer
Fast, simple, async php telegram api server:
[MadelineProto](https://github.com/danog/MadelineProto) and [AmpPhp](https://github.com/amphp/amp) Server
[MadelineProto](https://github.com/danog/MadelineProto) and [Amp](https://github.com/amphp/amp) Http Server
* Online server for tests: [tg.i-c-a.su](https://tg.i-c-a.su)
* Online demo (getHistory + Media Download): [tg.i-c-a.su](https://tg.i-c-a.su)
* My content aggregator: [i-c-a.su](https://i-c-a.su)
* Im using this micro-service with: [my TelegramRSS micro-service](https://github.com/xtrime-ru/TelegramRSS)
* Get telegram channels in RSS: [TelegramRSS](https://github.com/xtrime-ru/TelegramRSS)
## Features
* Fast async amp http server
* Fast async Amp Http Server
* Full access to telegram api: bot and user
* Multiple sessions
* Stream media (view files in browser)
* Stream media (view files in a browser)
* Upload media
* Websocket endpoint for events
* Websocket endpoints for events and logs
* MadelineProto optimized settings to reduce memory consumption
**Architecture Example**
![Architecture Example](https://hsto.org/webt/j-/ob/ky/j-obkye1dv68ngsrgi12qevutra.png)
## Installation
Docker:
* `docker-compose up` to build and start docker. Current folder will be linked inside.
* Prebuild image: https://hub.docker.com/r/xtrime/telegram-api-server
### Docker:
* `docker pull xtrime/telegram-api-server`
* `git clone https://github.com/xtrime-ru/TelegramApiServer.git TelegramApiServer`
* `cd TelegramApiServer`
* Start container: `docker-compose up`
Manual:
1. Git clone this repo or run `composer create-project xtrime-ru/telegramapiserver`
Folder will be linked inside container to store all necessary data: sessions, env, db.
### Manual:
1. `git clone https://github.com/xtrime-ru/TelegramApiServer.git TelegramApiServer`
1. `cd telegramapiserver`
1. `composer install -o --no-dev` to install required libs
1. `composer install -o --no-dev`
1. `php server.php`
## First start
1. Ctrl + C to stop TelegramApiServer if running.
1. Get app_id and app_hash at [my.telegram.org](https://my.telegram.org/).
Only one app_id needed for any amount of users and bots.
1. Create .env from .env.example
1. Fill .env
1. Start TelegramApiServer (TAS) in cli and authorize your session:
- `php server.php --session=session`
- chose manual mode
- follow instructions
1. Ctrl + c to end TAS
1. Fill app_id and app_hash in `.env.docker` or `.env`.
1. Start TelegramApiServer in cli:
* docker:
1. Start: `docker-compose up`
1. Connect to docker container: `bash bin/docker-exec.sh`
1. Start another instance with different port: `php server.php --port=9500 --session=session`
* manual:
1. `php server.php --session=session`
1. Authorize your session:
1. chose manual mode (`m`)
1. Chose account type: user (`u`) or bot (`b`)
1. Follow instructions
1. Wait 10-30 seconds until authorization is end and exit with `Ctrl + C`.
1. Run TAS in screen, tmux, supervisor (see below) or docker.
_Optional:_
1. Use [http://supervisord.org](supervisor) to monitor and restart swoole/amphp servers. Example of `/etc/supervisor/conf.d/telegram_api_server.conf`:
```
[program:telegram_client]
command=/usr/bin/php /home/admin/web/tg.i-c-a.su/TelegramApiServer/server.php --session=session
numprocs=1
directory=/home/admin/web/tg.i-c-a.su/TelegramApiServer/
autostart=true
autorestart=true
startretries=10
stdout_logfile=/var/log/telegram/stdout.log
redirect_stderr=true
```
## Usage
1. Run server/parser
@ -104,6 +105,33 @@ Manual:
* sendMessage: `http://127.0.0.1:9503/api/sendMessage/?data[peer]=@xtrime&data[message]=Hello!`
* copy message from one channel to another (not repost): `http://127.0.0.1:9503/api/copyMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id][0]=1`
## Run in background
* Docker: `docker compose up -d`
Docker will monitor and restart containers.
* Manual:
1. Use [http://supervisord.org](supervisor) to monitor and restart swoole/amphp servers.
1. `apt-get install supervisor`
1. Put config file in `/etc/supervisor/conf.d/telegram_api_server.conf`. Example:
```
[program:telegram_api_server]
command=/usr/bin/php /home/admin/web/tg.i-c-a.su/TelegramApiServer/server.php --session=*
numprocs=1
directory=/home/admin/web/tg.i-c-a.su/TelegramApiServer/
autostart=true
autorestart=true
startretries=10
stdout_logfile=/var/log/telegram/stdout.log
redirect_stderr=true
```
1. Load new config: `supervisorctl update`
1. View/control processes: `supervisorctl`
## Update
* `git fetch && git reset --hard origin/master`
* `composer install -o --no-dev`
* Compare `.env.docker` or `.env` with corresponding `.env.example`. Update if needed.
* `docker-compose restart` or `supervisorctl restart telegram_api_server`
## Advanced features
### Uploading files.

View File

@ -3,6 +3,7 @@
use TelegramApiServer\Logger;
$root = __DIR__;
const ENV_VERSION='1';
//Composer init
{
@ -37,6 +38,14 @@ $root = __DIR__;
}
Dotenv\Dotenv::createImmutable(ROOT_DIR, $envFile)->load();
if (getenv('VERSION') !== ENV_VERSION) {
Logger::getInstance()->critical('Env version mismatch. Update .env from .env.example.', [
'VERSION in .env' => getenv('VERSION'),
'required' => ENV_VERSION
]);
throw new RuntimeException('.env version mismatch');
}
}
}

View File

@ -5,6 +5,12 @@
"homepage": "https://tg.i-c-a.su/",
"license": "MIT",
"keywords": ["telegram", "mtproto", "protocol", "client", "PHP", "amphp", "async", "daemon", "coroutine", "parser", "micro-service"],
"repositories": [
{
"type": "vcs",
"url": "https://github.com/xtrime-ru/MadelineProto.git"
}
],
"require": {
"php": ">=7.4.0",
"ext-json": "*",
@ -14,7 +20,7 @@
"amphp/websocket-server": "^2",
"amphp/websocket-client": "dev-master",
"vlucas/phpdotenv": "^4",
"danog/madelineproto":"dev-master",
"danog/madelineproto":"dev-db",
"amphp/http-server-form-parser": "^1.1"
},
"require-dev": {

225
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "877345d8ecbc0435bbe435a8c1a4abd6",
"content-hash": "2421a8f40cc9f35bd6c33abb6ef1657a",
"packages": [
{
"name": "amphp/amp",
@ -823,6 +823,61 @@
],
"time": "2019-08-21T15:51:20+00:00"
},
{
"name": "amphp/mysql",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/mysql.git",
"reference": "cbef7dccaf076218470267a2739aa0f3b49511f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/mysql/zipball/cbef7dccaf076218470267a2739aa0f3b49511f1",
"reference": "cbef7dccaf076218470267a2739aa0f3b49511f1",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"amphp/file": "^1 || ^0.3.5",
"amphp/socket": "^1",
"amphp/sql": "^1",
"amphp/sql-common": "^1",
"php": ">=7.1"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1.1.2",
"ext-openssl": "*",
"phpbench/phpbench": "^0.13.0",
"phpunit/phpunit": "^8 || ^7"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Mysql\\": "src"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
},
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
}
],
"description": "Asynchronous MySQL client for PHP based on Amp.",
"time": "2019-12-10T11:02:12+00:00"
},
{
"name": "amphp/parallel",
"version": "v1.4.0",
@ -1121,6 +1176,95 @@
],
"time": "2020-02-27T21:29:37+00:00"
},
{
"name": "amphp/sql",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/amphp/sql.git",
"reference": "0445ac35623a18105efeac93364288434430a4d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/sql/zipball/0445ac35623a18105efeac93364288434430a4d8",
"reference": "0445ac35623a18105efeac93364288434430a4d8",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"php": ">=7"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1",
"phpunit/phpunit": "^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Sql\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Asynchronous SQL client for Amp.",
"homepage": "http://amphp.org",
"keywords": [
"async",
"asynchronous",
"database",
"db",
"sql"
],
"time": "2019-09-26T16:23:02+00:00"
},
{
"name": "amphp/sql-common",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/amphp/sql-common.git",
"reference": "6bd462b80f440a06165b49bedeb868e549a6284a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/sql-common/zipball/6bd462b80f440a06165b49bedeb868e549a6284a",
"reference": "6bd462b80f440a06165b49bedeb868e549a6284a",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"amphp/sql": "^1",
"php": ">=7"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1.3",
"phpunit/phpunit": "^6 || ^7 || ^8 || ^9"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Sql\\Common\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Common classes for non-blocking SQL implementations.",
"homepage": "http://amphp.org",
"keywords": [
"async",
"asynchronous",
"database",
"db",
"sql"
],
"time": "2020-03-04T18:01:46+00:00"
},
{
"name": "amphp/sync",
"version": "v1.4.0",
@ -1676,16 +1820,16 @@
},
{
"name": "danog/madelineproto",
"version": "dev-master",
"version": "dev-db",
"source": {
"type": "git",
"url": "https://github.com/danog/MadelineProto.git",
"reference": "ddc81d87648fad3200434e75c910ef29c56e36f5"
"url": "https://github.com/xtrime-ru/MadelineProto.git",
"reference": "2aab5e8cc8e02666d8089b48434dbc8580b0916d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/danog/MadelineProto/zipball/ddc81d87648fad3200434e75c910ef29c56e36f5",
"reference": "ddc81d87648fad3200434e75c910ef29c56e36f5",
"url": "https://api.github.com/repos/xtrime-ru/MadelineProto/zipball/2aab5e8cc8e02666d8089b48434dbc8580b0916d",
"reference": "2aab5e8cc8e02666d8089b48434dbc8580b0916d",
"shasum": ""
},
"require": {
@ -1695,6 +1839,7 @@
"amphp/file": "^1",
"amphp/http-client": "^4",
"amphp/http-client-cookies": "^1",
"amphp/mysql": "^2.0",
"amphp/socket": "^1",
"danog/dns-over-https": "^0.2",
"danog/ipc": "^0.1",
@ -1749,7 +1894,46 @@
"src/polyfill.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"autoload-dev": {
"psr-4": {
"danog\\MadelineProto\\Test\\": "tests/danog/"
}
},
"scripts": {
"post-autoload-dump": [
"git submodule init && git submodule update"
],
"build": [
"@docs",
"@cs-fix",
"@psalm"
],
"check": [
"@cs",
"@test"
],
"test-php7": [
"tests/test-conversion.sh 70"
],
"test-php56": [
"tests/test-conversion.sh 5"
],
"cs": [
"PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff --dry-run"
],
"cs-fix": [
"PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff"
],
"psalm": [
"psalm"
],
"docs": [
"php tools/build_docs.php"
],
"test": [
"@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text --config tests/phpunit.xml"
]
},
"license": [
"AGPL-3.0-only"
],
@ -1763,19 +1947,22 @@
"homepage": "https://docs.madelineproto.xyz",
"keywords": [
"GB",
"Messenger",
"PHP",
"audio",
"bytes",
"client",
"files",
"messenger",
"mtproto",
"php",
"protocol",
"stickers",
"telegram",
"video"
],
"time": "2020-05-23T12:08:04+00:00"
"support": {
"source": "https://github.com/xtrime-ru/MadelineProto/tree/db"
},
"time": "2020-06-07T18:56:54+00:00"
},
{
"name": "danog/magicalserializer",
@ -2785,28 +2972,28 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v4.1.6",
"version": "v4.1.7",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "0b32505d67c1abbfa829283c86bfc0642a661bf6"
"reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/0b32505d67c1abbfa829283c86bfc0642a661bf6",
"reference": "0b32505d67c1abbfa829283c86bfc0642a661bf6",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/db63b2ea280fdcf13c4ca392121b0b2450b51193",
"reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"phpoption/phpoption": "^1.7.2",
"symfony/polyfill-ctype": "^1.9"
"phpoption/phpoption": "^1.7.3",
"symfony/polyfill-ctype": "^1.16"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.3",
"bamarni/composer-bin-plugin": "^1.4.1",
"ext-filter": "*",
"ext-pcre": "*",
"phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
"phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0"
},
"suggest": {
"ext-filter": "Required to use the boolean validator.",
@ -2855,7 +3042,7 @@
"type": "tidelift"
}
],
"time": "2020-05-23T09:43:32+00:00"
"time": "2020-06-07T18:25:35+00:00"
}
],
"packages-dev": [

View File

@ -34,6 +34,19 @@ $settings = [
'serialization_interval' => 300,
'cleanup_before_serialization' => true,
],
'db' => [
'type' => getenv('DB_TYPE'),
'mysql' => [
'host' => getenv('MYSQL_HOST'),
'port' => (int) getenv('MYSQL_PORT'),
'user' => getenv('MYSQL_USER'),
'password' => getenv('MYSQL_PASSWORD'),
'database' => getenv('MYSQL_DATABASE'),
'max_connections' => (int) getenv('MYSQL_MAX_CONNECTIONS'),
'idle_timeout' => (int) getenv('MYSQL_IDLE_TIMEOUT'),
'cache_ttl' => getenv('MYSQL_CACHE_TTL'),
]
],
'download'=>[
'report_broken_media' => false,
]

View File

@ -1,4 +1,4 @@
version: '3'
version: '3.5'
services:
telegram-api-server:
build: ./
@ -10,8 +10,25 @@ services:
volumes:
- ./:/app-host-link
working_dir: /app-host-link
depends_on:
- mysql
environment:
WAIT_HOSTS: mysql:3306
logging:
driver: "json-file"
options:
max-size: "1024k"
max-file: "2"
max-file: "2"
mysql:
image: mariadb:10.4
container_name: telegram-api-server-mysql
restart: unless-stopped
ports:
- "127.0.0.1:9507:3306"
volumes:
- ./.mysql:/var/lib/mysql
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
networks:
default:
name: telegram-api-server

View File

@ -42,7 +42,7 @@ class Client
$this->startLoggedInSession($sessionName);
}
$this->startNotLoggedInSessions();
Loop::defer(fn() => yield $this->startNotLoggedInSessions());
$sessionsCount = count($sessionFiles);
warning(

View File

@ -61,7 +61,7 @@ class ApiExtensions
'limit' => 0,
'max_id' => 0,
'min_id' => 0,
'hash' => 0,
'hash' => [],
],
$data
);
@ -79,11 +79,12 @@ class ApiExtensions
return call(
function() use ($data) {
$response = yield $this->getHistory($data);
foreach ($response['messages'] as &$message) {
$message['message'] = $this->formatMessage($message['message'] ?? null, $message['entities'] ?? []);
if (!empty($response['messages'])) {
foreach ($response['messages'] as &$message) {
$message['message'] = $this->formatMessage($message['message'] ?? null, $message['entities'] ?? []);
}
unset($message);
}
unset($message);
return $response;
}
@ -126,20 +127,37 @@ class ApiExtensions
'messageEntityUrl' => '<a href="%s" target="_blank" rel="nofollow">%s</a>',
];
$entities = array_reverse($entities);
foreach ($entities as $entity) {
foreach ($entities as $key => &$entity) {
if (isset($html[$entity['_']])) {
$text = static::mbSubstr($message, $entity['offset'], $entity['length']);
$template = $html[$entity['_']];
if (in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) {
$textFormate = sprintf($html[$entity['_']], $entity['url'] ?? $text, $text);
$textFormated = sprintf($template, strip_tags($entity['url'] ?? $text), $text);
} else {
$textFormate = sprintf($html[$entity['_']], $text);
$textFormated = sprintf($template, $text);
}
$message = static::substringReplace($message, $textFormate, $entity['offset'], $entity['length']);
$message = static::substringReplace($message, $textFormated, $entity['offset'], $entity['length']);
//Увеличим оффсеты всех следующих entity
foreach ($entities as $nextKey => &$nextEntity) {
if ($nextKey <= $key) {
continue;
}
if ($nextEntity['offset'] < ($entity['offset'] + $entity['length'])) {
$nextEntity['offset'] += static::mbStrlen(
preg_replace('~(\>).*<\/.*$~', '$1', $textFormated)
);
} else {
$nextEntity['offset'] += static::mbStrlen($textFormated) - static::mbStrlen($text);
}
}
unset($nextEntity);
}
}
unset($entity);
$message = nl2br($message);
return $message;
}

View File

@ -13,14 +13,12 @@ use function Amp\call;
class Authorization implements Middleware
{
private array $ipWhitelist;
private int $selfIp;
public function __construct()
{
$this->ipWhitelist = (array) Config::getInstance()->get('api.ip_whitelist', []);
//Add self ip for docker.
if (\count($this->ipWhitelist) > 0) {
$this->ipWhitelist[] = getHostByName(php_uname('n'));
}
$this->selfIp = ip2long(getHostByName(php_uname('n')));
}
public function handleRequest(Request $request, RequestHandler $next): Promise {
@ -39,6 +37,14 @@ class Authorization implements Middleware
private function isIpAllowed(string $host): bool
{
global $options;
if ($options['docker']) {
$isSameNetwork = abs(ip2long($host) - $this->selfIp) < 10;
if ($isSameNetwork) {
return true;
}
}
if ($this->ipWhitelist && !in_array($host, $this->ipWhitelist, true)) {
return false;
}