使用Windows Device Portal REST API部署Hololens应用遇CSRF及413错误求助
我来帮你搞定这俩困扰你的问题,这都是Windows Device Portal API的典型坑,咱们一步步来拆解解决:
一、搞定CSRF Token Invalid错误
Windows Device Portal的CSRF防护不是只靠Cookie就行的,它需要同时携带Cookie和请求头里的CSRF Token,你之前只加了Cookie肯定不行,正确流程是这样的:
先获取有效的CSRF Token
发送一个GET请求到Hololens Device Portal的/api/csrf接口,这个接口会返回JSON格式的响应,里面包含csrfToken字段;同时响应头里会有Set-Cookie,把这个Cookie(尤其是XSRF-TOKEN)保存到Cookie容器里。
用RestSharp的示例代码大概是这样:var client = new RestClient("http://your-hololens-ip"); client.CookieContainer = new CookieContainer(); // 要复用这个容器! // 先获取CSRF Token var csrfRequest = new RestRequest("/api/csrf", Method.Get); csrfRequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password"))); // 替换成你的Device Portal账号密码 var csrfResponse = await client.ExecuteAsync(csrfRequest); var csrfToken = JsonConvert.DeserializeObject<CsrfResponse>(csrfResponse.Content).csrfToken; // 要定义对应的CsrfResponse类 // 此时CookieContainer已经自动保存了返回的Cookie部署请求时同时携带Token和Cookie
发送POST请求部署应用时,除了带上之前的Cookie容器,还要在请求头里添加X-CSRF-Token字段,值就是刚才拿到的csrfToken。
示例代码片段:var deployRequest = new RestRequest("/api/apps/package", Method.Post); deployRequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password"))); deployRequest.AddHeader("X-CSRF-Token", csrfToken); // 关键! deployRequest.AddFile("package", "path/to/your/app.msix"); // 添加要部署的文件 var deployResponse = await client.ExecuteAsync(deployRequest);Postman里的操作步骤
- 先发送GET请求到
/api/csrf,复制返回的csrfToken值 - 在POST部署请求的Headers里添加
X-CSRF-Token,值为刚才复制的token - 确保请求的Cookie里包含
XSRF-TOKEN(Postman会自动保存之前请求的Cookie,只要你是同一个会话)
- 先发送GET请求到
二、解决413 Payload Too Large错误
Web版Device Portal能传大文件是因为它用了分块上传,直接POST整个大文件会触发Device Portal的大小限制,所以你得用分块上传的API:
初始化上传会话
发送POST请求到/api/apps/package/upload,请求头带CSRF Token和Authorization,请求体里指定文件名和总大小,获取会话ID:var initRequest = new RestRequest("/api/apps/package/upload", Method.Post); initRequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password"))); initRequest.AddHeader("X-CSRF-Token", csrfToken); initRequest.AddParameter("fileName", "your-app.msix"); initRequest.AddParameter("fileSize", new FileInfo("path/to/your/app.msix").Length); var initResponse = await client.ExecuteAsync(initRequest); var sessionId = JsonConvert.DeserializeObject<UploadInitResponse>(initResponse.Content).sessionId; // 定义对应的类分块上传文件
把大文件分成若干块(比如每块10MB),依次发送PUT请求到/api/apps/package/upload/{sessionId},每个请求要带Content-Range头,指定当前块的字节范围:var fileBytes = File.ReadAllBytes("path/to/your/app.msix"); int chunkSize = 10 * 1024 * 1024; // 10MB每块 int totalChunks = (int)Math.Ceiling((double)fileBytes.Length / chunkSize); for (int i = 0; i < totalChunks; i++) { int start = i * chunkSize; int end = Math.Min(start + chunkSize - 1, fileBytes.Length - 1); var chunk = fileBytes.Skip(start).Take(end - start + 1).ToArray(); var chunkRequest = new RestRequest($"/api/apps/package/upload/{sessionId}", Method.Put); chunkRequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password"))); chunkRequest.AddHeader("X-CSRF-Token", csrfToken); chunkRequest.AddHeader("Content-Range", $"bytes {start}-{end}/{fileBytes.Length}"); chunkRequest.AddParameter("application/octet-stream", chunk, ParameterType.RequestBody); await client.ExecuteAsync(chunkRequest); }提交上传完成部署
所有块上传完后,发送POST请求到/api/apps/package/upload/{sessionId}/commit:var commitRequest = new RestRequest($"/api/apps/package/upload/{sessionId}/commit", Method.Post); commitRequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password"))); commitRequest.AddHeader("X-CSRF-Token", csrfToken); var commitResponse = await client.ExecuteAsync(commitRequest);
额外注意点
- 一定要确保所有请求复用同一个
CookieContainer,这样会话才能保持有效 - Device Portal的账号密码要正确,Authorization头不能少,否则也会出现403错误
- 分块大小可以根据网络情况调整,不用拘泥于10MB,只要不超过单请求的大小限制就行
内容的提问来源于stack exchange,提问作者elpha01

