From 117feaaed68bfd0231c3a38c5a2e55d44147aed6 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 5 Apr 2023 14:13:16 +0200 Subject: [PATCH] WIP --- .../Request/RestRequestExtensions.cs | 2 +- src/RestSharp/RestClient.Async.cs | 73 ++++++++++++++++--- .../RedirectTests.cs | 44 +++++++++++ .../Server/TestServer.cs | 10 +++ 4 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 test/RestSharp.Tests.Integrated/RedirectTests.cs diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs index 2e429d91d..ce33b2611 100644 --- a/src/RestSharp/Request/RestRequestExtensions.cs +++ b/src/RestSharp/Request/RestRequestExtensions.cs @@ -338,7 +338,7 @@ public static RestRequest AddBody(this RestRequest request, object obj, ContentT DataFormat.Json => request.AddJsonBody(obj, contentType), DataFormat.Xml => request.AddXmlBody(obj, contentType), DataFormat.Binary => request.AddParameter(new BodyParameter("", obj, ContentType.Binary)), - _ => request.AddParameter(new BodyParameter("", obj.ToString(), ContentType.Plain)) + _ => request.AddParameter(new BodyParameter("", obj.ToString()!, ContentType.Plain)) }; } diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index 821f4f487..9bd902dba 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -86,16 +86,11 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo throw new ObjectDisposedException(nameof(RestClient)); } - using var requestContent = new RequestContent(this, request); - var authenticator = request.Authenticator ?? Options.Authenticator; if (authenticator != null) await authenticator.Authenticate(this, request).ConfigureAwait(false); var httpMethod = AsHttpMethod(request.Method); var url = this.BuildUri(request); - var message = new HttpRequestMessage(httpMethod, url) { Content = requestContent.BuildContent() }; - message.Headers.Host = Options.BaseHost; - message.Headers.CacheControl = Options.CachePolicy; using var timeoutCts = new CancellationTokenSource(request.Timeout > 0 ? request.Timeout : int.MaxValue); using var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken); @@ -116,11 +111,50 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo headers.AddCookieHeaders(Options.CookieContainer, url); } - message.AddHeaders(headers); + HttpResponseMessage? responseMessage; + + while (true) { + using var requestContent = new RequestContent(this, request); + using var message = PrepareRequestMessage(httpMethod, url, requestContent, headers); - if (request.OnBeforeRequest != null) await request.OnBeforeRequest(message).ConfigureAwait(false); + if (request.OnBeforeRequest != null) await request.OnBeforeRequest(message).ConfigureAwait(false); + + responseMessage = await HttpClient.SendAsync(message, request.CompletionOption, ct).ConfigureAwait(false); + + if (request.OnAfterRequest != null) await request.OnAfterRequest(responseMessage).ConfigureAwait(false); + + if (!IsRedirect(responseMessage)) { + // || !Options.FollowRedirects) { + break; + } + + var location = responseMessage.Headers.Location; + + if (location == null) { + break; + } + + if (!location.IsAbsoluteUri) { + location = new Uri(url, location); + } + + if (responseMessage.StatusCode == HttpStatusCode.RedirectMethod) { + httpMethod = HttpMethod.Get; + } - var responseMessage = await HttpClient.SendAsync(message, request.CompletionOption, ct).ConfigureAwait(false); + url = location; + + if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var ch)) { + foreach (var header in ch) { + try { + cookieContainer.SetCookies(url, header); + } + catch (CookieException) { + // Do not fail request if we cannot parse a cookie + } + } + } + } // Parse all the cookies from the response and update the cookie jar with cookies if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) { @@ -134,8 +168,6 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo } } - if (request.OnAfterRequest != null) await request.OnAfterRequest(responseMessage).ConfigureAwait(false); - return new HttpResponse(responseMessage, url, cookieContainer, null, timeoutCts.Token); } catch (Exception ex) { @@ -143,6 +175,27 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo } } + HttpRequestMessage PrepareRequestMessage(HttpMethod httpMethod, Uri url, RequestContent requestContent, RequestHeaders headers) { + var message = new HttpRequestMessage(httpMethod, url) { Content = requestContent.BuildContent() }; + message.Headers.Host = Options.BaseHost; + message.Headers.CacheControl = Options.CachePolicy; + message.AddHeaders(headers); + + return message; + } + + static bool IsRedirect(HttpResponseMessage responseMessage) + => responseMessage.StatusCode switch { + HttpStatusCode.MovedPermanently => true, + HttpStatusCode.SeeOther => true, + HttpStatusCode.TemporaryRedirect => true, + HttpStatusCode.Redirect => true, +#if NET + HttpStatusCode.PermanentRedirect => true, +#endif + _ => false + }; + record HttpResponse( HttpResponseMessage? ResponseMessage, Uri Url, diff --git a/test/RestSharp.Tests.Integrated/RedirectTests.cs b/test/RestSharp.Tests.Integrated/RedirectTests.cs new file mode 100644 index 000000000..752c5a5cf --- /dev/null +++ b/test/RestSharp.Tests.Integrated/RedirectTests.cs @@ -0,0 +1,44 @@ +using System.Net; +using RestSharp.Tests.Integrated.Server; + +namespace RestSharp.Tests.Integrated; + +[Collection(nameof(TestServerCollection))] +public class RedirectTests { + readonly RestClient _client; + readonly string _host; + + public RedirectTests(TestServerFixture fixture, ITestOutputHelper output) { + var options = new RestClientOptions(fixture.Server.Url) { + FollowRedirects = false + }; + _client = new RestClient(options); + _host = _client.Options.BaseUrl!.Host; + } + + [Fact] + public async Task Can_Perform_GET_Async_With_Redirect() { + const string val = "Works!"; + + var request = new RestRequest("redirect"); + + var response = await _client.ExecuteAsync(request); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Data!.Message.Should().Be(val); + } + + [Fact] + public async Task Can_Perform_GET_Async_With_Request_Cookies() { + var request = new RestRequest("get-cookies-redirect") { + CookieContainer = new CookieContainer(), + }; + request.CookieContainer.Add(new Cookie("cookie", "value", null, _host)); + request.CookieContainer.Add(new Cookie("cookie2", "value2", null, _host)); + var response = await _client.ExecuteAsync(request); + response.Content.Should().Be("[\"cookie=value\",\"cookie2=value2\"]"); + } + + class Response { + public string? Message { get; set; } + } +} diff --git a/test/RestSharp.Tests.Integrated/Server/TestServer.cs b/test/RestSharp.Tests.Integrated/Server/TestServer.cs index 6b34ad7d8..5ce4f98e2 100644 --- a/test/RestSharp.Tests.Integrated/Server/TestServer.cs +++ b/test/RestSharp.Tests.Integrated/Server/TestServer.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using RestSharp.Tests.Integrated.Server.Handlers; using RestSharp.Tests.Shared.Extensions; + // ReSharper disable ConvertClosureToMethodGroup namespace RestSharp.Tests.Integrated.Server; @@ -36,11 +37,20 @@ public HttpServer(ITestOutputHelper? output = null) { _app.MapGet("headers", HeaderHandlers.HandleHeaders); _app.MapGet("request-echo", async context => await context.Request.BodyReader.AsStream().CopyToAsync(context.Response.BodyWriter.AsStream())); _app.MapDelete("delete", () => new TestResponse { Message = "Works!" }); + _app.MapGet("redirect", () => Results.Redirect("/success", false, true)); // Cookies _app.MapGet("get-cookies", CookieHandlers.HandleCookies); _app.MapGet("set-cookies", CookieHandlers.HandleSetCookies); + _app.MapGet( + "get-cookies-redirect", + (HttpContext ctx) => { + ctx.Response.Cookies.Append("redirectCookie", "value1"); + return Results.Redirect("/get-cookies", false, true); + } + ); + // PUT _app.MapPut( ContentResource,