diff --git a/README.md b/README.md index 4a6d6d625..fb54062c5 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ HomoChecker はホモ([@mpyw](https://twitter.com/mpyw))にリダイレク ### 設定方法 DNS を適切に設定したあと、お使いの Web サーバーに合わせて設定を行います。 -HomoChecker は HTTP/1.1、HTTP/2、HTTP/3(Alt-Svc のみ)に対応しています。 +HomoChecker は HTTP/1.1、HTTP/2、HTTP/3 に対応しています。 #### Apache diff --git a/api/Dockerfile b/api/Dockerfile index 66b0c3582..6459e161c 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -41,7 +41,7 @@ FROM build-dependencies AS curl COPY --from=openssl /usr/local/ /usr/local/ COPY --from=nghttp3 /usr/local/ /usr/local/ COPY --from=ngtcp2 /usr/local/ /usr/local/ -RUN git clone --depth=1 -b curl-7_87_0 https://github.com/curl/curl && \ +RUN git clone --depth=1 -b curl-8_0_1 https://github.com/curl/curl && \ cd curl && \ autoreconf -fi && \ ./configure --enable-alt-svc --with-openssl --with-nghttp2 --with-nghttp3 --with-ngtcp2 && \ diff --git a/api/phpunit.xml b/api/phpunit.xml index 9b506f6e9..6c9fbcde4 100644 --- a/api/phpunit.xml +++ b/api/phpunit.xml @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@ ./tests - + ./src @@ -17,6 +17,8 @@ ./src/index.php ./src/Providers + + diff --git a/api/src/Contracts/Repository/AltsvcRepository.php b/api/src/Contracts/Repository/AltsvcRepository.php deleted file mode 100644 index bbcca7e96..000000000 --- a/api/src/Contracts/Repository/AltsvcRepository.php +++ /dev/null @@ -1,21 +0,0 @@ -trim() - ->split('/\s*;\s*/') - ->map(fn (string $param) => str($param)->explode('=', 2)->pad(2, '')) - ->map->map(fn (string $v) => str($v)->trim('"')->toString()) - ->toArray(); - - foreach ($params as [$key, $value]) { - switch ($key) { - case 'clear': - $this->clear = true; - break; - case 'ma': - $this->maxAge = (float) $value; - break; - case 'persist': - break; - default: - $this->protocolId = $key; - $this->altAuthority = $value; - break; - } - } - } - - /** - * Get the value indicating whether to invalidate all alternatives. - * @return bool Whether to invalidate all alternatives. - */ - public function isClear(): bool - { - return $this->clear; - } - - /** - * Get the identifier for the protocol. - * @return ?string The identifier for the protocol. - */ - public function getProtocolId(): ?string - { - return $this->protocolId; - } - - /** - * Get the alternative authority. - * @return ?string The alternative authority. - */ - public function getAltAuthority(): ?string - { - return $this->altAuthority; - } - - /** - * Get the max age. - * @return float The max age. - */ - public function getMaxAge(): float - { - return $this->maxAge; - } -} diff --git a/api/src/Providers/HomoAppProvider.php b/api/src/Providers/HomoAppProvider.php index 97c054aa2..98bf4defa 100644 --- a/api/src/Providers/HomoAppProvider.php +++ b/api/src/Providers/HomoAppProvider.php @@ -8,14 +8,12 @@ use HomoChecker\Action\HealthCheckAction; use HomoChecker\Action\ListAction; use HomoChecker\Action\MetricsAction; -use HomoChecker\Contracts\Repository\AltsvcRepository as AltsvcRepositoryContract; use HomoChecker\Contracts\Repository\HomoRepository as HomoRepositoryContract; use HomoChecker\Contracts\Repository\ProfileRepository as ProfileRepositoryContract; use HomoChecker\Http\NonBufferedBody; use HomoChecker\Middleware\AccessLogMiddleware; use HomoChecker\Middleware\ErrorMiddleware; use HomoChecker\Middleware\MetricsMiddleware; -use HomoChecker\Repository\AltsvcRepository; use HomoChecker\Repository\HomoRepository; use HomoChecker\Repository\ProfileRepository; use Illuminate\Config\Repository; @@ -39,7 +37,6 @@ public function register() $this->app->singleton(ListAction::class); $this->app->singleton(BadgeAction::class); - $this->app->singleton(AltsvcRepositoryContract::class, AltsvcRepository::class); $this->app->singleton(HomoRepositoryContract::class, HomoRepository::class); $this->app->singleton(ProfileRepositoryContract::class, ProfileRepository::class); diff --git a/api/src/Repository/AltsvcRepository.php b/api/src/Repository/AltsvcRepository.php deleted file mode 100644 index 2b06a561e..000000000 --- a/api/src/Repository/AltsvcRepository.php +++ /dev/null @@ -1,31 +0,0 @@ -whereRaw('altsvcs.expires_at >= CURRENT_TIMESTAMP')->get()->all(); - } - - public function save(string $url, string $protocol, string $expiresAt): void - { - DB::table('altsvcs') - ->upsert( - [ - [ - 'url' => $url, - 'protocol' => $protocol, - 'expires_at' => $expiresAt, - ], - ], - ['url'], - ['protocol', 'expires_at'], - ); - } -} diff --git a/api/src/Service/ClientService.php b/api/src/Service/ClientService.php index f68c885ca..3aa33e56a 100644 --- a/api/src/Service/ClientService.php +++ b/api/src/Service/ClientService.php @@ -8,42 +8,19 @@ use GuzzleHttp\Psr7\Response as Psr7Response; use GuzzleHttp\RequestOptions; use GuzzleHttp\TransferStats; -use HomoChecker\Contracts\Repository\AltsvcRepository as AltsvcRepositoryContract; -use HomoChecker\Contracts\Service\Client\Altsvc; use HomoChecker\Contracts\Service\Client\Response; use HomoChecker\Contracts\Service\ClientService as ClientServiceContract; -use Illuminate\Support\Collection; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; class ClientService implements ClientServiceContract { - protected ?Collection $altsvc = null; - public function __construct( protected ClientInterface $client, - protected AltsvcRepositoryContract $altsvcRepository, protected int $redirect, ) { } - protected function getAltsvc(string $url): null|string - { - if (!$this->altsvc) { - $this->altsvc = collect($this->altsvcRepository->findAll()); - } - return $this->altsvc->where('url', $url)->pluck('protocol')->first(); - } - - protected function saveAltsvc(string $url, string $protocolId, float $maxAge): void - { - $this->altsvcRepository->save( - $url, - $protocolId, - (new \DateTimeImmutable("{$maxAge} seconds"))->format(\DateTimeInterface::ATOM), - ); - } - /** * Get the responses for URL. * @param string $url The URL. @@ -52,39 +29,7 @@ protected function saveAltsvc(string $url, string $protocolId, float $maxAge): v public function getAsync(string $url): \Generator { for ($i = 0; $i < $this->redirect; ++$i) { - $options = []; - - if (str_starts_with($this->getAltsvc($url) ?? '', 'h3')) { - $options['curl'] = [ - CURLOPT_CERTINFO => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_3, - ]; - $options['headers']['Alt-Used'] = parse_url($url, PHP_URL_HOST); - $url = str($url)->replaceFirst('http://', 'https://')->toString(); - } - - yield $url => $this->client->requestAsync('GET', $url, $options + [ - RequestOptions::ON_HEADERS => function (ResponseInterface|\Throwable $response) use ($url) { - if ($response instanceof \Throwable) { - return; - } - - $altsvc = $response->getHeaderLine('Alt-Svc'); - if (!$altsvc) { - return; - } - - /** @var ?Altsvc $h3 */ - $h3 = str($altsvc) - ->split('/\s*,\s*/') - ->mapInto(Altsvc::class) - ->first(fn (Altsvc $item) => str_starts_with($item->getProtocolId(), 'h3')); - if (!$h3) { - return; - } - - $this->saveAltsvc($url, $h3->getProtocolId(), $h3->getMaxAge()); - }, + yield $url => $this->client->requestAsync('GET', $url, [ RequestOptions::ON_STATS => function (TransferStats $stats) use (&$result) { $response = $stats->getResponse(); if (!$response) { diff --git a/api/src/config.php b/api/src/config.php index 5d2272380..94ae40670 100644 --- a/api/src/config.php +++ b/api/src/config.php @@ -45,7 +45,7 @@ 'allow_redirects' => false, 'curl' => [ CURLOPT_CERTINFO => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_3, ], 'headers' => [ 'User-Agent' => 'Homozilla/5.0 (Checker/1.14.514; homOSeX 8.10)', diff --git a/api/tests/Case/Repository/AltsvcRepositoryTest.php b/api/tests/Case/Repository/AltsvcRepositoryTest.php deleted file mode 100644 index 7827b5d68..000000000 --- a/api/tests/Case/Repository/AltsvcRepositoryTest.php +++ /dev/null @@ -1,82 +0,0 @@ -altsvcs = [ - (object) [ - 'url' => 'http://foo.example.com/1', - 'protocol' => 'h3', - 'expires_at' => '2022-12-31 23:59:59', - ], - (object) [ - 'url' => 'https://homo.example.com', - 'protocol' => 'h3', - 'expires_at' => '2022-12-31 23:59:59', - ], - ]; - } - - public function testFindAll(): void - { - /** @var Builder&MockInterface $builder */ - $builder = m::mock(Builder::class); - $builder->shouldReceive('whereRaw->get->all') - ->andReturn($this->altsvcs); - - DB::shouldReceive('table') - ->once() - ->with('altsvcs') - ->andReturn($builder); - - $altsvc = new AltsvcRepository(); - $actual = $altsvc->findAll(); - - $this->assertEquals($this->altsvcs, $actual); - } - - public function testSave(): void - { - /** @var Builder&MockInterface $builder */ - $builder = m::mock(Builder::class); - $builder->shouldReceive('upsert') - ->withArgs([ - [ - [ - 'url' => 'https://homo.example.com', - 'protocol' => 'h3', - 'expires_at' => '2022-12-31 23:59:59', - ], - ], - ['url'], - ['protocol', 'expires_at'], - ]); - - DB::shouldReceive('table') - ->once() - ->with('altsvcs') - ->andReturn($builder); - - $altsvc = new AltsvcRepository(); - $altsvc->save('https://homo.example.com', 'h3', '2022-12-31 23:59:59'); - } -} diff --git a/api/tests/Case/Repository/ProfileRepositoryTest.php b/api/tests/Case/Repository/ProfileRepositoryTest.php index 524537eb4..717dbecaf 100644 --- a/api/tests/Case/Repository/ProfileRepositoryTest.php +++ b/api/tests/Case/Repository/ProfileRepositoryTest.php @@ -43,7 +43,7 @@ public function testSave(): void ->with('profiles') ->andReturn($builder); - $altsvc = new ProfileRepository(); - $altsvc->save('foo', 'https://img.example.com/foo', '2022-12-31 23:59:59'); + $profile = new ProfileRepository(); + $profile->save('foo', 'https://img.example.com/foo', '2022-12-31 23:59:59'); } } diff --git a/api/tests/Case/Service/Client/AltsvcTest.php b/api/tests/Case/Service/Client/AltsvcTest.php deleted file mode 100644 index ef9f65be4..000000000 --- a/api/tests/Case/Service/Client/AltsvcTest.php +++ /dev/null @@ -1,26 +0,0 @@ -assertTrue($actual->isClear()); - } - - public function testConstructProtocol(): void - { - $actual = new Altsvc('h3=":443"; ma=86400; persist=1'); - - $this->assertEquals('h3', $actual->getProtocolId()); - $this->assertEquals(':443', $actual->getAltAuthority()); - $this->assertEquals(86400, $actual->getMaxAge()); - } -} diff --git a/api/tests/Case/Service/ClientServiceTest.php b/api/tests/Case/Service/ClientServiceTest.php index 636f9ee69..3c106de97 100644 --- a/api/tests/Case/Service/ClientServiceTest.php +++ b/api/tests/Case/Service/ClientServiceTest.php @@ -8,11 +8,9 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; -use GuzzleHttp\Middleware; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Request as Psr7Request; use GuzzleHttp\Psr7\Response as Psr7Response; -use HomoChecker\Contracts\Repository\AltsvcRepository; use HomoChecker\Contracts\Service\Client\Response; use HomoChecker\Service\ClientService; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; @@ -37,12 +35,7 @@ public function testGetAsyncWithSingleRedirectAndReturn(): void 'transfer_time' => 1.0, ]); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $generator = $service->getAsync('https://foo.example.com/1'); $generator->rewind(); @@ -86,12 +79,7 @@ public function testGetAsyncWithMultipleRedirectsAndReturn(): void 'transfer_time' => 1.0, ]); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $generator = $service->getAsync('https://foo.example.com/2'); $generator->rewind(); @@ -143,126 +131,6 @@ public function testGetAsyncWithMultipleRedirectsAndReturn(): void $this->assertFalse($generator->valid()); } - public function testGetAsyncWithAltsvcH2(): void - { - $container = []; - $handler = HandlerStack::create(new MockHandler([ - new Psr7Response(301, [ - 'Location' => 'https://homo.example.com', - 'Alt-Svc' => 'h2=":443"; ma=86400', - ], ''), - ])); - $handler->push(Middleware::history($container)); - - $client = new Client([ - 'allow_redirects' => false, - 'http_errors' => false, - 'handler' => $handler, - 'transfer_time' => 1.0, - ]); - - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); - $generator = $service->getAsync('https://foo.example.com/1'); - - $generator->rewind(); - $this->assertTrue($generator->valid()); - - // (1/2) https://foo.example.com/1 - /** @var string $actual */ - $actual = $generator->key(); - $this->assertEquals('https://foo.example.com/1', $actual); - - /** @var PromiseInterface $actual */ - $actual = $generator->current(); - $this->assertInstanceOf(PromiseInterface::class, $actual); - - $actual = $actual->wait(); - $this->assertArrayNotHasKey('curl', $container[0]['options']); - - /** @var Response $actual */ - $this->assertInstanceOf(Response::class, $actual); - $this->assertEquals('https://homo.example.com', $actual->getHeaderLine('Location')); - $this->assertEquals(301, $actual->getStatusCode()); - $this->assertEquals(1.0, $actual->getTotalTime()); - $this->assertEquals(0.0, $actual->getStartTransferTime()); - $this->assertNull($actual->getHttpVersion()); - $this->assertNull($actual->getPrimaryIP()); - - $generator->next(); - $this->assertTrue($generator->valid()); - } - - public function testGetAsyncWithAltsvcH3(): void - { - $container = []; - $handler = HandlerStack::create(new MockHandler([ - new Psr7Response(301, [ - 'Location' => 'https://homo.example.com', - 'Alt-Svc' => 'h3=":443"; ma=86400, h3-29=":443"; ma=86400, h3-28=":443"; ma=86400, h3-27=":443"; ma=86400', - ], ''), - ])); - $handler->push(Middleware::history($container)); - - $client = new Client([ - 'allow_redirects' => false, - 'http_errors' => false, - 'handler' => $handler, - 'transfer_time' => 1.0, - ]); - - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([ - [ - 'url' => 'http://foo.example.com/1', - 'protocol' => 'h3', - ], - [ - 'url' => 'https://homo.example.com', - 'protocol' => 'h3', - ], - ]); - $altsvc->shouldReceive('save') - ->withArgs(['https://foo.example.com/1', 'h3', m::type('string')]); - - $service = new ClientService($client, $altsvc, 5); - $generator = $service->getAsync('http://foo.example.com/1'); - - $generator->rewind(); - $this->assertTrue($generator->valid()); - - // (1/2) https://foo.example.com/1 - /** @var string $actual */ - $actual = $generator->key(); - $this->assertEquals('https://foo.example.com/1', $actual); - - /** @var PromiseInterface $actual */ - $actual = $generator->current(); - $this->assertInstanceOf(PromiseInterface::class, $actual); - - $actual = $actual->wait(); - $this->assertEquals(CURL_HTTP_VERSION_3, $container[0]['options']['curl'][CURLOPT_HTTP_VERSION]); - $this->assertEquals(['foo.example.com'], $container[0]['request']->getHeader('Alt-Used')); - - /** @var Response $actual */ - $this->assertInstanceOf(Response::class, $actual); - $this->assertEquals('https://homo.example.com', $actual->getHeaderLine('Location')); - $this->assertEquals(301, $actual->getStatusCode()); - $this->assertEquals(1.0, $actual->getTotalTime()); - $this->assertEquals(0.0, $actual->getStartTransferTime()); - $this->assertNull($actual->getHttpVersion()); - $this->assertNull($actual->getPrimaryIP()); - - $generator->next(); - $this->assertTrue($generator->valid()); - } - public function testGetAsyncWithException(): void { $client = new Client([ @@ -274,12 +142,7 @@ public function testGetAsyncWithException(): void 'transfer_time' => 1.0, ]); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $generator = $service->getAsync('https://baz.example.com'); $generator->rewind(); @@ -313,12 +176,7 @@ public function testSend(): void ->withArgs([$request, ['http_errors' => false]]) ->andReturn($response); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $actual = $service->send($request, [ 'http_errors' => false, ]); @@ -340,12 +198,7 @@ public function testSendAsync(): void ->withArgs([$request, ['http_errors' => false]]) ->andReturn($promise); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $actual = $service->sendAsync($request, [ 'http_errors' => false, ]); @@ -364,12 +217,7 @@ public function testRequest(): void ->withArgs(['GET', 'https://example.com', ['http_errors' => false]]) ->andReturn($response); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $actual = $service->request('GET', 'https://example.com', [ 'http_errors' => false, ]); @@ -388,12 +236,7 @@ public function testRequestAsync(): void ->withArgs(['GET', 'https://example.com', ['http_errors' => false]]) ->andReturn($promise); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $actual = $service->requestAsync('GET', 'https://example.com', [ 'http_errors' => false, ]); @@ -408,12 +251,7 @@ public function testGetConfig(): void $client->shouldReceive('getConfig') ->andReturn(['http_errors' => false]); - /** @var AltsvcRepository&MockInterface $altsvc */ - $altsvc = m::mock(AltsvcRepository::class); - $altsvc->shouldReceive('findAll') - ->andReturn([]); - - $service = new ClientService($client, $altsvc, 5); + $service = new ClientService($client, 5); $actual = $service->getConfig(); $this->assertEquals(['http_errors' => false], $actual); diff --git a/database/homo.sql b/database/homo.sql index 57f45f728..42f8ed751 100644 --- a/database/homo.sql +++ b/database/homo.sql @@ -12,9 +12,3 @@ CREATE TABLE IF NOT EXISTS "profiles" ( "expires_at" timestamp NOT NULL, UNIQUE ("screen_name") ); - -CREATE TABLE IF NOT EXISTS "altsvcs" ( - "url" varchar(255) NOT NULL PRIMARY KEY, - "protocol" varchar(20) NOT NULL, - "expires_at" timestamp NOT NULL -);