| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Diagnostics.CodeAnalysis; |
| | | 6 | | using System.Net.Http.Headers; |
| | | 7 | | using System.Text; |
| | | 8 | | |
| | | 9 | | namespace System.Net.Http |
| | | 10 | | { |
| | | 11 | | public class HttpResponseMessage : IDisposable |
| | | 12 | | { |
| | | 13 | | private const HttpStatusCode DefaultStatusCode = HttpStatusCode.OK; |
| | 1 | 14 | | private static Version DefaultResponseVersion => HttpVersion.Version11; |
| | | 15 | | |
| | | 16 | | private HttpStatusCode _statusCode; |
| | | 17 | | private HttpResponseHeaders? _headers; |
| | | 18 | | private HttpResponseHeaders? _trailingHeaders; |
| | | 19 | | private string? _reasonPhrase; |
| | | 20 | | private HttpRequestMessage? _requestMessage; |
| | | 21 | | private Version _version; |
| | | 22 | | private HttpContent? _content; |
| | | 23 | | private bool _disposed; |
| | | 24 | | |
| | | 25 | | public Version Version |
| | | 26 | | { |
| | 0 | 27 | | get { return _version; } |
| | | 28 | | set |
| | 0 | 29 | | { |
| | | 30 | | #if !PHONE |
| | 0 | 31 | | ArgumentNullException.ThrowIfNull(value); |
| | | 32 | | #endif |
| | 0 | 33 | | CheckDisposed(); |
| | | 34 | | |
| | 0 | 35 | | _version = value; |
| | 0 | 36 | | } |
| | | 37 | | } |
| | | 38 | | |
| | 0 | 39 | | internal void SetVersionWithoutValidation(Version value) => _version = value; |
| | | 40 | | |
| | | 41 | | [AllowNull] |
| | | 42 | | public HttpContent Content |
| | | 43 | | { |
| | 0 | 44 | | get { return _content ??= new EmptyContent(); } |
| | | 45 | | set |
| | 0 | 46 | | { |
| | 0 | 47 | | CheckDisposed(); |
| | | 48 | | |
| | 0 | 49 | | if (NetEventSource.Log.IsEnabled()) |
| | 0 | 50 | | { |
| | 0 | 51 | | if (value == null) |
| | 0 | 52 | | { |
| | 0 | 53 | | NetEventSource.ContentNull(this); |
| | 0 | 54 | | } |
| | | 55 | | else |
| | 0 | 56 | | { |
| | 0 | 57 | | NetEventSource.Associate(this, value); |
| | 0 | 58 | | } |
| | 0 | 59 | | } |
| | | 60 | | |
| | 0 | 61 | | _content = value; |
| | 0 | 62 | | } |
| | | 63 | | } |
| | | 64 | | |
| | | 65 | | public HttpStatusCode StatusCode |
| | | 66 | | { |
| | 0 | 67 | | get { return _statusCode; } |
| | | 68 | | set |
| | 0 | 69 | | { |
| | 0 | 70 | | ArgumentOutOfRangeException.ThrowIfNegative((int)value, nameof(value)); |
| | 0 | 71 | | ArgumentOutOfRangeException.ThrowIfGreaterThan((int)value, 999, nameof(value)); |
| | 0 | 72 | | CheckDisposed(); |
| | | 73 | | |
| | 0 | 74 | | _statusCode = value; |
| | 0 | 75 | | } |
| | | 76 | | } |
| | | 77 | | |
| | 0 | 78 | | internal void SetStatusCodeWithoutValidation(HttpStatusCode value) => _statusCode = value; |
| | | 79 | | |
| | | 80 | | public string? ReasonPhrase |
| | | 81 | | { |
| | | 82 | | get |
| | 0 | 83 | | { |
| | 0 | 84 | | if (_reasonPhrase != null) |
| | 0 | 85 | | { |
| | 0 | 86 | | return _reasonPhrase; |
| | | 87 | | } |
| | | 88 | | // Provide a default if one was not set. |
| | 0 | 89 | | return HttpStatusDescription.Get(StatusCode); |
| | 0 | 90 | | } |
| | | 91 | | set |
| | 0 | 92 | | { |
| | 0 | 93 | | if ((value != null) && HttpRuleParser.ContainsNewLineOrNull(value)) |
| | 0 | 94 | | { |
| | 0 | 95 | | throw new FormatException(SR.net_http_reasonphrase_format_error); |
| | | 96 | | } |
| | 0 | 97 | | CheckDisposed(); |
| | | 98 | | |
| | 0 | 99 | | _reasonPhrase = value; // It's OK to have a 'null' reason phrase. |
| | 0 | 100 | | } |
| | | 101 | | } |
| | | 102 | | |
| | 0 | 103 | | internal void SetReasonPhraseWithoutValidation(string value) => _reasonPhrase = value; |
| | | 104 | | |
| | 1 | 105 | | public HttpResponseHeaders Headers => _headers ??= new HttpResponseHeaders(); |
| | | 106 | | |
| | 0 | 107 | | public HttpResponseHeaders TrailingHeaders => _trailingHeaders ??= new HttpResponseHeaders(containsTrailingHeade |
| | | 108 | | |
| | | 109 | | /// <summary>Stores the supplied trailing headers into this instance.</summary> |
| | | 110 | | /// <remarks> |
| | | 111 | | /// In the common/desired case where response.TrailingHeaders isn't accessed until after the whole payload has b |
| | | 112 | | /// received, <see cref="_trailingHeaders" /> will still be null, and we can simply store the supplied instance |
| | | 113 | | /// <see cref="_trailingHeaders" /> and assume ownership of the instance. In the uncommon case where it was acc |
| | | 114 | | /// we add all of the headers to the existing instance. |
| | | 115 | | /// </remarks> |
| | | 116 | | internal void StoreReceivedTrailingHeaders(HttpResponseHeaders headers) |
| | 0 | 117 | | { |
| | 0 | 118 | | Debug.Assert(headers.ContainsTrailingHeaders); |
| | | 119 | | |
| | 0 | 120 | | if (_trailingHeaders is null) |
| | 0 | 121 | | { |
| | 0 | 122 | | _trailingHeaders = headers; |
| | 0 | 123 | | } |
| | | 124 | | else |
| | 0 | 125 | | { |
| | 0 | 126 | | _trailingHeaders.AddHeaders(headers); |
| | 0 | 127 | | } |
| | 0 | 128 | | } |
| | | 129 | | |
| | | 130 | | public HttpRequestMessage? RequestMessage |
| | | 131 | | { |
| | 0 | 132 | | get { return _requestMessage; } |
| | | 133 | | set |
| | 0 | 134 | | { |
| | 0 | 135 | | CheckDisposed(); |
| | 0 | 136 | | if (value is not null && NetEventSource.Log.IsEnabled()) |
| | 0 | 137 | | NetEventSource.Associate(this, value); |
| | 0 | 138 | | _requestMessage = value; |
| | 0 | 139 | | } |
| | | 140 | | } |
| | | 141 | | |
| | | 142 | | public bool IsSuccessStatusCode |
| | | 143 | | { |
| | 0 | 144 | | get { return ((int)_statusCode >= 200) && ((int)_statusCode <= 299); } |
| | | 145 | | } |
| | | 146 | | |
| | | 147 | | public HttpResponseMessage() |
| | 1 | 148 | | : this(DefaultStatusCode) |
| | 1 | 149 | | { |
| | 1 | 150 | | } |
| | | 151 | | |
| | 1 | 152 | | public HttpResponseMessage(HttpStatusCode statusCode) |
| | 1 | 153 | | { |
| | 1 | 154 | | ArgumentOutOfRangeException.ThrowIfNegative((int)statusCode, nameof(statusCode)); |
| | 1 | 155 | | ArgumentOutOfRangeException.ThrowIfGreaterThan((int)statusCode, 999, nameof(statusCode)); |
| | | 156 | | |
| | 1 | 157 | | _statusCode = statusCode; |
| | 1 | 158 | | _version = DefaultResponseVersion; |
| | 1 | 159 | | } |
| | | 160 | | |
| | | 161 | | public HttpResponseMessage EnsureSuccessStatusCode() |
| | 0 | 162 | | { |
| | 0 | 163 | | if (!IsSuccessStatusCode) |
| | 0 | 164 | | { |
| | 0 | 165 | | throw new HttpRequestException( |
| | 0 | 166 | | SR.Format( |
| | 0 | 167 | | System.Globalization.CultureInfo.InvariantCulture, |
| | 0 | 168 | | string.IsNullOrWhiteSpace(ReasonPhrase) ? SR.net_http_message_not_success_statuscode : SR.net_ht |
| | 0 | 169 | | (int)_statusCode, |
| | 0 | 170 | | ReasonPhrase), |
| | 0 | 171 | | inner: null, |
| | 0 | 172 | | _statusCode); |
| | | 173 | | } |
| | | 174 | | |
| | 0 | 175 | | return this; |
| | 0 | 176 | | } |
| | | 177 | | |
| | | 178 | | public override string ToString() |
| | 0 | 179 | | { |
| | 0 | 180 | | ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[512]); |
| | | 181 | | |
| | 0 | 182 | | sb.Append("StatusCode: "); |
| | 0 | 183 | | sb.AppendSpanFormattable((int)_statusCode); |
| | | 184 | | |
| | 0 | 185 | | sb.Append(", ReasonPhrase: '"); |
| | 0 | 186 | | sb.Append(ReasonPhrase ?? "<null>"); |
| | | 187 | | |
| | 0 | 188 | | sb.Append("', Version: "); |
| | 0 | 189 | | sb.AppendSpanFormattable(_version); |
| | | 190 | | |
| | 0 | 191 | | sb.Append(", Content: "); |
| | 0 | 192 | | sb.Append(_content == null ? "<null>" : _content.GetType().ToString()); |
| | | 193 | | |
| | 0 | 194 | | sb.Append(", Headers:"); |
| | 0 | 195 | | sb.Append(Environment.NewLine); |
| | 0 | 196 | | HeaderUtilities.DumpHeaders(ref sb, _headers, _content?.Headers); |
| | | 197 | | |
| | 0 | 198 | | if (_trailingHeaders != null) |
| | 0 | 199 | | { |
| | 0 | 200 | | sb.Append(", Trailing Headers:"); |
| | 0 | 201 | | sb.Append(Environment.NewLine); |
| | 0 | 202 | | HeaderUtilities.DumpHeaders(ref sb, _trailingHeaders); |
| | 0 | 203 | | } |
| | | 204 | | |
| | 0 | 205 | | return sb.ToString(); |
| | 0 | 206 | | } |
| | | 207 | | |
| | | 208 | | #region IDisposable Members |
| | | 209 | | |
| | | 210 | | protected virtual void Dispose(bool disposing) |
| | 0 | 211 | | { |
| | | 212 | | // The reason for this type to implement IDisposable is that it contains instances of types that implement |
| | | 213 | | // IDisposable (content). |
| | 0 | 214 | | if (disposing && !_disposed) |
| | 0 | 215 | | { |
| | 0 | 216 | | _disposed = true; |
| | 0 | 217 | | _content?.Dispose(); |
| | 0 | 218 | | } |
| | 0 | 219 | | } |
| | | 220 | | |
| | | 221 | | public void Dispose() |
| | 0 | 222 | | { |
| | 0 | 223 | | Dispose(true); |
| | 0 | 224 | | GC.SuppressFinalize(this); |
| | 0 | 225 | | } |
| | | 226 | | |
| | | 227 | | #endregion |
| | | 228 | | |
| | | 229 | | private void CheckDisposed() |
| | 0 | 230 | | { |
| | 0 | 231 | | ObjectDisposedException.ThrowIf(_disposed, this); |
| | 0 | 232 | | } |
| | | 233 | | } |
| | | 234 | | } |