autodesk-forge - http://localhost:3000/api/forge/designautomation/workitems 500(内部服务器错误)jquery.min.js:2 POST
问题描述
我正在尝试 Revit 中的设计自动化分步教程,以修改来自 learn.autodesk.io 的模型。即使几天前,这段代码也能正常工作,但今天我突然面临这个错误。我尝试按照教程再次重新创建整个项目示例,但这个错误并没有消失。谁能解释是什么原因造成的?
错误日志:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HM7HP2HS8PF1", Request id "0HM7HP2HS8PF1:00000002": An unhandled exception was thrown by the application.
Autodesk.Forge.Client.ApiException: Error calling UploadObject: Error while copying content to a stream. Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
at Autodesk.Forge.ObjectsApi.UploadObjectAsyncWithHttpInfo(String bucketKey, String objectName, Nullable`1 contentLength, Stream body, String contentDisposition, String ifMatch, String contentType)
at Autodesk.Forge.ObjectsApi.UploadObjectAsync(String bucketKey, String objectName, Nullable`1 contentLength, Stream body, String contentDisposition, String ifMatch, String contentType)
at forgeSample.Controllers.DesignAutomationController.StartWorkitem(StartWorkitemInput input) in E:\Test-2nd_Attempt\forgeSample\Controllers\DesignAutomationController.cs:line 275
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
设计自动化控制器.cs
using Autodesk.Forge;
using Autodesk.Forge.DesignAutomation;
using Autodesk.Forge.DesignAutomation.Model;
using Autodesk.Forge.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Activity = Autodesk.Forge.DesignAutomation.Model.Activity;
using Alias = Autodesk.Forge.DesignAutomation.Model.Alias;
using AppBundle = Autodesk.Forge.DesignAutomation.Model.AppBundle;
using Parameter = Autodesk.Forge.DesignAutomation.Model.Parameter;
using WorkItem = Autodesk.Forge.DesignAutomation.Model.WorkItem;
using WorkItemStatus = Autodesk.Forge.DesignAutomation.Model.WorkItemStatus;
namespace forgeSample.Controllers {
[ApiController]
public class DesignAutomationController: ControllerBase {
// Used to access the application folder (temp location for files & bundles)
private IWebHostEnvironment _env;
// used to access the SignalR Hub
private IHubContext < DesignAutomationHub > _hubContext;
// Local folder for bundles
public string LocalBundlesFolder {
get {
return Path.Combine(_env.WebRootPath, "bundles");
}
}
/// Prefix for AppBundles and Activities
public static string NickName {
get {
return OAuthController.GetAppSetting("FORGE_CLIENT_ID");
}
}
/// Alias for the app (e.g. DEV, STG, PROD). This value may come from an environment variable
public static string Alias {
get {
return "dev";
}
}
// Design Automation v3 API
DesignAutomationClient _designAutomation;
// Constructor, where env and hubContext are specified
public DesignAutomationController(IWebHostEnvironment env, IHubContext < DesignAutomationHub > hubContext, DesignAutomationClient api) {
_designAutomation = api;
_env = env;
_hubContext = hubContext;
}
// **********************************
//
/// <summary>
/// Names of app bundles on this project
/// </summary>
[HttpGet]
[Route("api/appbundles")]
public string[] GetLocalBundles() {
// this folder is placed under the public folder, which may expose the bundles
// but it was defined this way so it be published on most hosts easily
return Directory.GetFiles(LocalBundlesFolder, "*.zip").Select(Path.GetFileNameWithoutExtension).ToArray();
}
/// <summary>
/// Return a list of available engines
/// </summary>
[HttpGet]
[Route("api/forge/designautomation/engines")]
public async Task < List < string >> GetAvailableEngines() {
dynamic oauth = await OAuthController.GetInternalAsync();
// define Engines API
Page < string > engines = await _designAutomation.GetEnginesAsync();
engines.Data.Sort();
return engines.Data; // return list of engines
}
/// <summary>
/// Define a new appbundle
/// </summary>
[HttpPost]
[Route("api/forge/designautomation/appbundles")]
public async Task < IActionResult > CreateAppBundle([FromBody] JObject appBundleSpecs) {
// basic input validation
string zipFileName = appBundleSpecs["zipFileName"].Value < string > ();
string engineName = appBundleSpecs["engine"].Value < string > ();
// standard name for this sample
string appBundleName = zipFileName + "AppBundle";
// check if ZIP with bundle is here
string packageZipPath = Path.Combine(LocalBundlesFolder, zipFileName + ".zip");
if (!System.IO.File.Exists(packageZipPath)) throw new Exception("Appbundle not found at " + packageZipPath);
// get defined app bundles
Page < string > appBundles = await _designAutomation.GetAppBundlesAsync();
// check if app bundle is already define
dynamic newAppVersion;
string qualifiedAppBundleId = string.Format("{0}.{1}+{2}", NickName, appBundleName, Alias);
if (!appBundles.Data.Contains(qualifiedAppBundleId)) {
// create an appbundle (version 1)
AppBundle appBundleSpec = new AppBundle() {
Package = appBundleName,
Engine = engineName,
Id = appBundleName,
Description = string.Format("Description for {0}", appBundleName),
};
newAppVersion = await _designAutomation.CreateAppBundleAsync(appBundleSpec);
if (newAppVersion == null) throw new Exception("Cannot create new app");
// create alias pointing to v1
Alias aliasSpec = new Alias() {
Id = Alias, Version = 1
};
Alias newAlias = await _designAutomation.CreateAppBundleAliasAsync(appBundleName, aliasSpec);
} else {
// create new version
AppBundle appBundleSpec = new AppBundle() {
Engine = engineName,
Description = appBundleName
};
newAppVersion = await _designAutomation.CreateAppBundleVersionAsync(appBundleName, appBundleSpec);
if (newAppVersion == null) throw new Exception("Cannot create new version");
// update alias pointing to v+1
AliasPatch aliasSpec = new AliasPatch() {
Version = newAppVersion.Version
};
Alias newAlias = await _designAutomation.ModifyAppBundleAliasAsync(appBundleName, Alias, aliasSpec);
}
// upload the zip with .bundle
RestClient uploadClient = new RestClient(newAppVersion.UploadParameters.EndpointURL);
RestRequest request = new RestRequest(string.Empty, Method.POST);
request.AlwaysMultipartFormData = true;
foreach(KeyValuePair < string, string > x in newAppVersion.UploadParameters.FormData) request.AddParameter(x.Key, x.Value);
request.AddFile("file", packageZipPath);
request.AddHeader("Cache-Control", "no-cache");
await uploadClient.ExecuteAsync(request);
return Ok(new {
AppBundle = qualifiedAppBundleId, Version = newAppVersion.Version
});
}
/// <summary>
/// Helps identify the engine
/// </summary>
private dynamic EngineAttributes(string engine) {
if (engine.Contains("3dsMax")) return new {
commandLine = "$(engine.path)\\3dsmaxbatch.exe -sceneFile \"$(args[inputFile].path)\" $(settings[script].path)", extension = "max", script = "da = dotNetClass(\"Autodesk.Forge.Sample.DesignAutomation.Max.RuntimeExecute\")\nda.ModifyWindowWidthHeight()\n"
};
if (engine.Contains("AutoCAD")) return new {
commandLine = "$(engine.path)\\accoreconsole.exe /i \"$(args[inputFile].path)\" /al \"$(appbundles[{0}].path)\" /s $(settings[script].path)", extension = "dwg", script = "UpdateParam\n"
};
if (engine.Contains("Inventor")) return new {
commandLine = "$(engine.path)\\inventorcoreconsole.exe /i \"$(args[inputFile].path)\" /al \"$(appbundles[{0}].path)\"", extension = "ipt", script = string.Empty
};
if (engine.Contains("Revit")) return new {
commandLine = "$(engine.path)\\revitcoreconsole.exe /i \"$(args[inputFile].path)\" /al \"$(appbundles[{0}].path)\"", extension = "rvt", script = string.Empty
};
throw new Exception("Invalid engine");
}
/// <summary>
/// Define a new activity
/// </summary>
[HttpPost]
[Route("api/forge/designautomation/activities")]
public async Task < IActionResult > CreateActivity([FromBody] JObject activitySpecs) {
// basic input validation
string zipFileName = activitySpecs["zipFileName"].Value < string > ();
string engineName = activitySpecs["engine"].Value < string > ();
// standard name for this sample
string appBundleName = zipFileName + "AppBundle";
string activityName = zipFileName + "Activity";
//
Page < string > activities = await _designAutomation.GetActivitiesAsync();
string qualifiedActivityId = string.Format("{0}.{1}+{2}", NickName, activityName, Alias);
if (!activities.Data.Contains(qualifiedActivityId)) {
// define the activity
// ToDo: parametrize for different engines...
dynamic engineAttributes = EngineAttributes(engineName);
string commandLine = string.Format(engineAttributes.commandLine, appBundleName);
Activity activitySpec = new Activity() {
Id = activityName,
Appbundles = new List < string > () {
string.Format("{0}.{1}+{2}", NickName, appBundleName, Alias)
},
CommandLine = new List < string > () {
commandLine
},
Engine = engineName,
Parameters = new Dictionary < string, Parameter > () {
{
"inputFile",
new Parameter() {
Description = "input file", LocalName = "$(inputFile)", Ondemand = false, Required = true, Verb = Verb.Get, Zip = false
}
}, {
"inputJson",
new Parameter() {
Description = "input json", LocalName = "params.json", Ondemand = false, Required = false, Verb = Verb.Get, Zip = false
}
}, {
"outputFile",
new Parameter() {
Description = "output file", LocalName = "outputFile." + engineAttributes.extension, Ondemand = false, Required = true, Verb = Verb.Put, Zip = false
}
}
},
Settings = new Dictionary < string, ISetting > () {
{
"script",
new StringSetting() {
Value = engineAttributes.script
}
}
}
};
Activity newActivity = await _designAutomation.CreateActivityAsync(activitySpec);
// specify the alias for this Activity
Alias aliasSpec = new Alias() {
Id = Alias, Version = 1
};
Alias newAlias = await _designAutomation.CreateActivityAliasAsync(activityName, aliasSpec);
return Ok(new {
Activity = qualifiedActivityId
});
}
// as this activity points to a AppBundle "dev" alias (which points to the last version of the bundle),
// there is no need to update it (for this sample), but this may be extended for different contexts
return Ok(new {
Activity = "Activity already defined"
});
}
/// <summary>
/// Get all Activities defined for this account
/// </summary>
[HttpGet]
[Route("api/forge/designautomation/activities")]
public async Task < List < string >> GetDefinedActivities() {
// filter list of
Page < string > activities = await _designAutomation.GetActivitiesAsync();
List < string > definedActivities = new List < string > ();
foreach(string activity in activities.Data)
if (activity.StartsWith(NickName) && activity.IndexOf("$LATEST") == -1)
definedActivities.Add(activity.Replace(NickName + ".", String.Empty));
return definedActivities;
}
/// <summary>
/// Start a new workitem
/// </summary>
[HttpPost]
[Route("api/forge/designautomation/workitems")]
public async Task < IActionResult > StartWorkitem([FromForm] StartWorkitemInput input) {
// basic input validation
JObject workItemData = JObject.Parse(input.data);
string widthParam = workItemData["width"].Value < string > ();
string heigthParam = workItemData["height"].Value < string > ();
string activityName = string.Format("{0}.{1}", NickName, workItemData["activityName"].Value < string > ());
string browerConnectionId = workItemData["browerConnectionId"].Value < string > ();
// save the file on the server
var fileSavePath = Path.Combine(_env.ContentRootPath, Path.GetFileName(input.inputFile.FileName));
using(var stream = new FileStream(fileSavePath, FileMode.Create)) await input.inputFile.CopyToAsync(stream);
// OAuth token
dynamic oauth = await OAuthController.GetInternalAsync();
// upload file to OSS Bucket
// 1. ensure bucket existis
string bucketKey = NickName.ToLower() + "-designautomation";
BucketsApi buckets = new BucketsApi();
buckets.Configuration.AccessToken = oauth.access_token;
try {
PostBucketsPayload bucketPayload = new PostBucketsPayload(bucketKey, null, PostBucketsPayload.PolicyKeyEnum.Transient);
await buckets.CreateBucketAsync(bucketPayload, "US");
} catch {}; // in case bucket already exists
// 2. upload inputFile
string inputFileNameOSS = string.Format("{0}_input_{1}", DateTime.Now.ToString("yyyyMMddhhmmss"), Path.GetFileName(input.inputFile.FileName)); // avoid overriding
ObjectsApi objects = new ObjectsApi();
objects.Configuration.AccessToken = oauth.access_token;
using(StreamReader streamReader = new StreamReader(fileSavePath))
await objects.UploadObjectAsync(bucketKey, inputFileNameOSS, (int) streamReader.BaseStream.Length, streamReader.BaseStream, "application/octet-stream");
System.IO.File.Delete(fileSavePath); // delete server copy
// prepare workitem arguments
// 1. input file
XrefTreeArgument inputFileArgument = new XrefTreeArgument() {
Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", bucketKey, inputFileNameOSS),
Headers = new Dictionary < string, string > () {
{
"Authorization",
"Bearer " + oauth.access_token
}
}
};
// 2. input json
dynamic inputJson = new JObject();
inputJson.Width = widthParam;
inputJson.Height = heigthParam;
XrefTreeArgument inputJsonArgument = new XrefTreeArgument() {
Url = "data:application/json, " + ((JObject) inputJson).ToString(Formatting.None).Replace("\"", "'")
};
// 3. output file
string outputFileNameOSS = string.Format("{0}_output_{1}", DateTime.Now.ToString("yyyyMMddhhmmss"), Path.GetFileName(input.inputFile.FileName)); // avoid overriding
XrefTreeArgument outputFileArgument = new XrefTreeArgument() {
Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", bucketKey, outputFileNameOSS),
Verb = Verb.Put,
Headers = new Dictionary < string, string > () {
{
"Authorization",
"Bearer " + oauth.access_token
}
}
};
// prepare & submit workitem
// the callback contains the connectionId (used to identify the client) and the outputFileName of this workitem
string callbackUrl = string.Format("{0}/api/forge/callback/designautomation?id={1}&outputFileName={2}", OAuthController.GetAppSetting("FORGE_WEBHOOK_URL"), browerConnectionId, outputFileNameOSS);
WorkItem workItemSpec = new WorkItem() {
ActivityId = activityName,
Arguments = new Dictionary < string, IArgument > () {
{
"inputFile",
inputFileArgument
}, {
"inputJson",
inputJsonArgument
}, {
"outputFile",
outputFileArgument
}, {
"onComplete",
new XrefTreeArgument {
Verb = Verb.Post, Url = callbackUrl
}
}
}
};
WorkItemStatus workItemStatus = await _designAutomation.CreateWorkItemAsync(workItemSpec);
return Ok(new {
WorkItemId = workItemStatus.Id
});
}
/// <summary>
/// Input for StartWorkitem
/// </summary>
public class StartWorkitemInput {
public IFormFile inputFile {
get;
set;
}
public string data {
get;
set;
}
}
/// <summary>
/// Callback from Design Automation Workitem (onProgress or onComplete)
/// </summary>
[HttpPost]
[Route("/api/forge/callback/designautomation")]
public async Task < IActionResult > OnCallback(string id, string outputFileName, [FromBody] dynamic body) {
try {
// your webhook should return immediately! we can use Hangfire to schedule a job
JObject bodyJson = JObject.Parse((string) body.ToString());
await _hubContext.Clients.Client(id).SendAsync("onComplete", bodyJson.ToString());
var client = new RestClient(bodyJson["reportUrl"].Value < string > ());
var request = new RestRequest(string.Empty);
// send the result output log to the client
byte[] bs = client.DownloadData(request);
string report = System.Text.Encoding.Default.GetString(bs);
await _hubContext.Clients.Client(id).SendAsync("onComplete", report);
// generate a signed URL to download the result file and send to the client
ObjectsApi objectsApi = new ObjectsApi();
dynamic signedUrl = await objectsApi.CreateSignedResourceAsyncWithHttpInfo(NickName.ToLower() + "-designautomation", outputFileName, new PostBucketsSigned(10), "read");
await _hubContext.Clients.Client(id).SendAsync("downloadResult", (string)(signedUrl.Data.signedUrl));
} catch {}
// ALWAYS return ok (200)
return Ok();
}
/// <summary>
/// Clear the accounts (for debugging purpouses)
/// </summary>
[HttpDelete]
[Route("api/forge/designautomation/account")]
public async Task < IActionResult > ClearAccount() {
// clear account
await _designAutomation.DeleteForgeAppAsync("me");
return Ok();
}
}
/// <summary>
/// Class uses for SignalR
/// </summary>
public class DesignAutomationHub: Microsoft.AspNetCore.SignalR.Hub {
public string GetConnectionId() {
return Context.ConnectionId;
}
}
}
解决方案
此错误来自[Storage API][1]
. 我可以想象它可能有一个暂时的问题。你现在可以重试看看它是否有效吗?
推荐阅读
- amazon-web-services - Nginx:客户端请求正文缓冲到临时文件
- javascript - 使用 redux 为什么我的应用程序状态 isAuthenticated 在任何类型的重新加载后都不会保持设置为 true?
- prolog - 执行列表转换,Prolog
- python - 如何访问电子表格中的另一个页面?
- javascript - 车把问题
- android - 方法代码太大!android compileSdkVersion 27 更改为 compileSdkVersion 30 后
- angular - 在 NgRx 中,在哪里将来自服务器的响应数据(通过副作用获得)转换为组件可以理解的状态?
- python - 如何使用 python 将 UTC 时间转换为 UTC 中的相应日期和时间?
- python - 如何永远运行python代码直到停止它?
- javascript - 使用 Javascript 的文本框中的 Control+N 键事件