Last active
December 4, 2020 10:30
-
-
Save NeilBostrom/cab8b9275e39bb90ecf8e06ab980664b to your computer and use it in GitHub Desktop.
AWS AWS4Signer HttpClientHandler implementation with GraphQL client example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static async Task Main(string[] args) | |
{ | |
var options = new GraphQLHttpClientOptions | |
{ | |
EndPoint = new Uri("https://countries.trevorblades.com/"), | |
HttpMessageHandler = new AWS4SignerMessageHandler( | |
new AmazonAppSyncClient(), | |
"--awsAccessKeyId--", | |
"--awsSecretAccessKey--") | |
}; | |
var graphQLClient = new GraphQLHttpClient(options, new NewtonsoftJsonSerializer()); | |
var graphQLResponse = await graphQLClient.SendQueryAsync<DataResponse>(new GraphQL.GraphQLRequest( | |
@"{ | |
countries { | |
code, | |
name | |
} | |
}")); | |
if (graphQLResponse.Errors?.Any() == true) | |
throw new Exception(string.Join(Environment.NewLine, graphQLResponse.Errors.Select(e => e.Message))); | |
var data = graphQLResponse.Data; | |
Console.WriteLine($"Country count: {data.Countries.Count}"); | |
} | |
public class DataResponse | |
{ | |
public List<Country> Countries { get; set; } | |
} | |
public class Country | |
{ | |
public string Code { get; set; } | |
public string Name { get; set; } | |
} | |
public class AWS4SignerMessageHandler : HttpClientHandler | |
{ | |
private readonly AmazonServiceClient _client; | |
private readonly string _awsAccessKeyId; | |
private readonly string _awsSecretAccessKey; | |
private readonly string _sessionToken; | |
public AWS4SignerMessageHandler(AmazonServiceClient client, string awsAccessKeyId, string awsSecretAccessKey, string sessionToken = null) | |
{ | |
_client = client; | |
_awsAccessKeyId = awsAccessKeyId; | |
_awsSecretAccessKey = awsSecretAccessKey; | |
_sessionToken = sessionToken; | |
} | |
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |
{ | |
if (_sessionToken != null) | |
request.Headers.Add(HeaderKeys.XAmzSecurityTokenHeader, _sessionToken); | |
var signingRequest = new AmazonHttpRequestSigning(_client, request) | |
{ | |
Content = await request.Content.ReadAsByteArrayAsync() | |
}; | |
new AWS4Signer().Sign(signingRequest, _client.Config, null, _awsAccessKeyId, _awsSecretAccessKey); | |
foreach (var header in signingRequest.Headers) | |
{ | |
if (header.Key != HeaderKeys.HostHeader && header.Key != HeaderKeys.XAmzSecurityTokenHeader && header.Key != HeaderKeys.UserAgentHeader) | |
request.Headers.TryAddWithoutValidation(header.Key, header.Value); | |
} | |
return await base.SendAsync(request, cancellationToken); | |
} | |
} | |
public class AmazonHttpRequestSigning : IRequest | |
{ | |
public AmazonHttpRequestSigning(AmazonServiceClient client, HttpRequestMessage request) | |
{ | |
HttpMethod = request.Method.Method; | |
Endpoint = new Uri(request.RequestUri.AbsoluteUri[..request.RequestUri.AbsoluteUri.LastIndexOf(request.RequestUri.AbsolutePath)]); | |
ResourcePath = request.RequestUri.AbsolutePath; | |
Headers = request.Headers.ToDictionary(e => e.Key, e => e.Value.FirstOrDefault()); | |
AlternateEndpoint = client.Config.RegionEndpoint; | |
AuthenticationRegion = client.Config.AuthenticationRegion; | |
} | |
public Uri Endpoint { get; set; } | |
public string DeterminedSigningRegion { get; set; } | |
public RegionEndpoint AlternateEndpoint { get; set; } | |
public IDictionary<string, string> Headers { get; } | |
public IDictionary<string, string> SubResources { get; } | |
public string AuthenticationRegion { get; set; } | |
public string HttpMethod { get; set; } | |
public string ResourcePath { get; set; } | |
public bool UseQueryString { get; set; } | |
public bool UseChunkEncoding { get; set; } | |
public Stream ContentStream { get; set; } | |
public byte[] Content { get; set; } | |
public bool? DisablePayloadSigning { get; set; } | |
public string OverrideSigningServiceName { get; set; } | |
public IDictionary<string, string> PathResources { get; set; } | |
public int MarshallerVersion { get; set; } | |
#region Not Implemented | |
public string RequestName => throw new NotImplementedException(); | |
public IDictionary<string, string> Parameters => throw new NotImplementedException(); | |
public ParameterCollection ParameterCollection => throw new NotImplementedException(); | |
public bool SetContentFromParameters { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public long OriginalStreamPosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public string ServiceName => throw new NotImplementedException(); | |
public AmazonWebServiceRequest OriginalRequest => throw new NotImplementedException(); | |
public string HostPrefix { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public bool Suppress404Exceptions { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public AWS4SigningResult AWS4SignerResult { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public string CanonicalResourcePrefix { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public bool UseSigV4 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | |
public void AddPathResource(string key, string value) => throw new NotImplementedException(); | |
public void AddSubResource(string subResource) => throw new NotImplementedException(); | |
public void AddSubResource(string subResource, string value) => throw new NotImplementedException(); | |
public string ComputeContentStreamHash() => throw new NotImplementedException(); | |
public string GetHeaderValue(string headerName) => throw new NotImplementedException(); | |
public bool HasRequestBody() => throw new NotImplementedException(); | |
public bool IsRequestStreamRewindable() => throw new NotImplementedException(); | |
public bool MayContainRequestBody() => throw new NotImplementedException(); | |
#endregion | |
} |
@joseph-adam That is correct! It is indeed using this GraphQLClient. Yep, this handler adds AWS signing support to the GraphQLClient library to enable you to use it again AWS AppSync.
Thank you Neil!
Turns out my gist was a little out of date with the latest libraries. Updated gist to match GraphQL.Client 3.2.0 and AWSSDK.AppSync 3.5.1.23
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Neil,
Interested to know if the reference above if the GraphQLClient above is the .net GraphQL client lib (https://github.com/graphql-dotnet/graphql-dotnet) or is this from somewhere else? If I understand correctly, by signing the requests we can use the graphql client lib to connect to aws appsync? Thank you for any pointers.