Skip to content

Commit e6e8180

Browse files
author
krivchenko-kv
committed
added url and head builders
1 parent 6e110b6 commit e6e8180

8 files changed

Lines changed: 302 additions & 221 deletions

File tree

src/Storage.Benchmark/InternalBenchmarks/SignatureBenchmark.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BenchmarkDotNet.Attributes;
1+
using BenchmarkDotNet.Attributes;
22
using BenchmarkDotNet.Jobs;
33
using Storage.Benchmark.Utils;
44
using Storage.Utils;
@@ -23,13 +23,13 @@ public void Config()
2323
var data = BenchmarkHelper.ReadBigArray(config);
2424
var settings = BenchmarkHelper.ReadSettings(config);
2525

26+
var urlBuilder = new UrlBuilder(settings.SecretKey, settings.Region, settings.Service, DefaultArrayPool.Instance);
27+
2628
_headers = ["host", "x-amz-content-sha256", "x-amz-date"];
2729
_now = DateTime.UtcNow;
2830
_request = new HttpRequestMessage(HttpMethod.Post, "http://company-name.com/controller");
2931
_payloadHash = HashHelper.GetPayloadHash(data, DefaultArrayPool.Instance);
30-
_signature = new Signature(
31-
new HttpDescription(DefaultArrayPool.Instance, "", "", "", []),
32-
settings.SecretKey, settings.Region, settings.Service);
32+
_signature = new Signature(urlBuilder, settings.SecretKey, DefaultArrayPool.Instance);
3333
}
3434

3535
[Benchmark]
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//using Storage.Utils;
2+
3+
//namespace Storage.Tests.Utils;
4+
5+
//public class QueryParameterTests
6+
//{
7+
// public UrlBuilder appender = new QueryParameterAppender(new HttpDescription())
8+
9+
10+
11+
// [Fact]
12+
// public void Test_EmptyQuery()
13+
// {
14+
// var appender = new QueryParameterAppender();
15+
// var builder = new ValueStringBuilder();
16+
17+
// appender.AppendCanonicalQueryParameters1(ref builder, null);
18+
// Assert.Empty(builder.ToString());
19+
20+
// appender.AppendCanonicalQueryParameters1(ref builder, "");
21+
// Assert.Empty(builder.ToString());
22+
23+
// appender.AppendCanonicalQueryParameters1(ref builder, "?");
24+
// Assert.Empty(builder.ToString());
25+
// }
26+
27+
// [Fact]
28+
// public void Test_SingleParameter()
29+
// {
30+
// var appender = new QueryParameterAppender();
31+
// var builder = new ValueStringBuilder();
32+
33+
// appender.AppendCanonicalQueryParameters1(ref builder, "?key=value");
34+
// Assert.Equal("key=value", builder.ToString());
35+
// }
36+
37+
// [Fact]
38+
// public void Test_MultipleParameters()
39+
// {
40+
// var appender = new QueryParameterAppender();
41+
// var builder = new ValueStringBuilder();
42+
43+
// appender.AppendCanonicalQueryParameters1(ref builder, "?key1=value1&key2=value2");
44+
// Assert.Equal("key1=value1&key2=value2", builder.ToString());
45+
// }
46+
47+
// [Fact]
48+
// public void Test_ParameterWithWhitespace()
49+
// {
50+
// var appender = new QueryParameterAppender();
51+
// var builder = new ValueStringBuilder();
52+
53+
// appender.AppendCanonicalQueryParameters1(ref builder, "? key1 = value1 & key2 = value2 ");
54+
// Assert.Equal("key1=value1&key2=value2", builder.ToString());
55+
// }
56+
57+
// [Fact]
58+
// public void Test_ParameterWithoutValue()
59+
// {
60+
// var appender = new QueryParameterAppender();
61+
// var builder = new ValueStringBuilder();
62+
63+
// appender.AppendCanonicalQueryParameters1(ref builder, "?key1&key2=value2");
64+
// Assert.Equal("key1=&key2=value2", builder.ToString());
65+
// }
66+
67+
// [Fact]
68+
// public void Test_ParameterWithEmptyValue()
69+
// {
70+
// var appender = new QueryParameterAppender();
71+
// var builder = new ValueStringBuilder();
72+
73+
// appender.AppendCanonicalQueryParameters1(ref builder, "?key1=&key2=");
74+
// Assert.Equal("key1=&key2=", builder.ToString());
75+
// }
76+
77+
// [Fact]
78+
// public void Test_ParameterWithSpecialCharacters()
79+
// {
80+
// var appender = new QueryParameterAppender();
81+
// var builder = new ValueStringBuilder();
82+
83+
// appender.AppendCanonicalQueryParameters1(ref builder, "?key1=value%20with%20spaces&key2=value%26with%26ampersands");
84+
// Assert.Equal("key1=value with spaces&key2=value&with&ampersands", builder.ToString());
85+
// }
86+
//}

src/Storage/S3BucketClient.Transport.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Storage.Utils;
22

3-
namespace Storage;
4-
3+
namespace Storage;
4+
55
/// <summary>
66
/// Transport functions
77
/// </summary>
@@ -12,13 +12,13 @@ public partial class S3BucketClient
1212
private HttpRequestMessage CreateRequest(HttpMethod method, string? fileName = null)
1313
{
1414
var url = new ValueStringBuilder(stackalloc char[512], _arrayPool);
15-
url.Append(_bucket);
16-
17-
// ReSharper disable once InvertIf
15+
url.Append(_bucket);
16+
17+
// ReSharper disable once InvertIf
1818
if (!string.IsNullOrEmpty(fileName))
1919
{
2020
url.Append('/');
21-
_httpDescription.AppendEncodedName(ref url, fileName);
21+
_urlBuilder.AppendEncodedName(ref url, fileName);
2222
}
2323

2424
return new HttpRequestMessage(method, new Uri(url.Flush(), UriKind.Absolute));
@@ -31,8 +31,8 @@ private Task<HttpResponseMessage> Send(HttpRequestMessage request, string payloa
3131
Errors.Disposed();
3232
}
3333

34-
var now = DateTime.UtcNow;
35-
34+
var now = DateTime.UtcNow; // TODO: !!
35+
3636
var headers = request.Headers;
3737
headers.Add("host", _host);
3838
headers.Add("x-amz-content-sha256", payloadHash);
@@ -43,8 +43,8 @@ private Task<HttpResponseMessage> Send(HttpRequestMessage request, string payloa
4343
request.Version = HttpVersion.Version20;
4444
}
4545

46-
var signature = _signature.Calculate(request, payloadHash, S3Headers, now);
47-
headers.TryAddWithoutValidation("Authorization", _httpDescription.BuildHeader(now, signature));
46+
var signature = _signature.Calculate(request, payloadHash, HeadBuilder.S3Headers, now);
47+
headers.TryAddWithoutValidation("Authorization", _headBuilder.BuildAuthorization(now, signature));
4848

4949
return _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct);
5050
}

src/Storage/S3BucketClient.cs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,20 @@ namespace Storage;
1111
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Approved")]
1212
public partial class S3BucketClient : IS3BucketClient, IDisposable
1313
{
14-
internal const int DefaultPartSize = 5 * 1024 * 1024; // 5 Mb
15-
16-
private static readonly string[] S3Headers = // trimmed, lower invariant, ordered
17-
[
18-
"host",
19-
"x-amz-content-sha256",
20-
"x-amz-date",
21-
];
14+
internal const int DefaultPartSize = 5 * 1024 * 1024; // 5 Mb
2215

2316

2417
private readonly IArrayPool _arrayPool;
2518

2619
private readonly string _bucket;
2720
private readonly HttpClient _client;
28-
private readonly string _host;
29-
private readonly HttpDescription _httpDescription;
21+
private readonly string _host;
22+
private readonly HeadBuilder _headBuilder;
23+
private readonly UrlBuilder _urlBuilder;
3024
private readonly Signature _signature;
3125
private readonly bool _useHttp2;
32-
33-
26+
27+
3428
private bool _disposed;
3529

3630

@@ -50,14 +44,10 @@ public S3BucketClient(HttpClient client, S3BucketSettings settings, IArrayPool?
5044

5145
_arrayPool = arrayProvider ?? DefaultArrayPool.Instance;
5246

53-
_httpDescription = new HttpDescription(
54-
_arrayPool,
55-
settings.AccessKey,
56-
settings.Region,
57-
settings.Service,
58-
S3Headers);
59-
60-
_signature = new Signature(_httpDescription, settings.SecretKey, settings.Region, settings.Service, _arrayPool);
47+
_urlBuilder = new UrlBuilder(settings.AccessKey, settings.Region, settings.Service, _arrayPool);
48+
_headBuilder = new HeadBuilder(settings.AccessKey, settings.Region, settings.Service, _arrayPool);
49+
50+
_signature = new Signature(_urlBuilder, settings.SecretKey, _arrayPool);
6151
}
6252

6353
/// <summary>
@@ -68,8 +58,8 @@ public S3BucketClient(HttpClient client, S3BucketSettings settings, IArrayPool?
6858
/// <returns>Возвращает подписанную ссылку на файл</returns>
6959
public string BuildFileUrl(string fileName, TimeSpan expiration)
7060
{
71-
var now = DateTime.UtcNow;
72-
var url = _httpDescription.BuildUrl(_bucket, fileName, now, expiration);
61+
var now = DateTime.UtcNow; // TODO!!!
62+
var url = _urlBuilder.BuildUrl(_bucket, fileName, now, expiration);
7363
var signature = _signature.Calculate(url, now);
7464

7565
return $"{url}&X-Amz-Signature={signature}";
@@ -89,7 +79,7 @@ public async Task DeleteFile(string fileName, CancellationToken ct)
8979
}
9080

9181
response.Dispose();
92-
}
82+
}
9383

9484

9585
/// <summary>
@@ -180,7 +170,7 @@ public async IAsyncEnumerable<string> List(string? prefix, [EnumeratorCancellati
180170
{
181171
var url = string.IsNullOrEmpty(prefix)
182172
? $"{_bucket}?list-type=2"
183-
: $"{_bucket}?list-type=2&prefix={_httpDescription.EncodeName(prefix)}";
173+
: $"{_bucket}?list-type=2&prefix={_urlBuilder.EncodeName(prefix)}";
184174

185175
HttpResponseMessage response;
186176
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
@@ -223,7 +213,7 @@ public async IAsyncEnumerable<string> List(string? prefix, [EnumeratorCancellati
223213
/// <returns>Возвращает объект управления загрузкой</returns>
224214
public async Task<S3Upload> UploadFile(string fileName, string contentType, CancellationToken ct)
225215
{
226-
var encodedFileName = _httpDescription.EncodeName(fileName);
216+
var encodedFileName = _urlBuilder.EncodeName(fileName);
227217
var uploadId = await MultipartStart(encodedFileName, contentType, ct).ConfigureAwait(false);
228218

229219
return new S3Upload(this, fileName, encodedFileName, uploadId, _arrayPool);

src/Storage/Utils/HeadBuilder.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace Storage.Utils;
2+
3+
internal sealed class HeadBuilder(string accessKey, string region, string service, IArrayPool arrayPool)
4+
{
5+
6+
public static readonly string[] S3Headers = // trimmed, lower invariant, ordered
7+
[
8+
"host",
9+
"x-amz-content-sha256",
10+
"x-amz-date",
11+
];
12+
13+
14+
private readonly string _headerEnd = $"/{region}/{service}/aws4_request, SignedHeaders={string.Join(';', S3Headers)}, Signature=";
15+
private readonly string _headerStart = $"AWS4-HMAC-SHA256 Credential={accessKey}/";
16+
17+
18+
19+
[SkipLocalsInit]
20+
public string BuildAuthorization(DateTime now, string signature)
21+
{
22+
using var builder = new ValueStringBuilder(stackalloc char[512], arrayPool);
23+
24+
builder.Append(_headerStart);
25+
builder.Append(now, Signature.Iso8601Date);
26+
builder.Append(_headerEnd);
27+
builder.Append(signature);
28+
29+
return builder.Flush();
30+
}
31+
}

0 commit comments

Comments
 (0)