diff --git a/README.md b/README.md index 455ce5e52..4ca6b57c0 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,15 @@ var message = MessageResource.Create( Console.WriteLine(message.Sid); ``` +### Specify Region and/or Edge + +```csharp +TwilioClient.SetRegion("au1"); +TwilioClient.SetEdge("sydney"); +``` + +This will result in the `hostname` transforming from `api.twilio.com` to `api.sydney.au1.twilio.com`. + ## Generating TwiML To control phone calls, your application needs to output [TwiML][twiml]. diff --git a/src/Twilio/Base/Page.cs b/src/Twilio/Base/Page.cs index 6c31c57d4..86bba2c4e 100644 --- a/src/Twilio/Base/Page.cs +++ b/src/Twilio/Base/Page.cs @@ -36,14 +36,14 @@ public class Page where T : Resource private Page( List records, int pageSize, - string uri=null, - string url=null, - string firstPageUri=null, - string firstPageUrl=null, - string previousPageUri=null, - string previousPageUrl=null, - string nextPageUri=null, - string nextPageUrl=null + string uri = null, + string url = null, + string firstPageUri = null, + string firstPageUrl = null, + string previousPageUri = null, + string previousPageUrl = null, + string nextPageUri = null, + string nextPageUrl = null ) { Records = records; @@ -58,18 +58,9 @@ private Page( _previousPageUrl = previousPageUrl; } - private static string UrlFromUri(Domain domain, string region, string uri) + private static string UrlFromUri(Domain domain, string uri) { - var b = new StringBuilder(); - b.Append("https://").Append(domain); - - if (!IsNullOrEmpty(region)) - { - b.Append(".").Append(region); - } - - b.Append(".twilio.com").Append(uri); - return b.ToString(); + return "https://" + domain + ".twilio.com" + uri; } /// @@ -78,9 +69,9 @@ private static string UrlFromUri(Domain domain, string region, string uri) /// Twilio subdomain /// Twilio region /// URL for the first page of results - public string GetFirstPageUrl(Domain domain, string region) + public string GetFirstPageUrl(Domain domain, string region = null) { - return _firstPageUrl ?? UrlFromUri(domain, region, _firstPageUri); + return _firstPageUrl ?? UrlFromUri(domain, _firstPageUri); } /// @@ -89,9 +80,9 @@ public string GetFirstPageUrl(Domain domain, string region) /// Twilio subdomain /// Twilio region /// URL for the next page of results - public string GetNextPageUrl(Domain domain, string region) + public string GetNextPageUrl(Domain domain, string region = null) { - return _nextPageUrl ?? UrlFromUri(domain, region, _nextPageUri); + return _nextPageUrl ?? UrlFromUri(domain, _nextPageUri); } /// @@ -100,9 +91,9 @@ public string GetNextPageUrl(Domain domain, string region) /// Twilio subdomain /// Twilio region /// URL for the previous page of results - public string GetPreviousPageUrl(Domain domain, string region) + public string GetPreviousPageUrl(Domain domain, string region = null) { - return _previousPageUrl ?? UrlFromUri(domain, region, _previousPageUri); + return _previousPageUrl ?? UrlFromUri(domain, _previousPageUri); } /// @@ -111,9 +102,9 @@ public string GetPreviousPageUrl(Domain domain, string region) /// Twilio subdomain /// Twilio region /// URL for the current page of results - public string GetUrl(Domain domain, string region) + public string GetUrl(Domain domain, string region = null) { - return _url ?? UrlFromUri(domain, region, _uri); + return _url ?? UrlFromUri(domain, _uri); } /// @@ -170,4 +161,4 @@ record => JsonConvert.DeserializeObject(record.ToString()) ); } } -} \ No newline at end of file +} diff --git a/src/Twilio/Clients/TwilioRestClient.cs b/src/Twilio/Clients/TwilioRestClient.cs index 30fea6029..b337a30f2 100644 --- a/src/Twilio/Clients/TwilioRestClient.cs +++ b/src/Twilio/Clients/TwilioRestClient.cs @@ -35,6 +35,11 @@ public class TwilioRestClient : ITwilioRestClient /// public string Region { get; } + /// + /// Twilio edge to make requests to + /// + public string Edge { get; set; } + private readonly string _username; private readonly string _password; @@ -47,12 +52,14 @@ public class TwilioRestClient : ITwilioRestClient /// account sid to make requests for /// region to make requests for /// http client used to make the requests + /// edge to make requests for public TwilioRestClient( string username, string password, string accountSid = null, string region = null, - HttpClient httpClient = null + HttpClient httpClient = null, + string edge = null ) { _username = username; @@ -60,7 +67,9 @@ public TwilioRestClient( AccountSid = accountSid ?? username; HttpClient = httpClient ?? DefaultClient(); + Region = region; + Edge = edge; } /// @@ -72,6 +81,13 @@ public TwilioRestClient( public Response Request(Request request) { request.SetAuth(_username, _password); + + if (Region != null) + request.Region = Region; + + if (Edge != null) + request.Edge = Edge; + Response response; try { @@ -191,7 +207,8 @@ public static void ValidateSslCertificate(HttpClient client) ); } } - catch (CertificateValidationException e) { + catch (CertificateValidationException e) + { throw e; } catch (Exception e) diff --git a/src/Twilio/Http/Request.cs b/src/Twilio/Http/Request.cs index 7f318aeb8..d2b4d53b8 100644 --- a/src/Twilio/Http/Request.cs +++ b/src/Twilio/Http/Request.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Text; using Twilio.Rest; #if !NET35 @@ -17,29 +17,44 @@ namespace Twilio.Http /// public class Request { + private static readonly string DEFAULT_REGION = "us1"; + /// /// HTTP Method /// public HttpMethod Method { get; } + private Uri Uri; + /// /// Auth username /// public string Username { get; set; } - + /// /// Auth password /// public string Password { get; set; } - + /// - /// Post params + /// Twilio region + /// + public string Region { get; set; } + + /// + /// Twilio edge + /// + public string Edge { get; set; } + + /// + /// Query params /// - public List> PostParams { get { return _postParams; } } + public List> QueryParams { get; private set; } - private readonly Uri _uri; - private readonly List> _queryParams; - private readonly List> _postParams; + /// + /// Post params + /// + public List> PostParams { get; private set; } /// /// Create a new Twilio request @@ -49,9 +64,9 @@ public class Request public Request(HttpMethod method, string url) { Method = method; - _uri = new Uri(url); - _queryParams = new List>(); - _postParams = new List>(); + Uri = new Uri(url); + QueryParams = new List>(); + PostParams = new List>(); } /// @@ -63,28 +78,24 @@ public Request(HttpMethod method, string url) /// Twilio region /// Query parameters /// Post data + /// Twilio edge public Request( HttpMethod method, Domain domain, string uri, - string region, - List> queryParams=null, - List> postParams=null + string region = null, + List> queryParams = null, + List> postParams = null, + string edge = null ) { Method = method; + Uri = new Uri("https://" + domain + ".twilio.com" + uri); + Region = region; + Edge = edge; - var b = new StringBuilder(); - b.Append("https://").Append(domain); - if (!string.IsNullOrEmpty(region)) - { - b.Append(".").Append(region); - } - b.Append(".twilio.com").Append(uri); - - _uri = new Uri(b.ToString()); - _queryParams = queryParams ?? new List>(); - _postParams = postParams ?? new List>(); + QueryParams = queryParams ?? new List>(); + PostParams = postParams ?? new List>(); } /// @@ -93,9 +104,44 @@ public Request( /// Built URL including query parameters public Uri ConstructUrl() { - return _queryParams.Count > 0 ? - new Uri(_uri.AbsoluteUri + "?" + EncodeParameters(_queryParams)) : - new Uri(_uri.AbsoluteUri); + var uri = buildUri(); + return QueryParams.Count > 0 ? + new Uri(uri.AbsoluteUri + "?" + EncodeParameters(QueryParams)) : + new Uri(uri.AbsoluteUri); + } + + public Uri buildUri() + { + if (Region != null || Edge != null) + { + var uriBuilder = new UriBuilder(Uri); + var pieces = uriBuilder.Host.Split('.'); + var product = pieces[0]; + var domain = String.Join(".", pieces.Skip(pieces.Length - 2).ToArray()); + + var region = Region; + var edge = Edge; + + if (pieces.Length == 4) // product.region.twilio.com + { + region = region ?? pieces[1]; + } + else if (pieces.Length == 5) // product.edge.region.twilio.com + { + edge = edge ?? pieces[1]; + region = region ?? pieces[2]; + } + + if (edge != null && region == null) + region = DEFAULT_REGION; + + string[] parts = { product, edge, region, domain }; + + uriBuilder.Host = String.Join(".", Array.FindAll(parts, part => !string.IsNullOrEmpty(part))); + return uriBuilder.Uri; + } + + return Uri; } /// @@ -108,7 +154,7 @@ public void SetAuth(string username, string password) Username = username; Password = password; } - + private static string EncodeParameters(IEnumerable> data) { var result = ""; @@ -122,8 +168,8 @@ private static string EncodeParameters(IEnumerable> else { result += "&"; - } - + } + #if !NET35 result += WebUtility.UrlEncode(pair.Key) + "=" + WebUtility.UrlEncode(pair.Value); #else @@ -133,14 +179,14 @@ private static string EncodeParameters(IEnumerable> return result; } - + /// /// Encode POST data for transfer /// /// Encoded byte array public byte[] EncodePostParams() { - return Encoding.UTF8.GetBytes(EncodeParameters(_postParams)); + return Encoding.UTF8.GetBytes(EncodeParameters(PostParams)); } /// @@ -150,7 +196,7 @@ public byte[] EncodePostParams() /// value of parameter public void AddQueryParam(string name, string value) { - AddParam(_queryParams, name, value); + AddParam(QueryParams, name, value); } /// @@ -160,12 +206,12 @@ public void AddQueryParam(string name, string value) /// value of parameter public void AddPostParam(string name, string value) { - AddParam(_postParams, name, value); + AddParam(PostParams, name, value); } private static void AddParam(ICollection> list, string name, string value) { - list.Add(new KeyValuePair (name, value)); + list.Add(new KeyValuePair(name, value)); } /// @@ -191,11 +237,10 @@ public override bool Equals(object obj) var other = (Request)obj; return Method.Equals(other.Method) && - _uri.Equals(other._uri) && - _queryParams.All(other._queryParams.Contains) && - _postParams.All(other._postParams.Contains); + buildUri().Equals(other.buildUri()) && + QueryParams.All(other.QueryParams.Contains) && + PostParams.All(other.PostParams.Contains); } - /// /// Generate hash code for request @@ -206,9 +251,9 @@ public override int GetHashCode() unchecked { return (Method?.GetHashCode() ?? 0) ^ - (_uri?.GetHashCode() ?? 0) ^ - (_queryParams?.GetHashCode() ?? 0) ^ - (_postParams?.GetHashCode() ?? 0); + (buildUri()?.GetHashCode() ?? 0) ^ + (QueryParams?.GetHashCode() ?? 0) ^ + (PostParams?.GetHashCode() ?? 0); } } } diff --git a/src/Twilio/Twilio.cs b/src/Twilio/Twilio.cs index e64035987..7459f7187 100644 --- a/src/Twilio/Twilio.cs +++ b/src/Twilio/Twilio.cs @@ -11,9 +11,11 @@ public class TwilioClient private static string _username; private static string _password; private static string _accountSid; + private static string _region; + private static string _edge; private static ITwilioRestClient _restClient; - private TwilioClient() {} + private TwilioClient() { } /// /// Initialize base client with username and password @@ -62,7 +64,8 @@ public static void SetUsername(string username) /// Set the client password /// /// Auth password - public static void SetPassword(string password) { + public static void SetPassword(string password) + { if (password == null) { throw new AuthenticationException("Password can not be null"); @@ -95,6 +98,34 @@ public static void SetAccountSid(string accountSid) _accountSid = accountSid; } + /// + /// Set the client region + /// + /// Client region + public static void SetRegion(string region) + { + if (region != _region) + { + Invalidate(); + } + + _region = region; + } + + /// + /// Set the client edge + /// + /// Client edge + public static void SetEdge(string edge) + { + if (edge != _edge) + { + Invalidate(); + } + + _edge = edge; + } + /// /// Get the rest client /// @@ -108,12 +139,12 @@ public static ITwilioRestClient GetRestClient() if (_username == null || _password == null) { - throw new AuthenticationException ( + throw new AuthenticationException( "TwilioRestClient was used before AccountSid and AuthToken were set, please call TwilioClient.init()" ); } - _restClient = new TwilioRestClient(_username, _password, accountSid: _accountSid); + _restClient = new TwilioRestClient(_username, _password, accountSid: _accountSid, region: _region, edge: _edge); return _restClient; } diff --git a/test/Twilio.Test/Http/RequestTest.cs b/test/Twilio.Test/Http/RequestTest.cs new file mode 100644 index 000000000..ec1bcb68c --- /dev/null +++ b/test/Twilio.Test/Http/RequestTest.cs @@ -0,0 +1,70 @@ +using NUnit.Framework; +using System; +using Twilio.Http; +using Twilio.Rest; + +namespace Twilio.Tests.Http +{ + [TestFixture] + public class RequestTest + { + [Test] + public void TestNoEdgeOrRegionInUrl() + { + var request = new Request(HttpMethod.Get, "https://api.twilio.com"); + + Assert.AreEqual(new Uri("https://api.twilio.com"), request.buildUri()); + + request.Region = "region"; + Assert.AreEqual(new Uri("https://api.region.twilio.com"), request.buildUri()); + + request.Edge = "edge"; + Assert.AreEqual(new Uri("https://api.edge.region.twilio.com"), request.buildUri()); + + request.Region = null; + Assert.AreEqual(new Uri("https://api.edge.us1.twilio.com"), request.buildUri()); + } + + [Test] + public void TestRegionInUrl() + { + var request = new Request(HttpMethod.Get, "https://api.urlRegion.twilio.com"); + + Assert.AreEqual(new Uri("https://api.urlRegion.twilio.com"), request.buildUri()); + + request.Region = "region"; + Assert.AreEqual(new Uri("https://api.region.twilio.com"), request.buildUri()); + + request.Edge = "edge"; + Assert.AreEqual(new Uri("https://api.edge.region.twilio.com"), request.buildUri()); + + request.Region = null; + Assert.AreEqual(new Uri("https://api.edge.urlRegion.twilio.com"), request.buildUri()); + } + + [Test] + public void TestRegionAndEdgeInUrl() + { + var request = new Request(HttpMethod.Get, "https://api.urlEdge.urlRegion.twilio.com"); + + Assert.AreEqual(new Uri("https://api.urlEdge.urlRegion.twilio.com"), request.buildUri()); + + request.Region = "region"; + Assert.AreEqual(new Uri("https://api.urlEdge.region.twilio.com"), request.buildUri()); + + request.Edge = "edge"; + Assert.AreEqual(new Uri("https://api.edge.region.twilio.com"), request.buildUri()); + + request.Region = null; + Assert.AreEqual(new Uri("https://api.edge.urlRegion.twilio.com"), request.buildUri()); + } + + [Test] + public void TestRegionAndEdgeInConstrcutor() + { + var request = new Request(HttpMethod.Get, Domain.Accounts, "/path/to/something.json?foo=12.34", region: "region", edge: "edge"); + + Assert.AreEqual(new Uri("https://accounts.edge.region.twilio.com/path/to/something.json?foo=12.34"), request.buildUri()); + } + } +}