mirror of
https://github.com/rekryt/iplist.git
synced 2026-08-27 12:11:00 +00:00
New output formats: - `format=geosite` — v2ray/xray geosite.dat, built natively by GeositeDatWriter (domain-list-community rejects 406 of 409 portal names, so an external generator was not an option); `domaintype=suffix|full|keyword|regex`. - `format=singbox` — sing-box rule-set source JSON, served inline; it becomes an attachment only with `?filesave=1`. - `format=srs` — the same rule-set compiled to binary by the sing-box binary (SINGBOX_PATH); without the binary the format returns a plain-text error. - `format=geoip` — same contract, now built in PHP by GeoipDatWriter. `SYS_GEOIP_NATIVE=false` keeps the v2fly binary path for one release. Infrastructure: - src/Infrastructure/Codec: ProtobufWriter, GeoipDatWriter, GeositeDatWriter, SingboxRuleSetBuilder, EntryPayload, DatEncoder. Writers are pure functions — no I/O, no Amp, no singletons — so the same class runs inline and inside an amphp/parallel worker. Above SYS_ENCODE_WORKER_THRESHOLD encoding goes to a worker, below it yields to the event loop every 32 lists. - TempWorkspace: one temp dir per request under storage/tmp, removed in a finally, plus a sweeper for dirs orphaned by SIGKILL (SYS_TMP_TTL, SYS_TMP_SWEEP_INTERVAL). Replaces the leaking geoip/input + geoip/output. - ProcessRunner: the single place that starts external binaries — command as an array, both pipes buffered concurrently (not reading them deadlocked a chatty child), exit code and stderr surfaced in the error body. Fixes: - wildcard domain collapse no longer emits a bare "." for scraped junk like `DNSdumpster..`; such entries are dropped by SiteFactory::normalizeDomains on ingestion, and a matching guard sits in Site::getDomains. - geoip.dat was served as text/plain; both engines now use application/octet-stream. - HttpClient in AsyncTest is shared across the run: per-test clients kept their keep-alive pools alive and starved the Windows StreamSelectDriver by the end of a full run. UI, docs, build: - Form.vue: new formats, domaintype and rule-set version selects, wildcard on by default, filesave hidden for attachment-only formats; public/ rebuilt. - IndexTemplate.php (no-JS form on /index) caught up with the frontend. - README.md, README.en.md, CLAUDE.md, ROADMAP.md, .env.example. - Dockerfile: sing-box from the release tarball (-glibc), v2fly/geoip built on golang:1.25 with a pinned tag; the apt Go 1.19 stage was the broken build. https://github.com/itdoginfo/podkop/issues/418
97 lines
3.7 KiB
PHP
97 lines
3.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace OpenCCK;
|
||
|
||
use Amp\ByteStream\StreamException;
|
||
use Amp\Http\Client\HttpClient;
|
||
use Amp\Http\Client\HttpClientBuilder;
|
||
use Amp\Http\Client\HttpException;
|
||
use Amp\Http\Client\Request;
|
||
use Amp\Http\Client\Response;
|
||
use Amp\PHPUnit\AsyncTestCase;
|
||
|
||
use OpenCCK\App\Service\IPListService;
|
||
use OpenCCK\Infrastructure\API\App;
|
||
|
||
/**
|
||
* Base class for async controller tests.
|
||
*
|
||
* Boot is handled once by test/bootstrap.php (PATH_ROOT is pinned to
|
||
* test/fixtures, App + Server are started, IPListService loads fixtures
|
||
* with timeout=0 so no DNS/reload work happens during tests).
|
||
*
|
||
* Subclasses send HTTP requests via $this->get() and assert on the response.
|
||
*/
|
||
abstract class AsyncTest extends AsyncTestCase {
|
||
/**
|
||
* Один клиент на весь прогон, а не по клиенту на тест.
|
||
*
|
||
* PHPUnit держит экземпляры тест-кейсов живыми до конца прогона (они нужны
|
||
* ему для отчёта), поэтому клиент каждого теста тоже остаётся жив вместе со
|
||
* своим keep-alive пулом соединений. Число открытых сокетов росло линейно с
|
||
* числом тестов, а Revolt на Windows работает через StreamSelectDriver,
|
||
* который на каждом тике обходит весь набор дескрипторов — к концу прогона
|
||
* запросы начинали упираться в таймауты на ровном месте (падал то StressTest,
|
||
* то TextFilterTest, в зависимости от порядка). С общим клиентом соединение
|
||
* переиспользуется и набор дескрипторов не растёт.
|
||
*/
|
||
private static ?HttpClient $sharedHttpClient = null;
|
||
|
||
protected HttpClient $httpClient;
|
||
protected string $baseUrl;
|
||
protected App $app;
|
||
|
||
protected function setUp(): void {
|
||
parent::setUp();
|
||
self::$sharedHttpClient ??= (new HttpClientBuilder())->followRedirects(0)->build();
|
||
$this->httpClient = self::$sharedHttpClient;
|
||
$this->baseUrl = 'http://127.0.0.1:' . ($_ENV['HTTP_PORT'] ?? 8090);
|
||
$this->app = App::getInstance();
|
||
}
|
||
|
||
/**
|
||
* Send a GET request to the running test server.
|
||
*
|
||
* Query building preserves repeated keys like `exclude[group]=a&exclude[group]=b`.
|
||
* Pass an array value to repeat the key: ['exclude[group]' => ['casino', 'porn']].
|
||
*
|
||
* @param array<string, string|int|array<int, string|int>> $query
|
||
* @throws HttpException
|
||
*/
|
||
protected function get(string $path = '/', array $query = []): Response {
|
||
return $this->httpClient->request(new Request($this->buildUrl($path, $query), 'GET'));
|
||
}
|
||
|
||
/**
|
||
* @param array<string, string|int|array<int, string|int>> $query
|
||
*/
|
||
protected function buildUrl(string $path, array $query): string {
|
||
$url = $this->baseUrl . $path;
|
||
if (!$query) {
|
||
return $url;
|
||
}
|
||
|
||
$parts = [];
|
||
foreach ($query as $name => $value) {
|
||
foreach (is_array($value) ? $value : [$value] as $item) {
|
||
$parts[] = rawurlencode($name) . '=' . rawurlencode((string) $item);
|
||
}
|
||
}
|
||
return $url . '?' . implode('&', $parts);
|
||
}
|
||
|
||
/**
|
||
* Buffer and return the response body as a string.
|
||
*
|
||
* @throws StreamException
|
||
*/
|
||
protected function body(Response $response): string {
|
||
return $response->getBody()->buffer();
|
||
}
|
||
|
||
protected function service(): IPListService {
|
||
return IPListService::getInstance();
|
||
}
|
||
}
|