Skip to content

Commit

Permalink
Cleaned up how content is serialized/represented (Azure#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
KrzysztofCwalina authored Jan 7, 2019
1 parent 3f71427 commit b598b49
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 84 deletions.
36 changes: 20 additions & 16 deletions Azure.Configuration/ConfigurationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,20 @@ public async Task<Response<ConfigurationSetting>> AddAsync(ConfigurationSetting
Uri uri = BuildUrlForKvRoute(setting);

PipelineCallContext context = null;
try {
try
{
context = Pipeline.CreateContext(_options, cancellation);

context.AddRequestLine(ServiceMethod.Put, uri);
context.AddHeader(MediaTypeKeyValueApplicationHeader);
context.AddHeader(IfNoneMatchWildcard);
context.AddHeader(Header.Common.JsonContentType);

var content = new SettingContent(setting, context);
context.AddHeader(Header.Common.CreateContentLength(content.Bytes.Length));
AddAuthenticationHeader(context, uri, ServiceMethod.Put, content.Bytes, _secret, _credential);
context.AddContent(content);
ReadOnlyMemory<byte> content = Serialize(setting);

AddAuthenticationHeaders(context, uri, ServiceMethod.Put, content, _secret, _credential);
context.AddHeader(Header.Common.CreateContentLength(content.Length));
context.AddContent(PipelineContent.Create(content));

await Pipeline.ProcessAsync(context).ConfigureAwait(false);

Expand All @@ -83,10 +85,11 @@ public async Task<Response<ConfigurationSetting>> SetAsync(ConfigurationSetting
context.AddHeader(MediaTypeKeyValueApplicationHeader);
context.AddHeader(Header.Common.JsonContentType);

var content = new SettingContent(setting, context);
context.AddHeader(Header.Common.CreateContentLength(content.Bytes.Length));
AddAuthenticationHeader(context, uri, ServiceMethod.Put, content.Bytes, _secret, _credential);
context.AddContent(content);
ReadOnlyMemory<byte> content = Serialize(setting);

AddAuthenticationHeaders(context, uri, ServiceMethod.Put, content, _secret, _credential);
context.AddHeader(Header.Common.CreateContentLength(content.Length));
context.AddContent(PipelineContent.Create(content));

await Pipeline.ProcessAsync(context).ConfigureAwait(false);

Expand Down Expand Up @@ -115,10 +118,11 @@ public async Task<Response<ConfigurationSetting>> UpdateAsync(ConfigurationSetti
context.AddHeader(IfMatchName, $"\"{setting.ETag}\"");
context.AddHeader(Header.Common.JsonContentType);

var content = new SettingContent(setting, context);
context.AddHeader(Header.Common.CreateContentLength(content.Bytes.Length));
AddAuthenticationHeader(context, uri, ServiceMethod.Put, content.Bytes, _secret, _credential);
context.AddContent(content);
ReadOnlyMemory<byte> content = Serialize(setting);

AddAuthenticationHeaders(context, uri, ServiceMethod.Put, content, _secret, _credential);
context.AddHeader(Header.Common.CreateContentLength(content.Length));
context.AddContent(PipelineContent.Create(content));

await Pipeline.ProcessAsync(context).ConfigureAwait(false);

Expand All @@ -142,7 +146,7 @@ public async Task<Response<ConfigurationSetting>> DeleteAsync(string key, Settin
context.AddRequestLine(ServiceMethod.Delete, uri);

AddFilterHeaders(filter, context);
AddAuthenticationHeader(context, uri, ServiceMethod.Delete, content: default, _secret, _credential);
AddAuthenticationHeaders(context, uri, ServiceMethod.Delete, content: default, _secret, _credential);

await Pipeline.ProcessAsync(context).ConfigureAwait(false);

Expand Down Expand Up @@ -215,7 +219,7 @@ public async Task<Response<ConfigurationSetting>> GetAsync(string key, SettingFi
AddFilterHeaders(filter, context);
context.AddHeader(Header.Common.JsonContentType);

AddAuthenticationHeader(context, uri, ServiceMethod.Get, content: default, _secret, _credential);
AddAuthenticationHeaders(context, uri, ServiceMethod.Get, content: default, _secret, _credential);

await Pipeline.ProcessAsync(context).ConfigureAwait(false);

Expand Down Expand Up @@ -251,7 +255,7 @@ public async Task<Response<SettingBatch>> GetBatchAsync(BatchFilter filter, Canc
return new Response<SettingBatch>(response);
}

var batch = await ConfigurationServiceParser.ParseBatchAsync(response, cancellation);
var batch = await ConfigurationServiceSerializer.ParseBatchAsync(response, cancellation);
return new Response<SettingBatch>(response, batch);
}
catch {
Expand Down
88 changes: 22 additions & 66 deletions Azure.Configuration/ConfigurationClient_private.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ static async Task<Response<ConfigurationSetting>> CreateKeyValueResponse(Pipelin
return new Response<ConfigurationSetting>(response);
}

var result = await ConfigurationServiceParser.ParseSettingAsync(response.ContentStream, context.Cancellation);
var result = await ConfigurationServiceSerializer.ParseSettingAsync(response.ContentStream, context.Cancellation);

return new Response<ConfigurationSetting>(response, result);
}
Expand Down Expand Up @@ -151,7 +151,25 @@ Uri BuildUrlForGetBatch(BatchFilter options)
return builder.Uri;
}

internal static void AddAuthenticationHeader(PipelineCallContext context, Uri uri, ServiceMethod method, ReadOnlyMemory<byte> content, byte[] secret, string credential)
static ReadOnlyMemory<byte> Serialize(ConfigurationSetting setting)
{
ReadOnlyMemory<byte> content = default;
int size = 256;
while (true)
{
byte[] buffer = new byte[size];
if (ConfigurationServiceSerializer.TrySerialize(setting, buffer, out int written))
{
content = buffer.AsMemory(0, written);
break;
}
size *= 2;
}

return content;
}

internal static void AddAuthenticationHeaders(PipelineCallContext context, Uri uri, ServiceMethod method, ReadOnlyMemory<byte> content, byte[] secret, string credential)
{
string contentHash = null;
using (var alg = SHA256.Create())
Expand All @@ -170,76 +188,14 @@ internal static void AddAuthenticationHeader(PipelineCallContext context, Uri ur
var utcNowString = utcNow.ToString("r");
var stringToSign = $"{verb}\n{pathAndQuery}\n{utcNowString};{host};{contentHash}";
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.ASCII.GetBytes(stringToSign))); // Calculate the signature
string signedHeaders = "date;host;x-ms-content-sha256"; // Semicolon separated header names

context.AddHeader("Date", utcNowString);
context.AddHeader("x-ms-content-sha256", contentHash);
string signedHeaders = "date;host;x-ms-content-sha256"; // Semicolon separated header names
context.AddHeader("Authorization", $"HMAC-SHA256 Credential={credential}, SignedHeaders={signedHeaders}, Signature={signature}");
}
}

class SettingContent : PipelineContent
{
byte[] _content;
int _contentLength;

public SettingContent(ConfigurationSetting setting, PipelineCallContext context)
{
var writer = new ArrayWriter();

var json = new Utf8JsonWriter<ArrayWriter>(writer);
json.WriteObjectStart();
json.WriteAttribute("value", setting.Value);
json.WriteAttribute("content_type", setting.ContentType);
json.WriteObjectEnd();
json.Flush();
_contentLength = writer.Written;
_content = writer.Buffer;
}

public ReadOnlyMemory<byte> Bytes => _content.AsMemory(0, _contentLength);

public override void Dispose() { }

public override bool TryComputeLength(out long length)
{
length = _contentLength;
return true;
}

public async override Task WriteTo(Stream stream)
{
await stream.WriteAsync(_content, 0, _contentLength);
}

// TODO (pri 2): Utf8JsonWriter will have Written property soon and this type should be removed then.
// TODO (pri 2): Utf8JsonWriter will have the ability to write to Stream, at which point this code can be simplified
class ArrayWriter : IBufferWriter<byte>, IDisposable
{
byte[] _buffer;
int _written = 0;

public ArrayWriter(int length = 1024)
{
_buffer = ArrayPool<byte>.Shared.Rent(length);
}

public int Written => _written;
public byte[] Buffer => _buffer;

public void Advance(int count) => _written += count;

public void Dispose()
{
if (_buffer != null) ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null;
}

public Memory<byte> GetMemory(int sizeHint = 0) => _buffer.AsMemory(_written);

public Span<byte> GetSpan(int sizeHint = 0) => _buffer.AsSpan(_written);
}
}

#region nobody wants to see these
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object obj) => base.Equals(obj);
Expand Down
37 changes: 35 additions & 2 deletions Azure.Configuration/ConfigurationSettingParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,24 @@ namespace Azure.Configuration
// This should be simplified twice:
// - once JsonReader supports for reading from stream
// - second time we have the serializer
static class ConfigurationServiceParser
static class ConfigurationServiceSerializer
{
static byte[][] s_nameTable;
static JsonState[] s_valueTable;

public static bool TrySerialize(ConfigurationSetting setting, byte[] buffer, out int written)
{
var writer = new ArrayWriter(buffer);
var json = new Utf8JsonWriter<ArrayWriter>(writer);
json.WriteObjectStart();
json.WriteAttribute("value", setting.Value);
json.WriteAttribute("content_type", setting.ContentType);
json.WriteObjectEnd();
json.Flush();
written = writer.Written;
return true;
}

public enum JsonState : byte
{
Other = 0,
Expand All @@ -44,7 +57,7 @@ static JsonState ToJsonState(this ReadOnlySpan<byte> propertyName)
return JsonState.Other;
}

static ConfigurationServiceParser()
static ConfigurationServiceSerializer()
{
var names = Enum.GetNames(typeof(JsonState));
s_nameTable = new byte[names.Length][];
Expand Down Expand Up @@ -221,4 +234,24 @@ static bool TryGetNextAfterValue(ref ServiceResponse response, out int afterValu
return Utf8Parser.TryParse(urlBytes, out afterValue, out _);
}
}

// TODO (pri 2): Utf8JsonWriter will have Written property soon and this type should be removed then.
// TODO (pri 2): Utf8JsonWriter will have the ability to write to Stream, at which point this code can be simplified
class ArrayWriter : IBufferWriter<byte>
{
byte[] _buffer;
int _written = 0;

public ArrayWriter(byte[] buffer)
=> _buffer = buffer;

public int Written => _written;
public byte[] Buffer => _buffer;

public void Advance(int count) => _written += count;

public Memory<byte> GetMemory(int sizeHint = 0) => _buffer.AsMemory(_written);

public Span<byte> GetSpan(int sizeHint = 0) => _buffer.AsSpan(_written);
}
}

0 comments on commit b598b49

Please sign in to comment.