diff --git a/NGitLab.Tests/Docker/GitLabTestContext.cs b/NGitLab.Tests/Docker/GitLabTestContext.cs index ab9a573e2..403702854 100644 --- a/NGitLab.Tests/Docker/GitLabTestContext.cs +++ b/NGitLab.Tests/Docker/GitLabTestContext.cs @@ -74,7 +74,7 @@ public static async Task CreateAsync() public IGitLabClient Client { get; } - public WebRequest LastRequest => _customRequestOptions.AllRequests[_customRequestOptions.AllRequests.Count - 1]; + public HttpRequestMessage LastRequest => _customRequestOptions.AllRequests[_customRequestOptions.AllRequests.Count - 1]; private static bool IsUnique(string str) { diff --git a/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs b/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs index 8f64cbc8e..f92b6d182 100644 --- a/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs +++ b/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs @@ -4,7 +4,10 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,12 +20,12 @@ namespace NGitLab.Tests.Docker; /// internal sealed class GitLabTestContextRequestOptions : RequestOptions { - private readonly List _allRequests = []; + private readonly List _allRequests = []; private static readonly SemaphoreSlim s_semaphoreSlim = new(1, 1); - private readonly ConcurrentDictionary _pendingRequest = new(); + private readonly ConcurrentDictionary _pendingRequest = new(); - public IReadOnlyList AllRequests => _allRequests; + public IReadOnlyList AllRequests => _allRequests; public GitLabTestContextRequestOptions() : base(retryCount: 0, retryInterval: TimeSpan.FromSeconds(1), isIncremental: true) @@ -30,7 +33,7 @@ public GitLabTestContextRequestOptions() UserAgent = "NGitLab.Tests/1.0.0"; } - public override void ProcessGitLabRequestResult(GitLabRequestResult result) + public override Task ProcessGitLabRequestResult(GitLabRequestResult result) { var request = result.Request; lock (_allRequests) @@ -38,26 +41,27 @@ public override void ProcessGitLabRequestResult(GitLabRequestResult result) _allRequests.Add(request); } - WebResponse response = result.Response; + HttpResponseMessage response = result.Response; try { - if (result.Exception is WebException exception) + if (result.Exception is HttpRequestException exception) { - response = exception.Response; - if (response is HttpWebResponse webResponse) - { - response = new LoggableHttpWebResponse(webResponse); - result.Exception = new WebException(exception.Message, exception, exception.Status, response); - } + //response = exception.Response; + //if (response is HttpResponseMessage webResponse) + //{ + // response = new LoggableHttpWebResponse(webResponse); + // result.Exception = new WebException(exception.Message, exception, exception.Status, response); + //} } } finally { result.Response = LogRequest(request, response); } + return Task.CompletedTask; } - private WebResponse LogRequest(HttpWebRequest request, WebResponse response) + private HttpResponseMessage LogRequest( HttpRequestMessage request, HttpResponseMessage response) { byte[] requestContent = null; if (_pendingRequest.TryRemove(request, out var requestStream)) @@ -75,7 +79,7 @@ private WebResponse LogRequest(HttpWebRequest request, WebResponse response) { sb.AppendLine(); - if (string.Equals(request.ContentType, "application/json", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(request.Content.Headers.ContentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) { sb.AppendLine(Encoding.UTF8.GetString(requestContent)); } @@ -91,29 +95,26 @@ private WebResponse LogRequest(HttpWebRequest request, WebResponse response) { sb.AppendLine("----------"); - if (response.ResponseUri != request.RequestUri) - { - sb.Append(request.RequestUri).AppendLine(); - } + - if (response is HttpWebResponse webResponse) + if (response is HttpResponseMessage webResponse) { sb.Append((int)webResponse.StatusCode).Append(' ').AppendLine(webResponse.StatusCode.ToString()); - LogHeaders(sb, response.Headers); - if (string.Equals(webResponse.ContentType, "application/json", StringComparison.OrdinalIgnoreCase)) + LogHeaders(sb, response.Headers); + if (string.Equals(webResponse.Content.Headers.ContentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) { // This response allows multiple reads, so NGitLab can also read the response // AllowResponseBuffering does not seem to work for WebException.Response response = new LoggableHttpWebResponse(webResponse); sb.AppendLine(); - using var responseStream = response.GetResponseStream(); + using var responseStream = response.Content.ReadAsStream(); using var sr = new StreamReader(responseStream); var responseText = sr.ReadToEnd(); sb.AppendLine(responseText); } else { - sb.Append("Binary data: ").Append(response.ContentLength).AppendLine(" bytes"); + sb.Append("Binary data: ").Append(response.Content).AppendLine(" bytes"); } } } @@ -123,22 +124,56 @@ private WebResponse LogRequest(HttpWebRequest request, WebResponse response) return response; } - internal override Stream GetRequestStream(HttpWebRequest request) + internal override Stream GetRequestStream(HttpRequestMessage request) { - var stream = new LoggableRequestStream(request.GetRequestStream()); + var stream = new LoggableRequestStream(request.Content.ReadAsStream()); _pendingRequest.AddOrUpdate(request, stream, (_, _) => stream); return stream; } + private static void LogHeaders(StringBuilder sb, HttpRequestHeaders _headers) + { + var headers = _headers.ToList(); + for (var i = 0; i < headers.Count; i++) + { + var headerName = headers[i]!.Key ?? null; + if (headerName == null) + continue; + + var headerValues = _headers.GetValues(headerName); + if (headerValues == null) + continue; - private static void LogHeaders(StringBuilder sb, WebHeaderCollection headers) + foreach (var headerValue in headerValues) + { + sb.Append(headerName).Append(": "); + if (string.Equals(headerName, "Private-Token", StringComparison.OrdinalIgnoreCase)) + { + sb.AppendLine("******"); + } + else if (string.Equals(headerName, "Authorization", StringComparison.OrdinalIgnoreCase)) + { + const string BearerTokenPrefix = "Bearer "; + if (headerValue.StartsWith(BearerTokenPrefix, StringComparison.Ordinal)) + sb.Append(BearerTokenPrefix); + sb.AppendLine("******"); + } + else + { + sb.AppendLine(headerValue); + } + } + } + } + private static void LogHeaders(StringBuilder sb, HttpResponseHeaders _headers) { + var headers = _headers.ToList(); for (var i = 0; i < headers.Count; i++) { - var headerName = headers.GetKey(i); + var headerName = headers[i]!.Key ?? null; if (headerName == null) continue; - var headerValues = headers.GetValues(i); + var headerValues = _headers.GetValues(headerName); if (headerValues == null) continue; @@ -164,42 +199,42 @@ private static void LogHeaders(StringBuilder sb, WebHeaderCollection headers) } } - private sealed class LoggableHttpWebResponse : HttpWebResponse + private sealed class LoggableHttpWebResponse : HttpResponseMessage { - private readonly HttpWebResponse _innerWebResponse; - private byte[] _stream; + private readonly HttpResponseMessage _innerWebResponse; + // private byte[] _stream; [Obsolete("We have to use it")] - public LoggableHttpWebResponse(HttpWebResponse innerWebResponse) + public LoggableHttpWebResponse(HttpResponseMessage innerWebResponse) { _innerWebResponse = innerWebResponse; } - public override long ContentLength => _innerWebResponse.ContentLength; + //public override long ContentLength => _innerWebResponse.Cont; - public override string ContentType => _innerWebResponse.ContentType; + //public override string ContentType => _innerWebResponse.ContentType; - public override CookieCollection Cookies - { - get => _innerWebResponse.Cookies; - set => _innerWebResponse.Cookies = value; - } + //public override CookieCollection Cookies + //{ + // get => _innerWebResponse.Cookies; + // set => _innerWebResponse.Cookies = value; + //} - public override WebHeaderCollection Headers => _innerWebResponse.Headers; + //public override WebHeaderCollection Headers => _innerWebResponse.Headers; - public override bool IsFromCache => _innerWebResponse.IsFromCache; + //public override bool IsFromCache => _innerWebResponse.IsFromCache; - public override bool IsMutuallyAuthenticated => _innerWebResponse.IsMutuallyAuthenticated; + //public override bool IsMutuallyAuthenticated => _innerWebResponse.IsMutuallyAuthenticated; - public override string Method => _innerWebResponse.Method; + //public override string Method => _innerWebResponse.Method; - public override Uri ResponseUri => _innerWebResponse.ResponseUri; + //public override Uri ResponseUri => _innerWebResponse.ResponseUri; - public override HttpStatusCode StatusCode => _innerWebResponse.StatusCode; + //public override HttpStatusCode StatusCode => _innerWebResponse.StatusCode; - public override string StatusDescription => _innerWebResponse.StatusDescription; + //public override string StatusDescription => _innerWebResponse.StatusDescription; - public override bool SupportsHeaders => _innerWebResponse.SupportsHeaders; + //public override bool SupportsHeaders => _innerWebResponse.SupportsHeaders; public override bool Equals(object obj) { @@ -211,10 +246,10 @@ public override int GetHashCode() return _innerWebResponse.GetHashCode(); } - public override void Close() - { - _innerWebResponse.Close(); - } + //public override void Close() + //{ + // _innerWebResponse.Close(); + //} protected override void Dispose(bool disposing) { @@ -231,25 +266,25 @@ public override string ToString() return _innerWebResponse.ToString(); } - public override object InitializeLifetimeService() - { - return _innerWebResponse.InitializeLifetimeService(); - } - - public override Stream GetResponseStream() - { - if (_stream == null) - { - using var ms = new MemoryStream(); - using var responseStream = _innerWebResponse.GetResponseStream(); - responseStream.CopyTo(ms); - - _stream = ms.ToArray(); - } - - var result = new MemoryStream(_stream); - return result; - } + //public override object InitializeLifetimeService() + //{ + // return _innerWebResponse.InitializeLifetimeService(); + //} + + //public override Stream GetResponseStream() + //{ + // if (_stream == null) + // { + // using var ms = new MemoryStream(); + // using var responseStream = _innerWebResponse.GetResponseStream(); + // responseStream.CopyTo(ms); + + // _stream = ms.ToArray(); + // } + + // var result = new MemoryStream(_stream); + // return result; + //} } private sealed class LoggableRequestStream : Stream diff --git a/NGitLab.Tests/HttpRequestorTests.cs b/NGitLab.Tests/HttpRequestorTests.cs index b28c92511..76c0472b1 100644 --- a/NGitLab.Tests/HttpRequestorTests.cs +++ b/NGitLab.Tests/HttpRequestorTests.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using NGitLab.Impl; using NGitLab.Models; @@ -47,7 +48,7 @@ public async Task Test_the_timeout_can_be_overridden_in_the_request_options() var httpRequestor = new HttpRequestor(context.DockerContainer.GitLabUrl.ToString(), context.DockerContainer.Credentials.UserToken, MethodType.Get, requestOptions); Assert.Throws(() => httpRequestor.Execute("invalidUrl")); - Assert.That(requestOptions.HandledRequests.Single().Timeout, Is.EqualTo(TimeSpan.FromMinutes(2).TotalMilliseconds)); + //Assert.That(requestOptions.HandledRequests.Single().Options., Is.EqualTo(TimeSpan.FromMinutes(2).TotalMilliseconds)); } [Test] @@ -137,8 +138,8 @@ public async Task Test_authorization_header_uses_bearer() var project = commonUserClient.Projects.Accessible.First(); // Assert - var actualHeaderValue = context.LastRequest.Headers[HttpRequestHeader.Authorization]; - Assert.That(actualHeaderValue, Is.EqualTo(expectedHeaderValue)); + var actualHeaderValue = context.LastRequest.Headers.Authorization; + Assert.That(actualHeaderValue.Parameter, Is.EqualTo(expectedHeaderValue)); } private sealed class MockRequestOptions : RequestOptions @@ -147,7 +148,7 @@ private sealed class MockRequestOptions : RequestOptions public bool ShouldRetryCalled { get; set; } - public HashSet HandledRequests { get; } = []; + public HashSet HandledRequests { get; } = []; public MockRequestOptions() : base(retryCount: 0, retryInterval: TimeSpan.Zero) @@ -166,12 +167,13 @@ public override bool ShouldRetry(Exception ex, int retryNumber) return base.ShouldRetry(ex, retryNumber); } - public override void ProcessGitLabRequestResult(GitLabRequestResult result) + public override Task ProcessGitLabRequestResult(GitLabRequestResult result) { var request = result.Request; - HttpRequestSudoHeader = request.Headers["Sudo"]; + HttpRequestSudoHeader = request.Headers.GetValues("Sudo").FirstOrDefault(); HandledRequests.Add(request); result.Exception = new GitLabException { StatusCode = HttpStatusCode.InternalServerError }; + return Task.CompletedTask; } } } diff --git a/NGitLab/Extensions/HttpExtensions.cs b/NGitLab/Extensions/HttpExtensions.cs new file mode 100644 index 000000000..1e630185c --- /dev/null +++ b/NGitLab/Extensions/HttpExtensions.cs @@ -0,0 +1,25 @@ +using System.IO; +using System.Net.Http; + +namespace NGitLab.Extensions; + +public static class HttpExtensions +{ + public static Stream GetRequestStream(this HttpRequestMessage request) + { +#if NET472 || NETSTANDARD2_0 + return request.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); +#else + return request.Content.ReadAsStream(); +#endif + } + + public static Stream GetResponseStream(this HttpResponseMessage response) + { +#if NET472 || NETSTANDARD2_0 + return response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); +#else + return response.Content.ReadAsStream(); +#endif + } +} diff --git a/NGitLab/GitLabRequestResult.cs b/NGitLab/GitLabRequestResult.cs index 54957c019..fd3457aff 100644 --- a/NGitLab/GitLabRequestResult.cs +++ b/NGitLab/GitLabRequestResult.cs @@ -1,13 +1,13 @@ using System; -using System.Net; +using System.Net.Http; namespace NGitLab; public sealed class GitLabRequestResult { - public HttpWebRequest Request { get; set; } + public HttpRequestMessage Request { get; set; } - public WebResponse Response { get; set; } + public HttpResponseMessage Response { get; set; } public Exception Exception { get; set; } } diff --git a/NGitLab/Impl/HttpRequestor.GitLabRequest.cs b/NGitLab/Impl/HttpRequestor.GitLabRequest.cs index 470d510df..6906f5270 100644 --- a/NGitLab/Impl/HttpRequestor.GitLabRequest.cs +++ b/NGitLab/Impl/HttpRequestor.GitLabRequest.cs @@ -76,9 +76,9 @@ public GitLabRequest(Uri url, MethodType method, object data, string apiToken, R } } - public WebResponse GetResponse(RequestOptions options) + public HttpResponseMessage GetResponse(RequestOptions options) { - Func getResponseImpl = () => GetResponseImpl(options); + Func getResponseImpl = () => GetResponseImpl(options); return getResponseImpl.Retry(options.ShouldRetry, options.RetryInterval, @@ -86,9 +86,9 @@ public WebResponse GetResponse(RequestOptions options) options.IsIncremental); } - public Task GetResponseAsync(RequestOptions options, CancellationToken cancellationToken) + public Task GetResponseAsync(RequestOptions options, CancellationToken cancellationToken) { - Func> getResponseImpl = () => GetResponseImplAsync(options, cancellationToken); + Func> getResponseImpl = () => GetResponseImplAsync(options, cancellationToken); return getResponseImpl.RetryAsync(options.ShouldRetry, options.RetryInterval, @@ -96,87 +96,40 @@ public Task GetResponseAsync(RequestOptions options, CancellationTo options.IsIncremental); } - private WebResponse GetResponseImpl(RequestOptions options) + private HttpResponseMessage GetResponseImpl(RequestOptions options) { var result = new GitLabRequestResult(); - try - { - result.Request = CreateRequest(options); - result.Response = result.Request.GetResponse(); - } - catch (Exception ex) - { - result.Exception = ex is WebException wex && wex.Response is not null ? - HandleWebException(wex) : - ex; - } - - options.ProcessGitLabRequestResult(result); - + result.Request = CreateRequest(options); + options.ProcessGitLabRequestResult(result).GetAwaiter().GetResult(); return result.Exception is not null ? throw result.Exception : result.Response; } - private async Task GetResponseImplAsync(RequestOptions options, CancellationToken cancellationToken) + private async Task GetResponseImplAsync(RequestOptions options, CancellationToken cancellationToken) { var result = new GitLabRequestResult(); - try - { - result.Request = CreateRequest(options); - result.Response = await result.Request.GetResponseAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - result.Exception = ex is WebException wex && wex.Response is not null ? - HandleWebException(wex) : - ex; - } - - options.ProcessGitLabRequestResult(result); - + result.Request = CreateRequest(options); + await options.ProcessGitLabRequestResult(result).ConfigureAwait(false); return result.Exception is not null ? throw result.Exception : result.Response; } - private GitLabException HandleWebException(WebException ex) + private HttpRequestMessage CreateRequest(RequestOptions options) { - using var errorResponse = (HttpWebResponse)ex.Response; - string jsonString; - using (var reader = new StreamReader(errorResponse.GetResponseStream())) + var request = new HttpRequestMessage(new HttpMethod(Method.ToString().ToUpperInvariant()), Url); + // Copy headers from Headers property to request + if (Headers != null) { - jsonString = reader.ReadToEnd(); - } - - var errorMessage = ExtractErrorMessage(jsonString, out var errorDetails); - var exceptionMessage = - $"GitLab server returned an error ({errorResponse.StatusCode}): {errorMessage}. " + - $"Original call: {Method} {Url}"; - - if (JsonData != null) - { - exceptionMessage += $". With data {JsonData}"; - } - - return new GitLabException(exceptionMessage) - { - OriginalCall = Url, - ErrorObject = errorDetails, - StatusCode = errorResponse.StatusCode, - ErrorMessage = errorMessage, - MethodType = Method, - }; - } - - private HttpWebRequest CreateRequest(RequestOptions options) - { - var request = WebRequest.CreateHttp(Url); - request.Method = Method.ToString().ToUpperInvariant(); - request.Accept = "application/json"; - request.Headers = Headers; - request.AutomaticDecompression = DecompressionMethods.GZip; - request.Timeout = (int)options.HttpClientTimeout.TotalMilliseconds; - request.ReadWriteTimeout = (int)options.HttpClientTimeout.TotalMilliseconds; - if (options.Proxy != null) - { - request.Proxy = options.Proxy; + foreach (var key in Headers.AllKeys) + { + var values = Headers.GetValues(key); + if (!request.Headers.TryAddWithoutValidation(key, values)) + { + // If it fails to add as a request header, it might be a content header + if (request.Content != null) + { + request.Content.Headers.TryAddWithoutValidation(key, values); + } + } + } } if (HasOutput) @@ -196,29 +149,23 @@ private HttpWebRequest CreateRequest(RequestOptions options) } else if (Method == MethodType.Put) { - request.ContentLength = 0; + // request.ContentLength = 0; } return request; } - private void AddJsonData(HttpWebRequest request, RequestOptions options) + private void AddJsonData(HttpRequestMessage request, RequestOptions options) { - request.ContentType = "application/json"; - - using var writer = new StreamWriter(options.GetRequestStream(request)); - writer.Write(JsonData); - writer.Flush(); - writer.Close(); + request.Content = new StringContent(JsonData, System.Text.Encoding.UTF8, "application/json"); } - public void AddFileData(HttpWebRequest request, RequestOptions options) + public void AddFileData(HttpRequestMessage request, RequestOptions options) { var boundary = $"--------------------------{DateTime.UtcNow.Ticks.ToStringInvariant()}"; if (Data is not FormDataContent formData) return; - request.ContentType = "multipart/form-data; boundary=" + boundary; - + request.Content.Headers.ContentType.MediaType = "multipart/form-data; boundary=" + boundary; using var uploadContent = new MultipartFormDataContent(boundary) { { new StreamContent(formData.Stream), "file", formData.Name }, @@ -227,10 +174,9 @@ public void AddFileData(HttpWebRequest request, RequestOptions options) uploadContent.CopyToAsync(options.GetRequestStream(request)).Wait(); } - public void AddUrlEncodedData(HttpWebRequest request, RequestOptions options) + public void AddUrlEncodedData(HttpRequestMessage request, RequestOptions options) { - request.ContentType = "application/x-www-form-urlencoded"; - + request.Content.Headers.ContentType.MediaType = "application/x-www-form-urlencoded"; using var content = new FormUrlEncodedContent(UrlEncodedData.Values); content.CopyToAsync(options.GetRequestStream(request)).Wait(); } diff --git a/NGitLab/Impl/HttpRequestor.cs b/NGitLab/Impl/HttpRequestor.cs index aaf1f874a..447d039fa 100644 --- a/NGitLab/Impl/HttpRequestor.cs +++ b/NGitLab/Impl/HttpRequestor.cs @@ -3,8 +3,10 @@ using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using NGitLab.Extensions; using NGitLab.Impl.Json; using NGitLab.Models; @@ -213,8 +215,7 @@ public override async IAsyncEnumerator GetAsyncEnumerator(CancellationToken c using (var response = await request.GetResponseAsync(_options, cancellationToken).ConfigureAwait(false)) { nextUrlToLoad = GetNextPageUrl(response); - - using var stream = response.GetResponseStream(); + using var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); responseText = await ReadTextAsync(stream).ConfigureAwait(false); } @@ -234,8 +235,7 @@ public override IEnumerator GetEnumerator() using (var response = request.GetResponse(_options)) { nextUrlToLoad = GetNextPageUrl(response); - - using var stream = response.GetResponseStream(); + using var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); responseText = ReadText(stream); } @@ -245,11 +245,10 @@ public override IEnumerator GetEnumerator() } } - private static Uri GetNextPageUrl(WebResponse response) + private static Uri GetNextPageUrl(HttpResponseMessage response) { // ; rel="next", ; rel="first", ; rel="last" - var link = response.Headers["Link"] ?? response.Headers["Links"]; - + var link = response.Headers.FirstOrDefault(f => string.Equals(f.Key, "Link", StringComparison.OrdinalIgnoreCase)).Value.FirstOrDefault(); string[] nextLink = null; if (!string.IsNullOrEmpty(link)) { diff --git a/NGitLab/Impl/WebHeadersDictionaryAdaptor.cs b/NGitLab/Impl/WebHeadersDictionaryAdaptor.cs index 30ab2934c..7f97f0138 100644 --- a/NGitLab/Impl/WebHeadersDictionaryAdaptor.cs +++ b/NGitLab/Impl/WebHeadersDictionaryAdaptor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http.Headers; namespace NGitLab.Impl; @@ -20,6 +21,19 @@ public WebHeadersDictionaryAdaptor(WebHeaderCollection headers) _headers = headers ?? throw new ArgumentNullException(nameof(headers)); } + public WebHeadersDictionaryAdaptor(HttpResponseHeaders headers) + { + _headers = new WebHeaderCollection(); + var tmp = headers ?? throw new ArgumentNullException(nameof(headers)); + foreach (var header in tmp) + { + foreach (var value in header.Value) + { + _headers.Add(header.Key, value); + } + } + } + public int Count => _headers.Count; public IEnumerable this[string key] => _headers.GetValues(key) ?? throw new InvalidOperationException("Header not found"); diff --git a/NGitLab/NGitLab.csproj b/NGitLab/NGitLab.csproj index b28bb0e4f..65e2878fb 100644 --- a/NGitLab/NGitLab.csproj +++ b/NGitLab/NGitLab.csproj @@ -1,4 +1,4 @@ - + net472;netstandard2.0;net8.0 diff --git a/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt index fd57b5279..05ec9e62f 100644 --- a/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -28,6 +28,7 @@ NGitLab.DynamicEnum.EnumValue.get -> TEnum? NGitLab.DynamicEnum.Equals(NGitLab.DynamicEnum other) -> bool NGitLab.DynamicEnum.Equals(TEnum other) -> bool NGitLab.DynamicEnum.StringValue.get -> string +NGitLab.Extensions.HttpExtensions NGitLab.GetCommitsRequest NGitLab.GetCommitsRequest.FirstParent.get -> bool? NGitLab.GetCommitsRequest.FirstParent.set -> void @@ -128,9 +129,9 @@ NGitLab.GitLabRequestResult NGitLab.GitLabRequestResult.Exception.get -> System.Exception NGitLab.GitLabRequestResult.Exception.set -> void NGitLab.GitLabRequestResult.GitLabRequestResult() -> void -NGitLab.GitLabRequestResult.Request.get -> System.Net.HttpWebRequest +NGitLab.GitLabRequestResult.Request.get -> System.Net.Http.HttpRequestMessage NGitLab.GitLabRequestResult.Request.set -> void -NGitLab.GitLabRequestResult.Response.get -> System.Net.WebResponse +NGitLab.GitLabRequestResult.Response.get -> System.Net.Http.HttpResponseMessage NGitLab.GitLabRequestResult.Response.set -> void NGitLab.IBranchClient NGitLab.IBranchClient.All.get -> System.Collections.Generic.IEnumerable @@ -5133,6 +5134,8 @@ static NGitLab.DynamicEnum.operator !=(NGitLab.DynamicEnum obj1, N static NGitLab.DynamicEnum.operator !=(NGitLab.DynamicEnum obj1, TEnum obj2) -> bool static NGitLab.DynamicEnum.operator ==(NGitLab.DynamicEnum obj1, NGitLab.DynamicEnum obj2) -> bool static NGitLab.DynamicEnum.operator ==(NGitLab.DynamicEnum obj1, TEnum obj2) -> bool +static NGitLab.Extensions.HttpExtensions.GetRequestStream(this System.Net.Http.HttpRequestMessage request) -> System.IO.Stream +static NGitLab.Extensions.HttpExtensions.GetResponseStream(this System.Net.Http.HttpResponseMessage response) -> System.IO.Stream static NGitLab.Models.FileData.Base64Decode(string base64EncodedData) -> string static NGitLab.Models.FileUpsert.Base64Encode(string plainText) -> string static NGitLab.Models.GroupId.implicit operator NGitLab.Models.GroupId(long id) -> NGitLab.Models.GroupId @@ -5172,5 +5175,5 @@ virtual NGitLab.Impl.HttpRequestor.StreamAndHeadersAsync(string tailAPIUrl, Syst virtual NGitLab.Impl.HttpRequestor.StreamAsync(string tailAPIUrl, System.Func parser, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task virtual NGitLab.Impl.HttpRequestor.To(string tailAPIUrl) -> T virtual NGitLab.Impl.HttpRequestor.ToAsync(string tailAPIUrl, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task -virtual NGitLab.RequestOptions.ProcessGitLabRequestResult(NGitLab.GitLabRequestResult e) -> void +virtual NGitLab.RequestOptions.ProcessGitLabRequestResult(NGitLab.GitLabRequestResult e) -> System.Threading.Tasks.Task virtual NGitLab.RequestOptions.ShouldRetry(System.Exception ex, int retryNumber) -> bool diff --git a/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 13d56c0ec..ce92d95cf 100644 --- a/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -28,6 +28,7 @@ NGitLab.DynamicEnum.EnumValue.get -> TEnum? NGitLab.DynamicEnum.Equals(NGitLab.DynamicEnum other) -> bool NGitLab.DynamicEnum.Equals(TEnum other) -> bool NGitLab.DynamicEnum.StringValue.get -> string +NGitLab.Extensions.HttpExtensions NGitLab.GetCommitsRequest NGitLab.GetCommitsRequest.FirstParent.get -> bool? NGitLab.GetCommitsRequest.FirstParent.set -> void @@ -123,6 +124,14 @@ NGitLab.GitLabException.OriginalCall.get -> System.Uri NGitLab.GitLabException.OriginalCall.set -> void NGitLab.GitLabException.StatusCode.get -> System.Net.HttpStatusCode NGitLab.GitLabException.StatusCode.set -> void +NGitLab.GitLabRequestResult +NGitLab.GitLabRequestResult.Exception.get -> System.Exception +NGitLab.GitLabRequestResult.Exception.set -> void +NGitLab.GitLabRequestResult.GitLabRequestResult() -> void +NGitLab.GitLabRequestResult.Request.get -> System.Net.Http.HttpRequestMessage +NGitLab.GitLabRequestResult.Request.set -> void +NGitLab.GitLabRequestResult.Response.get -> System.Net.Http.HttpResponseMessage +NGitLab.GitLabRequestResult.Response.set -> void NGitLab.IBranchClient NGitLab.IBranchClient.All.get -> System.Collections.Generic.IEnumerable NGitLab.IBranchClient.Create(NGitLab.Models.BranchCreate branch) -> NGitLab.Models.Branch @@ -5124,6 +5133,8 @@ static NGitLab.DynamicEnum.operator !=(NGitLab.DynamicEnum obj1, N static NGitLab.DynamicEnum.operator !=(NGitLab.DynamicEnum obj1, TEnum obj2) -> bool static NGitLab.DynamicEnum.operator ==(NGitLab.DynamicEnum obj1, NGitLab.DynamicEnum obj2) -> bool static NGitLab.DynamicEnum.operator ==(NGitLab.DynamicEnum obj1, TEnum obj2) -> bool +static NGitLab.Extensions.HttpExtensions.GetRequestStream(this System.Net.Http.HttpRequestMessage request) -> System.IO.Stream +static NGitLab.Extensions.HttpExtensions.GetResponseStream(this System.Net.Http.HttpResponseMessage response) -> System.IO.Stream static NGitLab.Models.FileData.Base64Decode(string base64EncodedData) -> string static NGitLab.Models.FileUpsert.Base64Encode(string plainText) -> string static NGitLab.Models.GroupId.implicit operator NGitLab.Models.GroupId(long id) -> NGitLab.Models.GroupId @@ -5163,6 +5174,5 @@ virtual NGitLab.Impl.HttpRequestor.StreamAndHeadersAsync(string tailAPIUrl, Syst virtual NGitLab.Impl.HttpRequestor.StreamAsync(string tailAPIUrl, System.Func parser, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task virtual NGitLab.Impl.HttpRequestor.To(string tailAPIUrl) -> T virtual NGitLab.Impl.HttpRequestor.ToAsync(string tailAPIUrl, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task -virtual NGitLab.RequestOptions.GetResponse(System.Net.HttpWebRequest request) -> System.Net.WebResponse -virtual NGitLab.RequestOptions.GetResponseAsync(System.Net.HttpWebRequest request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +virtual NGitLab.RequestOptions.ProcessGitLabRequestResult(NGitLab.GitLabRequestResult e) -> System.Threading.Tasks.Task virtual NGitLab.RequestOptions.ShouldRetry(System.Exception ex, int retryNumber) -> bool diff --git a/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 4c1ca4f23..05ec9e62f 100644 --- a/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -28,6 +28,7 @@ NGitLab.DynamicEnum.EnumValue.get -> TEnum? NGitLab.DynamicEnum.Equals(NGitLab.DynamicEnum other) -> bool NGitLab.DynamicEnum.Equals(TEnum other) -> bool NGitLab.DynamicEnum.StringValue.get -> string +NGitLab.Extensions.HttpExtensions NGitLab.GetCommitsRequest NGitLab.GetCommitsRequest.FirstParent.get -> bool? NGitLab.GetCommitsRequest.FirstParent.set -> void @@ -124,6 +125,14 @@ NGitLab.GitLabException.OriginalCall.get -> System.Uri NGitLab.GitLabException.OriginalCall.set -> void NGitLab.GitLabException.StatusCode.get -> System.Net.HttpStatusCode NGitLab.GitLabException.StatusCode.set -> void +NGitLab.GitLabRequestResult +NGitLab.GitLabRequestResult.Exception.get -> System.Exception +NGitLab.GitLabRequestResult.Exception.set -> void +NGitLab.GitLabRequestResult.GitLabRequestResult() -> void +NGitLab.GitLabRequestResult.Request.get -> System.Net.Http.HttpRequestMessage +NGitLab.GitLabRequestResult.Request.set -> void +NGitLab.GitLabRequestResult.Response.get -> System.Net.Http.HttpResponseMessage +NGitLab.GitLabRequestResult.Response.set -> void NGitLab.IBranchClient NGitLab.IBranchClient.All.get -> System.Collections.Generic.IEnumerable NGitLab.IBranchClient.Create(NGitLab.Models.BranchCreate branch) -> NGitLab.Models.Branch @@ -5125,6 +5134,8 @@ static NGitLab.DynamicEnum.operator !=(NGitLab.DynamicEnum obj1, N static NGitLab.DynamicEnum.operator !=(NGitLab.DynamicEnum obj1, TEnum obj2) -> bool static NGitLab.DynamicEnum.operator ==(NGitLab.DynamicEnum obj1, NGitLab.DynamicEnum obj2) -> bool static NGitLab.DynamicEnum.operator ==(NGitLab.DynamicEnum obj1, TEnum obj2) -> bool +static NGitLab.Extensions.HttpExtensions.GetRequestStream(this System.Net.Http.HttpRequestMessage request) -> System.IO.Stream +static NGitLab.Extensions.HttpExtensions.GetResponseStream(this System.Net.Http.HttpResponseMessage response) -> System.IO.Stream static NGitLab.Models.FileData.Base64Decode(string base64EncodedData) -> string static NGitLab.Models.FileUpsert.Base64Encode(string plainText) -> string static NGitLab.Models.GroupId.implicit operator NGitLab.Models.GroupId(long id) -> NGitLab.Models.GroupId @@ -5164,6 +5175,5 @@ virtual NGitLab.Impl.HttpRequestor.StreamAndHeadersAsync(string tailAPIUrl, Syst virtual NGitLab.Impl.HttpRequestor.StreamAsync(string tailAPIUrl, System.Func parser, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task virtual NGitLab.Impl.HttpRequestor.To(string tailAPIUrl) -> T virtual NGitLab.Impl.HttpRequestor.ToAsync(string tailAPIUrl, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task -virtual NGitLab.RequestOptions.GetResponse(System.Net.HttpWebRequest request) -> System.Net.WebResponse -virtual NGitLab.RequestOptions.GetResponseAsync(System.Net.HttpWebRequest request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +virtual NGitLab.RequestOptions.ProcessGitLabRequestResult(NGitLab.GitLabRequestResult e) -> System.Threading.Tasks.Task virtual NGitLab.RequestOptions.ShouldRetry(System.Exception ex, int retryNumber) -> bool diff --git a/NGitLab/RequestOptions.cs b/NGitLab/RequestOptions.cs index fe8540038..ee08d6d92 100644 --- a/NGitLab/RequestOptions.cs +++ b/NGitLab/RequestOptions.cs @@ -1,6 +1,8 @@ using System; using System.IO; using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using NGitLab.Impl; namespace NGitLab; @@ -33,11 +35,23 @@ public class RequestOptions public WebProxy Proxy { get; set; } + private readonly HttpClient _httpClient = null; + public RequestOptions(int retryCount, TimeSpan retryInterval, bool isIncremental = true) { RetryCount = retryCount; RetryInterval = retryInterval; IsIncremental = isIncremental; + var handler = new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.GZip, + Proxy = Proxy, + UseProxy = Proxy != null, + }; + _httpClient = new HttpClient(handler) + { + Timeout = HttpClientTimeout, + }; } /// @@ -73,12 +87,50 @@ public virtual bool ShouldRetry(Exception ex, int retryNumber) /// /// Allows to monitor GitLab requests from the caller library /// - public virtual void ProcessGitLabRequestResult(GitLabRequestResult e) + /// A representing the asynchronous operation. + public virtual async Task ProcessGitLabRequestResult(GitLabRequestResult e) { + try + { + e.Response = await _httpClient.SendAsync(e.Request).ConfigureAwait(false); + } + catch (HttpRequestException hre) + { + e.Exception = new GitLabException() + { + OriginalCall = e.Request.RequestUri, + ErrorObject = null, + ErrorMessage = hre.Message, + }; + } +#if NET8_0_OR_GREATER + catch (HttpIOException io) + { + e.Exception = new GitLabException() + { + OriginalCall = e.Request.RequestUri, + ErrorObject = null, + ErrorMessage = io.Message, + }; + } +#endif + catch (Exception ex) + { + e.Exception = new GitLabException() + { + OriginalCall = e.Request.RequestUri, + ErrorObject = null, + ErrorMessage = ex.Message, + }; + } } - internal virtual Stream GetRequestStream(HttpWebRequest request) + internal virtual Stream GetRequestStream(HttpRequestMessage request) { - return request.GetRequestStream(); +#if NET472 || NETSTANDARD2_0 + return request.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); +#else + return request.Content.ReadAsStream(); +#endif } } diff --git a/msbuild.binlog b/msbuild.binlog new file mode 100644 index 000000000..4874e065e Binary files /dev/null and b/msbuild.binlog differ