Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NGitLab.Tests/Docker/GitLabTestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static async Task<GitLabTestContext> 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)
{
Expand Down
175 changes: 105 additions & 70 deletions NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,47 +20,48 @@ namespace NGitLab.Tests.Docker;
/// </summary>
internal sealed class GitLabTestContextRequestOptions : RequestOptions
{
private readonly List<WebRequest> _allRequests = [];
private readonly List<HttpRequestMessage> _allRequests = [];
private static readonly SemaphoreSlim s_semaphoreSlim = new(1, 1);

private readonly ConcurrentDictionary<WebRequest, LoggableRequestStream> _pendingRequest = new();
private readonly ConcurrentDictionary<HttpRequestMessage, LoggableRequestStream> _pendingRequest = new();

public IReadOnlyList<WebRequest> AllRequests => _allRequests;
public IReadOnlyList<HttpRequestMessage> AllRequests => _allRequests;

public GitLabTestContextRequestOptions()
: base(retryCount: 0, retryInterval: TimeSpan.FromSeconds(1), isIncremental: true)
{
UserAgent = "NGitLab.Tests/1.0.0";
}

public override void ProcessGitLabRequestResult(GitLabRequestResult result)
public override Task ProcessGitLabRequestResult(GitLabRequestResult result)
{
var request = result.Request;
lock (_allRequests)
{
_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))
Expand All @@ -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));
}
Expand All @@ -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");
}
}
}
Expand All @@ -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;

Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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
Expand Down
14 changes: 8 additions & 6 deletions NGitLab.Tests/HttpRequestorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<GitLabException>(() => 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]
Expand Down Expand Up @@ -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
Expand All @@ -147,7 +148,7 @@ private sealed class MockRequestOptions : RequestOptions

public bool ShouldRetryCalled { get; set; }

public HashSet<WebRequest> HandledRequests { get; } = [];
public HashSet<HttpRequestMessage> HandledRequests { get; } = [];

public MockRequestOptions()
: base(retryCount: 0, retryInterval: TimeSpan.Zero)
Expand All @@ -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;
}
}
}
25 changes: 25 additions & 0 deletions NGitLab/Extensions/HttpExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Loading