| File: System\Net\Http\HttpRequestMessage.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; using System.Text; namespace System.Net.Http { public class HttpRequestMessage : IDisposable { internal static Version DefaultRequestVersion => HttpVersion.Version11; internal static HttpVersionPolicy DefaultVersionPolicy => HttpVersionPolicy.RequestVersionOrLower; [Flags] private enum MessageFlags { AlreadySent = 1, PropagatorStateInjectedByDiagnosticsHandler = 2, Disposed = 4, AuthDisabled = 8, ConnectionIdSet = 16, DoNotPartitionConnectionPoolBySni = 32, } private MessageFlags _flags; private long _connectionId; private HttpMethod _method; private Uri? _requestUri; private HttpRequestHeaders? _headers; private Version _version; private HttpVersionPolicy _versionPolicy; private HttpContent? _content; internal HttpRequestOptions? _options; public Version Version { get { return _version; } set { ArgumentNullException.ThrowIfNull(value); CheckDisposed(); _version = value; } } /// <summary> /// Gets or sets the policy determining how <see cref="Version" /> is interpreted and how is the final HTTP version negotiated with the server. /// </summary> public HttpVersionPolicy VersionPolicy { get { return _versionPolicy; } set { if ((uint)value > (uint)HttpVersionPolicy.RequestVersionExact) { throw new ArgumentException(SR.Format(SR.net_invalid_enum, nameof(HttpVersionPolicy)), nameof(value)); } CheckDisposed(); _versionPolicy = value; } } public HttpContent? Content { get { return _content; } set { CheckDisposed(); if (NetEventSource.Log.IsEnabled()) { if (value == null) { NetEventSource.ContentNull(this); } else { NetEventSource.Associate(this, value); } } // It's OK to set a 'null' content, even if the method is POST/PUT. _content = value; } } public HttpMethod Method { get { return _method; } set { ArgumentNullException.ThrowIfNull(value); CheckDisposed(); _method = value; } } public Uri? RequestUri { get { return _requestUri; } set { CheckDisposed(); _requestUri = value; } } public HttpRequestHeaders Headers => _headers ??= new HttpRequestHeaders(); internal bool HasHeaders => _headers != null; [Obsolete("HttpRequestMessage.Properties has been deprecated. Use Options instead.")] public IDictionary<string, object?> Properties => Options; /// <summary> /// Gets the collection of options to configure the HTTP request. /// </summary> public HttpRequestOptions Options => _options ??= new HttpRequestOptions(); /// <summary> /// Gets or sets the identifier of the connection that this request was most recently sent on. The value is not /// guaranteed to be set: it remains <see langword="null"/> when the request was not handled by a connection, for /// example because it timed out before a connection could be obtained. /// </summary> /// <remarks> /// When the request is sent through a <see cref="SocketsHttpHandler"/>, the value matches the connection id /// reported through EventSource telemetry and the id passed to /// <see cref="SocketsHttpHandler.ShouldEvictConnection"/> for the connection that served the request, allowing /// a caller to correlate a request with that connection. It also matches the id surfaced to a custom /// <see cref="SocketsHttpHandler.ConnectCallback"/>. When a request is sent over multiple connections (for /// example after a redirect or a retry), the value reflects the most recent attempt. /// <para> /// HTTP CONNECT proxy tunnels are an exception to the correlation with a custom /// <see cref="SocketsHttpHandler.ConnectCallback"/>: when the request is served over such a tunnel, the callback /// observes the tunnel's underlying transport connection to the proxy, whose id differs from this one (which /// identifies the tunneled connection that carried the request). Both ids remain observable through a /// <see cref="SocketsHttpHandler.PlaintextStreamFilter"/>, which runs once per hop and reports the transport /// connection's id for the CONNECT hop and this id for the tunneled hop. /// </para> /// <para> /// These correlations apply only when the request is handled by <see cref="SocketsHttpHandler"/>. Another /// <see cref="HttpMessageHandler"/> may never set this value, or may assign it a different meaning. /// </para> /// <para> /// This property is intended to be read after the request has been sent. Assigning a value before the request /// is sent has no effect on how the request is handled: it does not request or influence the use of a particular /// connection, and any value set by the caller is overwritten with the id of the connection that actually serves /// the request. /// </para> /// </remarks> [Experimental(Experimentals.SocketsHttpHandlerExperimentalDiagId, UrlFormat = Experimentals.SharedUrlFormat)] public long? ConnectionId { // ConnectionIdSet is stored separately to avoid the extra bytes needed for a nullable 'long?' field. get => _flags.HasFlag(MessageFlags.ConnectionIdSet) ? _connectionId : null; set { if (value is null) { _flags &= ~MessageFlags.ConnectionIdSet; } else { _connectionId = value.Value; _flags |= MessageFlags.ConnectionIdSet; } } } public HttpRequestMessage() : this(HttpMethod.Get, (Uri?)null) { } public HttpRequestMessage(HttpMethod method, Uri? requestUri) { ArgumentNullException.ThrowIfNull(method); // It's OK to have a 'null' request Uri. If HttpClient is used, the 'BaseAddress' will be added. // If there is no 'BaseAddress', sending this request message will throw. // Note that we also allow the string to be empty: null and empty are considered equivalent. _method = method; _requestUri = requestUri; _version = DefaultRequestVersion; _versionPolicy = DefaultVersionPolicy; } public HttpRequestMessage(HttpMethod method, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri) : this(method, string.IsNullOrEmpty(requestUri) ? null : new Uri(requestUri, UriKind.RelativeOrAbsolute)) { } public override string ToString() { ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[512]); sb.Append("Method: "); sb.Append(_method.ToString()); sb.Append(", RequestUri: '"); if (_requestUri is null) { sb.Append("<null>"); } else { sb.AppendSpanFormattable(_requestUri); } sb.Append("', Version: "); sb.AppendSpanFormattable(_version); sb.Append(", Content: "); sb.Append(_content == null ? "<null>" : _content.GetType().ToString()); sb.Append(", Headers:"); sb.Append(Environment.NewLine); HeaderUtilities.DumpHeaders(ref sb, _headers, _content?.Headers); return sb.ToString(); } internal bool MarkAsSent() { MessageFlags previousFlags = _flags; _flags = previousFlags | MessageFlags.AlreadySent; return !previousFlags.HasFlag(MessageFlags.AlreadySent); } internal bool WasSentByHttpClient() => _flags.HasFlag(MessageFlags.AlreadySent); internal void MarkPropagatorStateInjectedByDiagnosticsHandler() => _flags |= MessageFlags.PropagatorStateInjectedByDiagnosticsHandler; internal bool WasPropagatorStateInjectedByDiagnosticsHandler() => _flags.HasFlag(MessageFlags.PropagatorStateInjectedByDiagnosticsHandler); internal void DisableAuth() => _flags |= MessageFlags.AuthDisabled; internal bool IsAuthDisabled() => _flags.HasFlag(MessageFlags.AuthDisabled); internal bool IsConnectionPoolPartitioningBySniDisabled() => _flags.HasFlag(MessageFlags.DoNotPartitionConnectionPoolBySni); // Experimental opt-in accessed via UnsafeAccessor from System.Net.Http tests. There is no product code path // that sets this flag today, so it is preserved from the trimmer via ILLink.Descriptors.LibraryBuild.xml. internal void ExperimentalDangerousDoNotPartitionConnectionPoolBySni() => _flags |= MessageFlags.DoNotPartitionConnectionPoolBySni; private bool Disposed { get => _flags.HasFlag(MessageFlags.Disposed); set { Debug.Assert(value); _flags |= MessageFlags.Disposed; } } internal bool IsExtendedConnectRequest => Method == HttpMethod.Connect && _headers?.Protocol != null; #region IDisposable Members protected virtual void Dispose(bool disposing) { // The reason for this type to implement IDisposable is that it contains instances of types that implement // IDisposable (content). if (disposing && !Disposed) { Disposed = true; _content?.Dispose(); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion private void CheckDisposed() { ObjectDisposedException.ThrowIf(Disposed, this); } } }