File: System\Net\Http\HttpRequestMessage.cs
Web Access
Project: src\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;
using System.Threading;
 
namespace System.Net.Http
{
    public class HttpRequestMessage : IDisposable
    {
        internal static Version DefaultRequestVersion => HttpVersion.Version11;
        internal static HttpVersionPolicy DefaultVersionPolicy => HttpVersionPolicy.RequestVersionOrLower;
 
        private const int MessageNotYetSent = 0;
        private const int MessageAlreadySent = 1;
        private const int MessageIsRedirect = 2;
        private const int MessageDisposed = 4;
 
        // Track whether the message has been sent.
        // The message shouldn't be sent again if this field is equal to MessageAlreadySent.
        private int _sendStatus = MessageNotYetSent;
 
        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
            {
                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();
 
        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()
        {
            StringBuilder sb = new StringBuilder();
 
            sb.Append("Method: ");
            sb.Append(_method);
 
            sb.Append(", RequestUri: '");
            if (_requestUri is null)
            {
                sb.Append("<null>");
            }
            else
            {
                sb.Append($"{_requestUri}");
            }
 
            sb.Append("', Version: ");
            sb.Append(_version);
 
            sb.Append(", Content: ");
            sb.Append(_content == null ? "<null>" : _content.GetType().ToString());
 
            sb.AppendLine(", Headers:");
            HeaderUtilities.DumpHeaders(sb, _headers, _content?.Headers);
 
            return sb.ToString();
        }
 
        internal bool MarkAsSent() => Interlocked.CompareExchange(ref _sendStatus, MessageAlreadySent, MessageNotYetSent) == MessageNotYetSent;
 
        internal bool WasSentByHttpClient() => (_sendStatus & MessageAlreadySent) != 0;
 
        internal void MarkAsRedirected() => _sendStatus |= MessageIsRedirect;
 
        internal bool WasRedirected() => (_sendStatus & MessageIsRedirect) != 0;
 
        private bool Disposed
        {
            get => (_sendStatus & MessageDisposed) != 0;
            set
            {
                Debug.Assert(value);
                _sendStatus |= MessageDisposed;
            }
        }
 
        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);
        }
    }
}