Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
Merge pull request #429 from hasankhan/pulluri
Browse files Browse the repository at this point in the history
[client][managed][offline] Support absolute uri in pull same as table.read.
  • Loading branch information
hasankhan committed Oct 9, 2014
2 parents de19f92 + c9d8e39 commit a566f0a
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,45 @@ namespace Microsoft.WindowsAzure.MobileServices
{
internal static class HttpUtility
{

/// <summary>
/// Tries to parse the query as relative or absolute uri
/// </summary>
/// <param name="applicationUri">The application uri to use as base</param>
/// <param name="query">The query string that may be relative path starting with slash or absolute uri</param>
/// <param name="uri">The uri in case it was relative path or absolute uri</param>
/// <returns>True if it was relative or absolute uri, False otherwise</returns>
public static bool TryParseQueryUri(Uri applicationUri, string query, out Uri uri, out bool absolute)
{
if (query.StartsWith("/") && Uri.TryCreate(applicationUri, query, out uri))
{
absolute = false;
return true;
}
else if (Uri.TryCreate(query, UriKind.Absolute, out uri))
{
if (uri.Host != applicationUri.Host)
{
throw new ArgumentException(Resources.MobileServiceTable_QueryUriHostIsDifferent);
}

absolute = true;
return true;
}
else
{
absolute = false;
return false;
}
}

/// Returns the complete uri excluding the query part
public static string GetUriWithoutQuery(Uri uri)
{
string path = uri.GetComponents(UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
return path;
}

/// <summary>
/// Parses a query string into a <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>
/// </summary>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,7 @@
<data name="MobileServiceSyncTable_OrderByNotAllowed" xml:space="preserve">
<value>The supported table options does not include orderby.</value>
</data>
<data name="MobileServiceTable_QueryUriHostIsDifferent" xml:space="preserve">
<value>The query uri must be on the same host as the Mobile Service.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,14 @@ internal virtual async Task<QueryResult> ReadAsync(string query, IDictionary<str

string uriPath;
Uri uri;
if (query.StartsWith("/") && Uri.TryCreate(this.MobileServiceClient.ApplicationUri, query, out uri))
bool absolute;
if (HttpUtility.TryParseQueryUri(this.MobileServiceClient.ApplicationUri, query, out uri, out absolute))
{
uriPath = uri.GetComponents(UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
query = uri.Query;
}
else if (Uri.TryCreate(query, UriKind.Absolute, out uri))
{
features |= MobileServiceFeatures.ReadWithLinkHeader;
uriPath = uri.GetComponents(UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
if (absolute)
{
features |= MobileServiceFeatures.ReadWithLinkHeader;
}
uriPath = HttpUtility.GetUriWithoutQuery(uri);
query = uri.Query;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public MobileServiceTableQueryDescription(string tableName)
this.Ordering = new List<OrderByNode>();
}

/// <summary>
/// Gets the uri path if the query was an absolute or relative uri
/// </summary>
internal string UriPath { get; private set; }

/// <summary>
/// Gets or sets the name of the table being queried.
/// </summary>
Expand Down Expand Up @@ -188,6 +193,27 @@ public string ToODataString()
public static MobileServiceTableQueryDescription Parse(string tableName, string query)
{
query = query ?? String.Empty;

return Parse(tableName, query, null);
}

internal static MobileServiceTableQueryDescription Parse(Uri applicationUri, string tableName, string query)
{
query = query ?? String.Empty;
string uriPath = null;
Uri uri;
bool absolute;
if (HttpUtility.TryParseQueryUri(applicationUri, query, out uri, out absolute))
{
query = uri.Query.Length > 0 ? uri.Query.Substring(1) : String.Empty;
uriPath = uri.AbsolutePath;
}

return Parse(tableName, query, uriPath);
}

private static MobileServiceTableQueryDescription Parse(string tableName, string query, string uriPath)
{
bool includeTotalCount = false;
int? top = null;
int? skip = null;
Expand Down Expand Up @@ -231,23 +257,24 @@ public static MobileServiceTableQueryDescription Parse(string tableName, string
}
}

var queryDescription = new MobileServiceTableQueryDescription(tableName)
var description = new MobileServiceTableQueryDescription(tableName)
{
IncludeTotalCount = includeTotalCount,
Skip = skip,
Top = top
};
description.UriPath = uriPath;
if (selection != null)
{
((List<string>)queryDescription.Selection).AddRange(selection);
((List<string>)description.Selection).AddRange(selection);
}
if (orderings != null)
{
((List<OrderByNode>)queryDescription.Ordering).AddRange(orderings);
((List<OrderByNode>)description.Ordering).AddRange(orderings);
}
queryDescription.Filter = filter;
description.Filter = filter;

return queryDescription;
return description;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ public async Task PullAsync(string tableName, string queryKey, string query, Mob
await this.EnsureInitializedAsync();

var table = await this.GetTable(tableName);
var queryDescription = MobileServiceTableQueryDescription.Parse(tableName, query);
var queryDescription = MobileServiceTableQueryDescription.Parse(this.client.ApplicationUri, tableName, query);


// local schema should be same as remote schema otherwise push can't function
if (queryDescription.Selection.Any() || queryDescription.Projections.Any())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ protected async override Task ProcessTableAsync()
{
this.CancellationToken.ThrowIfCancellationRequested();

string odata = this.Query.ToODataString();
result = await this.Table.ReadAsync(odata, MobileServiceTable.IncludeDeleted(parameters), this.Table.Features);
string query = this.Query.ToODataString();
if (this.Query.UriPath != null)
{
query = MobileServiceUrlBuilder.CombinePathAndQuery(this.Query.UriPath, query);
}
result = await this.Table.ReadAsync(query, MobileServiceTable.IncludeDeleted(parameters), this.Table.Features);
await this.ProcessAll(result.Values); // process the first batch

result = await FollowNextLinks(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="packages.config" />
<Compile Include="SerializationTypes\TypeWithConstructor.cs" />
<Compile Include="UnitTests\Http\HttpUtilityTests.cs" />
<Compile Include="UnitTests\OData\ODataExpressionParser.Test.cs" />
<Compile Include="UnitTests\Table\Sync\MobileServiceSyncContext.Test.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using Microsoft.WindowsAzure.MobileServices.TestFramework;

namespace Microsoft.WindowsAzure.MobileServices.Test
{
[Tag("unit")]
[Tag("http")]
public class HttpUtilityTests : TestBase
{

[TestMethod]
public static void TryParseQueryUri_ReturnsTrue_WhenQueryIsRelativeOrAbsoluteUri()
{
var data = new[]
{
new
{
ServiceUri = "http://www.test.com",
Query = "/about?$filter=a eq b&$orderby=c",
Absolute = false,
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
},
new
{
ServiceUri = "http://www.test.com/",
Query = "http://www.test.com/about?$filter=a eq b&$orderby=c",
Absolute = true,
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
}
};

foreach (var item in data)
{
Uri result;
bool absolute;
Assert.IsTrue(HttpUtility.TryParseQueryUri(new Uri(item.ServiceUri), item.Query, out result, out absolute));
Assert.AreEqual(absolute, item.Absolute);
AssertEx.QueryEquals(result.AbsoluteUri, item.Result);
}
}

[TestMethod]
public static void TryParseQueryUri_ReturnsFalse_WhenQueryIsNotRelativeOrAbsoluteUri()
{
var data = new[]
{
new
{
ServiceUri = "http://www.test.com",
Query = "about?$filter=a eq b&$orderby=c",
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
},
new
{
ServiceUri = "http://www.test.com/",
Query = "$filter=a eq b&$orderby=c",
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
}
};

foreach (var item in data)
{
Uri result;
bool absolute;
Assert.IsFalse(HttpUtility.TryParseQueryUri(new Uri(item.ServiceUri), item.Query, out result, out absolute));
Assert.IsFalse(absolute);
Assert.IsNull(result);
}
}

[TestMethod]
public void GetUriWithoutQuery_ReturnsUriWithPath()
{
Tuple<string, string>[] input = new[]
{
Tuple.Create("http://contoso.com/asdf?$filter=3", "http://contoso.com/asdf"),
Tuple.Create("http://contoso.com/asdf/def?$filter=3", "http://contoso.com/asdf/def"),
Tuple.Create("https://contoso.com/asdf/def?$filter=3", "https://contoso.com/asdf/def")
};

foreach (var item in input)
{
AssertEx.QueryEquals(HttpUtility.GetUriWithoutQuery(new Uri(item.Item1)), item.Item2);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,21 @@ public async Task ReadAsync_WithAbsoluteUri_Generic()

IMobileServiceTable<ToDo> table = service.GetTable<ToDo>();

await table.ReadAsync<ToDo>("http://wwww.contoso.com/about?$filter=a eq b&$orderby=c");
await table.ReadAsync<ToDo>("http://www.test.com/about?$filter=a eq b&$orderby=c");

Assert.AreEqual("TT,LH", hijack.Request.Headers.GetValues("X-ZUMO-FEATURES").First());
Assert.AreEqual("http://wwww.contoso.com/about?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
Assert.AreEqual("http://www.test.com/about?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
}

[AsyncTestMethod]
public async Task ReadAsync_Throws_IfAbsoluteUriHostNameDoesNotMatch()
{
IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", new TestHttpHandler());
IMobileServiceTable<ToDo> table = service.GetTable<ToDo>();

var ex = await AssertEx.Throws<ArgumentException>(async () => await table.ReadAsync<ToDo>("http://www.contoso.com/about?$filter=a eq b&$orderby=c"));

Assert.AreEqual(ex.Message, "The query uri must be on the same host as the Mobile Service.");
}

[AsyncTestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,10 @@ public async Task ReadAsync_WithAbsoluteUri()

IMobileServiceTable table = service.GetTable("someTable");

await table.ReadAsync("http://wwww.contoso.com/about/?$filter=a eq b&$orderby=c");
await table.ReadAsync("http://www.test.com/about/?$filter=a eq b&$orderby=c");

Assert.AreEqual("TU,LH", hijack.Request.Headers.GetValues("X-ZUMO-FEATURES").First());
Assert.AreEqual("http://wwww.contoso.com/about/?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
Assert.AreEqual("http://www.test.com/about/?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
}

[AsyncTestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// ----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -330,6 +329,51 @@ public async Task PullAsync_UsesSkipAndTakeThenFollowsLinkThenUsesSkipAndTake()
"http://www.test.com/tables/stringId_test_table?$skip=9&$top=45&__includeDeleted=true&__systemproperties=__version%2C__deleted");
}

[AsyncTestMethod]
public async Task PullAsync_Supports_AbsoluteAndRelativeUri()
{
var data = new string[]
{
"http://www.test.com/api/todoitem",
"/api/todoitem"
};

foreach (string uri in data)
{
var hijack = new TestHttpHandler();
hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]"); // first page
hijack.AddResponseContent("[]");

var store = new MobileServiceLocalStoreMock();
IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

await table.PullAsync(uri);

Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 2);

AssertEx.MatchUris(hijack.Requests, "http://www.test.com/api/todoitem?$skip=0&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted",
"http://www.test.com/api/todoitem?$skip=2&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted");

Assert.AreEqual("QS,OL", hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First());
Assert.AreEqual("QS,OL", hijack.Requests[1].Headers.GetValues("X-ZUMO-FEATURES").First());
}
}

public async Task PullASync_Throws_IfAbsoluteUriHostNameDoesNotMatch()
{
IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", new TestHttpHandler());
IMobileServiceSyncTable<ToDo> table = service.GetSyncTable<ToDo>();

var ex = await AssertEx.Throws<ArgumentException>(async () => await table.PullAsync("http://www.contoso.com/about?$filter=a eq b&$orderby=c"));

Assert.AreEqual(ex.Message, "The query uri must be on the same host as the Mobile Service.");
}

[AsyncTestMethod]
public async Task PullAsync_DoesNotFollowLink_IfRelationIsNotNext()
{
Expand Down

0 comments on commit a566f0a

Please sign in to comment.