/* * Copyright (c) 2022 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials * File: HttpEntity.cs * * HttpEntity.cs is part of VNLib.Plugins.Essentials which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Essentials is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Plugins.Essentials is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.IO; using System.Net; using System.Threading; using System.Collections.Generic; using System.Runtime.CompilerServices; using VNLib.Net.Http; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Essentials.Extensions; #nullable enable /* * HttpEntity was converted to an object as during profiling * it was almost always heap allcated due to async opertaions * or other object tracking issues. So to reduce the number of * allocations (at the cost of larger objects) basic profiling * showed less GC load and less collections when SessionInfo * remained a value type */ #pragma warning disable CA1051 // Do not declare visible instance fields namespace VNLib.Plugins.Essentials { /// /// A container for an with its attached session. /// This class cannot be inherited. /// public sealed class HttpEntity : IHttpEvent { /// /// The connection event entity /// private readonly IHttpEvent Entity; public HttpEntity(IHttpEvent entity, EventProcessor root, in SessionHandle session, in CancellationToken cancellation) { Entity = entity; RequestedRoot = root; EventCancellation = cancellation; //See if the connection is coming from an downstream server IsBehindDownStreamServer = root.Options.DownStreamServers.Contains(entity.Server.RemoteEndpoint.Address); /* * If the connection was behind a trusted downstream server, * we can trust the x-forwarded-for header, * otherwise use the remote ep ip address */ TrustedRemoteIp = entity.Server.GetTrustedIp(IsBehindDownStreamServer); //Initialize the session Session = session.IsSet ? new(session.SessionData, entity.Server, TrustedRemoteIp) : new(); //Local connection IsLocalConnection = entity.Server.LocalEndpoint.Address.IsLocalSubnet(TrustedRemoteIp); //Cache value IsSecure = entity.Server.IsSecure(IsBehindDownStreamServer); } /// /// A token that has a scheduled timeout to signal the cancellation of the entity event /// public readonly CancellationToken EventCancellation; /// /// The session assocaited with the event /// public readonly SessionInfo Session; /// /// A value that indicates if the connecion came from a trusted downstream server /// public readonly bool IsBehindDownStreamServer; /// /// Determines if the connection came from the local network to the current server /// public readonly bool IsLocalConnection; /// /// Gets a value that determines if the connection is using tls, locally /// or behind a trusted downstream server that is using tls. /// public readonly bool IsSecure; /// /// The connection info object assocated with the entity /// public IConnectionInfo Server => Entity.Server; /// /// User's ip. If the connection is behind a local proxy, returns the users actual IP. Otherwise returns the connection ip. /// public readonly IPAddress TrustedRemoteIp; /// /// The requested web root. Provides additional site information /// public readonly EventProcessor RequestedRoot; /// /// If the request has query arguments they are stored in key value format /// public IReadOnlyDictionary QueryArgs => Entity.QueryArgs; /// /// If the request body has form data or url encoded arguments they are stored in key value format /// public IReadOnlyDictionary RequestArgs => Entity.RequestArgs; /// /// Contains all files upladed with current request /// public IReadOnlyList Files => Entity.Files; /// HttpServer IHttpEvent.OriginServer => Entity.OriginServer; /// /// Complete the session and respond to user /// /// Status code of operation /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CloseResponse(HttpStatusCode code) => Entity.CloseResponse(code); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CloseResponse(HttpStatusCode code, ContentType type, Stream stream) { Entity.CloseResponse(code, type, stream); //Verify content type matches if (!Server.Accepts(type)) { throw new ContentTypeUnacceptableException("The client does not accept the content type of the response"); } } /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity) { //Verify content type matches if (!Server.Accepts(type)) { throw new ContentTypeUnacceptableException("The client does not accept the content type of the response"); } Entity.CloseResponse(code, type, entity); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void DisableCompression() => Entity.DisableCompression(); /* * Do not directly expose dangerous methods, but allow them to be called */ /// [MethodImpl(MethodImplOptions.AggressiveInlining)] void IHttpEvent.DangerousChangeProtocol(IAlternateProtocol protocolHandler) => Entity.DangerousChangeProtocol(protocolHandler); } }