summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-09-25 17:39:51 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-09-25 17:39:51 -0400
commit6dff9779a349945d502dee67d5d4dd8e63b4f753 (patch)
tree7621f85a392329cb0a782355feff252b6d34e2d6
parentb105c97dda01441d503d31e8b8ac0b3fd35fef14 (diff)
downloadSMAPI-6dff9779a349945d502dee67d5d4dd8e63b4f753.tar.gz
SMAPI-6dff9779a349945d502dee67d5d4dd8e63b4f753.tar.bz2
SMAPI-6dff9779a349945d502dee67d5d4dd8e63b4f753.zip
use POST for SMAPI update checks to avoid issues with long queries (#336)
-rw-r--r--src/StardewModdingAPI.Models/ModSeachModel.cs30
-rw-r--r--src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems1
-rw-r--r--src/StardewModdingAPI.Web/Controllers/ModsController.cs17
-rw-r--r--src/StardewModdingAPI.Web/Framework/VersionConstraint.cs15
-rw-r--r--src/StardewModdingAPI.Web/Startup.cs19
-rw-r--r--src/StardewModdingAPI/Framework/WebApiClient.cs29
6 files changed, 87 insertions, 24 deletions
diff --git a/src/StardewModdingAPI.Models/ModSeachModel.cs b/src/StardewModdingAPI.Models/ModSeachModel.cs
new file mode 100644
index 00000000..526fbaf3
--- /dev/null
+++ b/src/StardewModdingAPI.Models/ModSeachModel.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StardewModdingAPI.Models
+{
+ /// <summary>Specifies mods whose update-check info to fetch.</summary>
+ internal class ModSearchModel
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The namespaced mod keys to search.</summary>
+ public string[] ModKeys { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an empty instance.</summary>
+ /// <remarks>This constructed is needed for JSON deserialisation.</remarks>
+ public ModSearchModel() { }
+
+ /// <summary>Construct an valid instance.</summary>
+ /// <param name="modKeys">The namespaced mod keys to search.</param>
+ public ModSearchModel(IEnumerable<string> modKeys)
+ {
+ this.ModKeys = modKeys.ToArray();
+ }
+ }
+}
diff --git a/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems b/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems
index 2465760e..e2cb29e1 100644
--- a/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems
+++ b/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems
@@ -9,6 +9,7 @@
<Import_RootNamespace>StardewModdingAPI.Models</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)ModSeachModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ModInfoModel.cs" />
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs
index 566577e4..f29de45a 100644
--- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs
+++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs
@@ -14,6 +14,7 @@ namespace StardewModdingAPI.Web.Controllers
{
/// <summary>Provides an API to perform mod update checks.</summary>
[Produces("application/json")]
+ [Route("api/{version:semanticVersion}/[controller]")]
internal class ModsController : Controller
{
/*********
@@ -80,15 +81,27 @@ namespace StardewModdingAPI.Web.Controllers
[HttpGet]
public async Task<IDictionary<string, ModInfoModel>> GetAsync(string modKeys)
{
+ string[] modKeysArray = modKeys?.Split(',').Select(p => p.Trim()).ToArray();
+ if (modKeysArray == null || !modKeysArray.Any())
+ return new Dictionary<string, ModInfoModel>();
+
+ return await this.PostAsync(new ModSearchModel(modKeysArray));
+ }
+
+ /// <summary>Fetch version metadata for the given mods.</summary>
+ /// <param name="search">The mod search criteria.</param>
+ [HttpPost]
+ public async Task<IDictionary<string, ModInfoModel>> PostAsync([FromBody] ModSearchModel search)
+ {
// sort & filter keys
- string[] modKeysArray = (modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0])
+ string[] modKeys = (search?.ModKeys?.ToArray() ?? new string[0])
.Distinct(StringComparer.CurrentCultureIgnoreCase)
.OrderBy(p => p, StringComparer.CurrentCultureIgnoreCase)
.ToArray();
// fetch mod info
IDictionary<string, ModInfoModel> result = new Dictionary<string, ModInfoModel>(StringComparer.CurrentCultureIgnoreCase);
- foreach (string modKey in modKeysArray)
+ foreach (string modKey in modKeys)
{
// parse mod key
if (!this.TryParseModKey(modKey, out string vendorKey, out string modID))
diff --git a/src/StardewModdingAPI.Web/Framework/VersionConstraint.cs b/src/StardewModdingAPI.Web/Framework/VersionConstraint.cs
new file mode 100644
index 00000000..be9c0918
--- /dev/null
+++ b/src/StardewModdingAPI.Web/Framework/VersionConstraint.cs
@@ -0,0 +1,15 @@
+using Microsoft.AspNetCore.Routing.Constraints;
+
+namespace StardewModdingAPI.Web.Framework
+{
+ /// <summary>Constrains a route value to a valid semantic version.</summary>
+ internal class VersionConstraint : RegexRouteConstraint
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public VersionConstraint()
+ : base(@"^v(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>(?>[a-z0-9]+[\-\.]?)+))?$") { }
+ }
+}
diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs
index d5b828b7..eaf14983 100644
--- a/src/StardewModdingAPI.Web/Startup.cs
+++ b/src/StardewModdingAPI.Web/Startup.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Rewrite;
+using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -42,6 +43,7 @@ namespace StardewModdingAPI.Web
{
services
.Configure<ModUpdateCheckConfig>(this.Configuration.GetSection("ModUpdateCheck"))
+ .Configure<RouteOptions>(options => options.ConstraintMap.Add("semanticVersion", typeof(VersionConstraint)))
.AddMemoryCache()
.AddMvc()
.ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(new InternalControllerFeatureProvider()))
@@ -62,22 +64,7 @@ namespace StardewModdingAPI.Web
loggerFactory.AddDebug();
app
.UseRewriter(new RewriteOptions().Add(new RewriteSubdomainRule())) // convert subdomain.smapi.io => smapi.io/subdomain for routing
- .UseMvc(route =>
- {
- route.MapRoute(
- name: "API",
- template: "api/{version}/{controller}/{action?}",
- defaults: new
- {
- action = "GetAsync"
- },
- constraints: new
- {
- // version regex from SMAPI's SemanticVersion implementation
- version = @"^v(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>(?>[a-z0-9]+[\-\.]?)+))?$"
- }
- );
- });
+ .UseMvc();
}
}
}
diff --git a/src/StardewModdingAPI/Framework/WebApiClient.cs b/src/StardewModdingAPI/Framework/WebApiClient.cs
index 0ee57648..8f0b403d 100644
--- a/src/StardewModdingAPI/Framework/WebApiClient.cs
+++ b/src/StardewModdingAPI/Framework/WebApiClient.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using StardewModdingAPI.Models;
@@ -40,8 +41,10 @@ namespace StardewModdingAPI.Framework
/// <param name="modKeys">The mod keys for which to fetch the latest version.</param>
public async Task<IDictionary<string, ModInfoModel>> GetModInfoAsync(params string[] modKeys)
{
- string url = $"v{this.Version}/mods?modKeys={Uri.EscapeDataString(string.Join(",", modKeys))}";
- return await this.GetAsync<Dictionary<string, ModInfoModel>>(url);
+ return await this.PostAsync<ModSearchModel, Dictionary<string, ModInfoModel>>(
+ $"v{this.Version}/mods",
+ new ModSearchModel(modKeys)
+ );
}
@@ -49,13 +52,27 @@ namespace StardewModdingAPI.Framework
** Private methods
*********/
/// <summary>Fetch the response from the backend API.</summary>
- /// <typeparam name="T">The expected response type.</typeparam>
+ /// <typeparam name="TBody">The body content type.</typeparam>
+ /// <typeparam name="TResult">The expected response type.</typeparam>
/// <param name="url">The request URL, optionally excluding the base URL.</param>
- private async Task<T> GetAsync<T>(string url)
+ /// <param name="content">The body content to post.</param>
+ private async Task<TResult> PostAsync<TBody, TResult>(string url, TBody content)
{
- // build request (avoid HttpClient for Mac compatibility)
+ /***
+ ** Note: avoid HttpClient for Mac compatibility.
+ ***/
+
+ // serialise content
+ byte[] data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content));
+
+ // build request
HttpWebRequest request = WebRequest.CreateHttp(new Uri(this.BaseUrl, url).ToString());
+ request.Method = "POST";
request.UserAgent = $"SMAPI/{this.Version}";
+ request.ContentType = "application/json";
+ request.ContentLength = data.Length;
+ using (Stream bodyStream = request.GetRequestStream())
+ bodyStream.Write(data, 0, data.Length);
// fetch data
using (WebResponse response = await request.GetResponseAsync())
@@ -63,7 +80,7 @@ namespace StardewModdingAPI.Framework
using (StreamReader reader = new StreamReader(responseStream))
{
string responseText = reader.ReadToEnd();
- return JsonConvert.DeserializeObject<T>(responseText);
+ return JsonConvert.DeserializeObject<TResult>(responseText);
}
}
}