using Blog.Components; using Blog.Services; using Microsoft.AspNetCore.Mvc; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorComponents(); builder.Services.AddHttpClient(); builder.Services.AddHybridCache(); builder.Services.AddHttpClient( client => { client.BaseAddress = new Uri("https://brp.bes.is/"); }); // /Warframe searches Digital Extremes' published drop tables. warframe.com blocks requests from // the server, so the 4 MB page ships in Blog/Resources and scripts/update-droptables.sh refreshes // it before a publish. Singleton: the parsed tables live in the service, so a transient one would // re-parse per request - see WarframeDropService. builder.Services.Configure(builder.Configuration.GetSection(WarframeOptions.Section)); builder.Services.AddSingleton(); // /git browses the bare repositories on this server, in place of the cgit that used to. The // service holds nothing: every read opens the repository it needs and closes it again, so a push // shows up with nothing to invalidate - see GitService. builder.Services.Configure(builder.Configuration.GetSection(GitOptions.Section)); builder.Services.AddSingleton(); // /Send introduces two browsers to each other and then gets out of the way: the signalling // socket below carries their WebRTC handshake, and the file goes straight from one to the other. // Singleton because the rooms are the service - see SignalingService. builder.Services.Configure(builder.Configuration.GetSection(SendOptions.Section)); builder.Services.AddSingleton(); // /rvrb reads the rvrb bot's stats straight off its BEAM, over Erlang distribution. The node this // site dials with is started on the first request, not here - see RvrbService. builder.Services.Configure(builder.Configuration.GetSection(RvrbOptions.Section)); builder.Services.AddSingleton(); // builder.Services.AddTransient(); // builder.Services.AddTransient(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); // /Send's signalling socket. Nothing else on this site uses WebSockets. app.UseWebSockets(); app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents(); // One file out of a repository, exactly as it was committed. // // Never inline HTML: these repositories hold .html and .svg files, and serving one from this // origin would run whatever a commit put in it as a page of mb.bes.is. Text goes out as // text/plain with nosniff, which a browser will not reinterpret, and everything else is a // download. app.MapGet("/git/{repository}/raw/{**path}", ( string repository, string path, [FromQuery(Name = "h")] string? reference, GitService git, HttpResponse response) => { if (git.GetRawBlob(repository, reference, path) is not { } blob) return Results.NotFound(); response.Headers.XContentTypeOptions = "nosniff"; return blob.IsBinary ? Results.File(blob.Bytes, "application/octet-stream", blob.Name) : Results.File(blob.Bytes, "text/plain; charset=utf-8"); }); // Item names matching what has been typed, for the search box's suggestion list. app.MapGet("/api/warframe/names", async (string? q, WarframeDropService drops, CancellationToken cancellationToken) => { var status = await drops.GetTablesAsync(cancellationToken).ConfigureAwait(false); return status.Tables?.Search(q ?? "", 10) ?? []; }); // The introduction for /Send: two browsers holding the same room code trade their WebRTC offer, // answer and ICE candidates through here, and once the peer connection is up this socket has // nothing left to carry. The file never passes through this process. app.MapGet("/api/send/{room}", async ( string room, HttpContext context, SignalingService signaling) => { if (!context.WebSockets.IsWebSocketRequest) return Results.BadRequest("Expected a WebSocket request."); if (!SignalingService.IsRoomCode(room)) return Results.BadRequest("Malformed room code."); using var socket = await context.WebSockets.AcceptWebSocketAsync(); await signaling.RelayAsync(room, socket, context.RequestAborted); return Results.Empty; }); app.Run();