-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #818 from EdiWang/feature/index-now
Add IndexNow Support
- Loading branch information
Showing
11 changed files
with
201 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Moonglade.IndexNow.Client; | ||
|
||
public interface IIndexNowClient | ||
{ | ||
Task SendRequestAsync(Uri url); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Logging; | ||
using System.Net; | ||
using System.Text; | ||
|
||
namespace Moonglade.IndexNow.Client; | ||
|
||
public class IndexNowClient(ILogger<IndexNowClient> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory) : IIndexNowClient | ||
{ | ||
private readonly string[] _pingTargets = configuration.GetSection("IndexNow:PingTargets").Get<string[]>(); | ||
private readonly string _apiKey = configuration["IndexNow:ApiKey"] ?? throw new InvalidOperationException("IndexNow:ApiKey is not configured."); | ||
|
||
public async Task SendRequestAsync(Uri uri) | ||
{ | ||
if (string.IsNullOrWhiteSpace(_apiKey)) | ||
{ | ||
logger.LogWarning("IndexNow:ApiKey is not configured."); | ||
return; | ||
} | ||
|
||
if (_pingTargets == null || _pingTargets.Length == 0) | ||
{ | ||
throw new InvalidOperationException("IndexNow:PingTargets is not configured."); | ||
} | ||
|
||
foreach (var pingTarget in _pingTargets) | ||
{ | ||
var client = httpClientFactory.CreateClient(pingTarget); | ||
|
||
var requestBody = CreateRequestBody(uri); | ||
var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"); | ||
|
||
try | ||
{ | ||
var response = await client.PostAsync("/indexnow", content); | ||
await HandleResponseAsync(pingTarget, response); | ||
} | ||
catch (Exception e) | ||
{ | ||
logger.LogError(e, $"Failed to send index request to '{pingTarget}'"); | ||
} | ||
} | ||
} | ||
|
||
private IndexNowRequest CreateRequestBody(Uri uri) | ||
{ | ||
// https://www.indexnow.org/documentation | ||
return new IndexNowRequest | ||
{ | ||
Host = uri.Host, | ||
Key = _apiKey, | ||
KeyLocation = $"https://{uri.Host}/indexnowkey.txt", | ||
UrlList = new[] { uri.ToString() } | ||
}; | ||
} | ||
|
||
private async Task HandleResponseAsync(string pingTarget, HttpResponseMessage response) | ||
{ | ||
var responseBody = await response.Content.ReadAsStringAsync(); | ||
|
||
switch (response.StatusCode) | ||
{ | ||
// Success cases | ||
case HttpStatusCode.OK: | ||
logger.LogInformation($"Index request sent to '{pingTarget}', {response.StatusCode}: {responseBody}. URL submitted successfully."); | ||
break; | ||
case HttpStatusCode.Accepted: | ||
logger.LogWarning($"Index request sent to '{pingTarget}', {response.StatusCode}. URL received. IndexNow key validation pending."); | ||
break; | ||
|
||
// Error cases | ||
case HttpStatusCode.BadRequest: | ||
logger.LogError($"Index request sent to '{pingTarget}', {response.StatusCode}: {responseBody}. Invalid format."); | ||
break; | ||
case HttpStatusCode.Forbidden: | ||
logger.LogError($"Index request sent to '{pingTarget}', {response.StatusCode}: {responseBody}. Key not valid (e.g., key not found, file found but key not in the file)."); | ||
break; | ||
case HttpStatusCode.UnprocessableEntity: | ||
logger.LogError($"Index request sent to '{pingTarget}', {response.StatusCode}: {responseBody}. URLs which don’t belong to the host or the key is not matching the schema in the protocol."); | ||
break; | ||
case HttpStatusCode.TooManyRequests: | ||
logger.LogError($"Index request sent to '{pingTarget}', {response.StatusCode}: {responseBody}. Too many requests (potential spam)."); | ||
break; | ||
default: | ||
response.EnsureSuccessStatusCode(); | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Configuration; | ||
using System.Text; | ||
|
||
namespace Moonglade.IndexNow.Client; | ||
|
||
public class IndexNowMapHandler | ||
{ | ||
public static Delegate Handler => async (HttpContext httpContext, IConfiguration configuration) => | ||
{ | ||
await Handle(httpContext, configuration); | ||
}; | ||
|
||
public static async Task Handle(HttpContext httpContext, IConfiguration configuration) | ||
{ | ||
var apiKey = configuration["IndexNow:ApiKey"]; | ||
if (string.IsNullOrWhiteSpace(apiKey)) | ||
{ | ||
httpContext.Response.StatusCode = StatusCodes.Status404NotFound; | ||
await httpContext.Response.WriteAsync("No indexnowkey.txt is present.", httpContext.RequestAborted); | ||
} | ||
else | ||
{ | ||
httpContext.Response.ContentType = "text/plain"; | ||
await httpContext.Response.WriteAsync(apiKey, Encoding.UTF8, httpContext.RequestAborted); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Moonglade.IndexNow.Client; | ||
|
||
public class IndexNowRequest | ||
{ | ||
public string Host { get; set; } | ||
public string Key { get; set; } | ||
public string KeyLocation { get; set; } | ||
public string[] UrlList { get; set; } | ||
} |
15 changes: 15 additions & 0 deletions
15
src/Moonglade.IndexNow.Client/Moonglade.IndexNow.Client.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.9.1" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\Moonglade.Utils\Moonglade.Utils.csproj" /> | ||
</ItemGroup> | ||
</Project> |
30 changes: 30 additions & 0 deletions
30
src/Moonglade.IndexNow.Client/ServiceCollectionExtension.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Moonglade.Utils; | ||
using System.Net.Http.Headers; | ||
|
||
namespace Moonglade.IndexNow.Client; | ||
|
||
public static class ServiceCollectionExtension | ||
{ | ||
public static IServiceCollection AddIndexNowClient(this IServiceCollection services, IConfigurationSection configurationSection) | ||
{ | ||
var pingTargets = configurationSection.GetSection("PingTargets").Get<string[]>(); | ||
|
||
foreach (var pingTarget in pingTargets) | ||
{ | ||
services.AddHttpClient(pingTarget, o => | ||
{ | ||
o.BaseAddress = new Uri($"https://{pingTarget}"); | ||
o.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | ||
o.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Moonglade", Helper.AppVersionBasic)); | ||
o.DefaultRequestHeaders.Host = pingTarget; | ||
}) | ||
.AddStandardResilienceHandler(); | ||
} | ||
|
||
services.AddScoped<IIndexNowClient, IndexNowClient>(); | ||
|
||
return services; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters