Skip to content

Commit 86edf43

Browse files
authored
Merge pull request #27 from AMDevIT/Task-RawCalls
Added support for raw calls with text/plain or customized media type, and for form encoded calls. Also, Restling now doesn't follow automatically redirect by default, allowing to use retry patterns to follow redirects with an authentication header set.
2 parents 5aea003 + 8c144fa commit 86edf43

10 files changed

Lines changed: 842 additions & 47 deletions

File tree

Sources/AMDevIT.Restling/AMDevIT.Restling.Core/AMDevIT.Restling.Core.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
<PackageReadmeFile>README.md</PackageReadmeFile>
1616
<RepositoryUrl>https://github.com/AMDevIT/Restling.git</RepositoryUrl>
1717
<PackageTags>rest;httpclient;api;apiclient;client;restclient;restling</PackageTags>
18-
<AssemblyVersion>1.0.18.10</AssemblyVersion>
18+
<AssemblyVersion>1.0.19.0</AssemblyVersion>
1919
<FileVersion>$(AssemblyVersion)</FileVersion>
20-
<PackageReleaseNotes>Added a property used to force json serializer for the HTTP Rest call. Also, it's possibile to define the default JSON serializer for the restling client as a global property.
20+
<PackageReleaseNotes>Added support for raw content requests and form url encoded requests. Also changed behaviours of redirects, now Restling doesn't follow redirects automatically.
2121
</PackageReleaseNotes>
2222
<PackageLicenseFile>LICENSE</PackageLicenseFile>
2323
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using AMDevIT.Restling.Core.Network;
2+
3+
namespace AMDevIT.Restling.Core
4+
{
5+
/// <summary>
6+
/// Represents a request that will be sent as a non rest compliant form-urlencoded request.
7+
/// </summary>
8+
public class FormUrlEncodedRequest
9+
: RestRequest
10+
{
11+
#region Fields
12+
13+
private readonly IDictionary<string, string> parameters = new Dictionary<string, string>();
14+
15+
#endregion
16+
17+
#region Properties
18+
19+
public IDictionary<string, string> Parameters => this.parameters;
20+
21+
#endregion
22+
23+
#region .ctor
24+
25+
public FormUrlEncodedRequest(string uri,
26+
HttpMethod method,
27+
string? customMethod = null)
28+
: base(uri, method, customMethod)
29+
{
30+
}
31+
32+
public FormUrlEncodedRequest(string uri,
33+
HttpMethod method,
34+
RequestHeaders headers,
35+
string? customMethod = null)
36+
: base(uri, method, headers, customMethod)
37+
{
38+
}
39+
40+
public FormUrlEncodedRequest(string uri,
41+
HttpMethod method,
42+
IDictionary<string, string> parameters,
43+
string? customMethod = null)
44+
: this(uri, method, customMethod)
45+
{
46+
this.parameters = parameters;
47+
}
48+
49+
public FormUrlEncodedRequest(string uri,
50+
HttpMethod method,
51+
RequestHeaders headers,
52+
IDictionary<string, string> parameters,
53+
string? customMethod = null)
54+
: this(uri, method, headers, customMethod)
55+
{
56+
this.parameters = parameters;
57+
}
58+
59+
#endregion
60+
}
61+
}

Sources/AMDevIT.Restling/AMDevIT.Restling.Core/HttpResponseParser.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,22 @@ public async Task<RestRequestResult> DecodeAsync(HttpResponseMessage resultHttpM
4242
{
4343
byte[] rawContent;
4444
RetrievedContentResult retrievedContent;
45+
ResponseHeaders responseHeaders;
4546
MediaTypeHeaderValue? contentType = resultHttpMessage.Content.Headers.ContentType;
4647
Charset charset = CharsetParser.Parse(contentType?.CharSet);
4748
rawContent = await resultHttpMessage.Content.ReadAsByteArrayAsync(cancellationToken);
4849
retrievedContent = RetrieveContent(rawContent, contentType);
50+
51+
responseHeaders = ResponseHeaders.Create(resultHttpMessage.Headers);
4952

5053
restRequestResult = new(restRequest,
5154
resultHttpMessage.StatusCode,
5255
elapsed,
5356
rawContent,
5457
contentType?.MediaType,
5558
charset,
56-
retrievedContent);
59+
retrievedContent,
60+
responseHeaders);
5761
}
5862
else
5963
{
@@ -79,6 +83,7 @@ public async Task<RestRequestResult<T>> DecodeAsync<T>(HttpResponseMessage resul
7983
byte[] rawContent;
8084
T? data = default;
8185
RetrievedContentResult content;
86+
ResponseHeaders responseHeaders;
8287
MediaTypeHeaderValue? contentType = resultHttpMessage.Content.Headers.ContentType;
8388
Charset charset = CharsetParser.Parse(contentType?.CharSet);
8489
rawContent = await resultHttpMessage.Content.ReadAsByteArrayAsync(cancellationToken);
@@ -108,14 +113,16 @@ public async Task<RestRequestResult<T>> DecodeAsync<T>(HttpResponseMessage resul
108113
// If not, ignore the exception.
109114
}
110115

116+
responseHeaders = ResponseHeaders.Create(resultHttpMessage.Headers);
111117
restRequestResult = new(restRequest,
112118
data,
113119
resultHttpMessage.StatusCode,
114120
elapsed,
115121
rawContent,
116122
contentType?.MediaType,
117123
charset,
118-
content);
124+
content,
125+
responseHeaders);
119126
}
120127
else
121128
{

Sources/AMDevIT.Restling/AMDevIT.Restling.Core/Network/Builders/HttpClientContextBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ public HttpClientContext Build()
218218
SocketsHttpHandler socketsHttpHandler = new()
219219
{
220220
CookieContainer = this.cookieContainer,
221-
UseCookies = true
221+
UseCookies = true,
222+
AllowAutoRedirect = false
222223
};
223224
this.httpMessageHandler = socketsHttpHandler;
224225
this.disposeHandler = true;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Collections.ObjectModel;
2+
using System.Net.Http.Headers;
3+
4+
namespace AMDevIT.Restling.Core.Network
5+
{
6+
public class ResponseHeaders
7+
{
8+
#region Fields
9+
10+
private static readonly ResponseHeaders empty = new();
11+
12+
private readonly ReadOnlyDictionary<string, IEnumerable<string>> headers;
13+
private readonly string? redirectLocation;
14+
15+
#endregion
16+
17+
#region Properties
18+
19+
public static ResponseHeaders Empty => empty;
20+
21+
public ReadOnlyDictionary<string, IEnumerable<string>> Headers => this.headers;
22+
23+
public string? RedirectLocation => this.redirectLocation;
24+
25+
#endregion
26+
27+
#region .ctor
28+
29+
protected ResponseHeaders()
30+
{
31+
Dictionary<string, IEnumerable<string>> headers = [];
32+
this.headers = new ReadOnlyDictionary<string, IEnumerable<string>>(headers);
33+
this.redirectLocation = null;
34+
}
35+
36+
protected ResponseHeaders(HttpResponseHeaders sourceResponseHeaders)
37+
{
38+
Dictionary<string, IEnumerable<string>> headers = [];
39+
40+
foreach (KeyValuePair<string, IEnumerable<string>> header in sourceResponseHeaders)
41+
{
42+
headers.Add(header.Key, header.Value);
43+
}
44+
45+
this.headers = new ReadOnlyDictionary<string, IEnumerable<string>>(headers);
46+
this.redirectLocation = sourceResponseHeaders.Location?.ToString();
47+
}
48+
49+
#endregion
50+
51+
#region Methods
52+
53+
public static ResponseHeaders Create(HttpResponseHeaders httpResponseMessage)
54+
{
55+
ResponseHeaders responseHeaders = new(httpResponseMessage);
56+
return responseHeaders;
57+
}
58+
59+
public override string ToString()
60+
{
61+
string headers;
62+
63+
if (this.headers.Count > 0)
64+
headers = string.Join(", ", this.headers.Select(h => $"{h.Key}: {h.Value}"));
65+
else
66+
headers = "No headers";
67+
68+
return $"Headers: {headers} - Redirect location: {this.redirectLocation ?? "No redirect location provided"}";
69+
}
70+
71+
#endregion
72+
}
73+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using AMDevIT.Restling.Core.Network;
2+
3+
namespace AMDevIT.Restling.Core
4+
{
5+
public class RestRawRequest
6+
: RestRequest
7+
{
8+
#region Properties
9+
10+
public string? Content
11+
{
12+
get;
13+
set;
14+
}
15+
16+
public string? AcceptType
17+
{
18+
get;
19+
set;
20+
}
21+
22+
public string? ContentType
23+
{
24+
get;
25+
set;
26+
}
27+
28+
#endregion
29+
30+
#region .ctor
31+
32+
public RestRawRequest(string uri,
33+
HttpMethod method,
34+
string? customMethod = null)
35+
: base(uri, method, customMethod)
36+
{
37+
}
38+
39+
public RestRawRequest(string uri,
40+
HttpMethod method,
41+
RequestHeaders headers,
42+
string? customMethod = null)
43+
: base(uri, method, headers, customMethod)
44+
{
45+
}
46+
47+
public RestRawRequest(string uri,
48+
HttpMethod method,
49+
string? content,
50+
string? contentType,
51+
string? customMethod = null)
52+
: base(uri, method, customMethod)
53+
{
54+
this.Content = content;
55+
this.ContentType = contentType;
56+
}
57+
58+
public RestRawRequest(string uri,
59+
HttpMethod method,
60+
string? content,
61+
string? customMethod = null)
62+
: this(uri, method, content: content, contentType: null, customMethod)
63+
{
64+
}
65+
66+
public RestRawRequest(string uri,
67+
HttpMethod method,
68+
string? content,
69+
RequestHeaders headers,
70+
string? customMethod = null)
71+
: this(uri,
72+
method,
73+
content: content,
74+
contentType: null,
75+
headers,
76+
customMethod)
77+
{
78+
}
79+
80+
public RestRawRequest(string uri,
81+
HttpMethod method,
82+
string? content,
83+
string? contentType,
84+
RequestHeaders headers,
85+
string? customMethod = null)
86+
: base(uri, method, headers, customMethod)
87+
{
88+
this.Content = content;
89+
this.ContentType = contentType;
90+
}
91+
92+
#endregion
93+
94+
#region Methods
95+
96+
public override string ToString()
97+
{
98+
return $"{base.ToString()} - Content: {this.Content ?? "No content"} " +
99+
$"- ContentType: {this.ContentType ?? "No content type"} " +
100+
$"- Accept Type: {this.AcceptType ?? "No accept type"}";
101+
}
102+
103+
#endregion
104+
}
105+
}

Sources/AMDevIT.Restling/AMDevIT.Restling.Core/RestRequestResult.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using AMDevIT.Restling.Core.Text;
1+
using AMDevIT.Restling.Core.Network;
2+
using AMDevIT.Restling.Core.Text;
23
using System.Net;
4+
using System.Security.AccessControl;
35

46
namespace AMDevIT.Restling.Core
57
{
@@ -10,6 +12,7 @@ public class RestRequestResult(RestRequest request,
1012
string? contentType,
1113
Charset charset,
1214
RetrievedContentResult? retrievedContent,
15+
ResponseHeaders responseHeaders,
1316
Exception? exception = null)
1417
{
1518
#region Fields
@@ -22,6 +25,7 @@ public class RestRequestResult(RestRequest request,
2225
private readonly byte[]? rawContent = rawContent;
2326
private readonly RetrievedContentResult? retrievedContent = retrievedContent;
2427
private readonly Exception? exception = exception;
28+
private readonly ResponseHeaders responseHeaders = responseHeaders;
2529

2630
#endregion
2731

@@ -37,15 +41,16 @@ public class RestRequestResult(RestRequest request,
3741

3842
public string? ContentType => this.contentType;
3943
public Charset CharSet => this.charSet;
40-
4144
public Exception? Exception => this.exception;
4245

46+
public ResponseHeaders ResponseHeaders => this.responseHeaders;
47+
4348
#endregion
4449

4550
#region .ctor
4651

4752
public RestRequestResult(RestRequest request, Exception exception)
48-
: this(request, null, TimeSpan.Zero, [], null, Charset.UTF8, null, exception)
53+
: this(request, null, TimeSpan.Zero, [], null, Charset.UTF8, null, ResponseHeaders.Empty, exception)
4954
{
5055

5156
}
@@ -59,7 +64,8 @@ public RestRequestResult(RestRequest request,
5964
[],
6065
null,
6166
Charset.UTF8,
62-
null,
67+
null,
68+
ResponseHeaders.Empty,
6369
exception)
6470
{
6571
}

Sources/AMDevIT.Restling/AMDevIT.Restling.Core/RestRequestResultOfT.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using AMDevIT.Restling.Core.Text;
1+
using AMDevIT.Restling.Core.Network;
2+
using AMDevIT.Restling.Core.Text;
23
using System.Net;
34

45
namespace AMDevIT.Restling.Core
@@ -28,14 +29,16 @@ public RestRequestResult(RestRequest request,
2829
string? contentType,
2930
Charset charset,
3031
RetrievedContentResult? retrievedContent,
32+
ResponseHeaders responseHeaders,
3133
Exception? exception = null)
3234
: base(request,
3335
statusCode,
3436
elapsed,
3537
rawContent,
3638
contentType,
3739
charset,
38-
retrievedContent,
40+
retrievedContent,
41+
responseHeaders,
3942
exception)
4043
{
4144
this.data = data;

0 commit comments

Comments
 (0)