/*
* 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);
//Cache current time
RequestedTimeUtc = DateTimeOffset.UtcNow;
}
///
/// 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;
///
/// Caches a that was created when the connection was created.
/// The approximate current UTC time
///
public readonly DateTimeOffset RequestedTimeUtc;
///
/// 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);
}
}