Transfer file to hololens via POST httprequest (error 429)

Viewed 1650

I work on a desktop application to communicate with hololens glasses via httprequest (hololens is connected via USB), especially, I want to read and write files. To do that, I use the following code :

class BuildDeployPortal
{
    // Consts
    public static readonly string API_FileQuery = @"http://{0}/api/filesystem/apps/file";

    // Classes & Structs
    public struct ConnectInfo
    {
        public ConnectInfo(string ip, string user, string password)
        {
            IP = ip;
            User = user;
            Password = password;
        }

        public string IP;
        public string User;
        public string Password;
    }

    public async static void GetFile(ConnectInfo connectInfo, string knownfolderid, string filename, string packagefullname, string path)
    {
        try
        {
            // Query
            string query = string.Format(API_FileQuery, connectInfo.IP);
            query += "?knownfolderid=" + Uri.EscapeUriString(knownfolderid);
            query += "&filename=" + Uri.EscapeUriString(filename);
            query += "&packagefullname=" + Uri.EscapeUriString(packagefullname);
            query += "&path=" + Uri.EscapeUriString(path);

            // Create http request
            var httpRequest = new HttpClient();

            httpRequest.DefaultRequestHeaders.Clear();
            httpRequest.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic",
                                                                                        EncodeTo64(connectInfo.User + ":" + connectInfo.Password));

            HttpResponseMessage resp = await httpRequest.GetAsync(query);

            byte[] responseMessage = await resp.Content.ReadAsByteArrayAsync();

        }
        catch (Exception ex)
        {
            //log some problems
        }

    }



    public async static void UploadFile(ConnectInfo connectInfo, string knownfolderid, string packagefullname, string path, string filePath)
    {
        try
        {
            // Query
            string query = string.Format(API_FileQuery, connectInfo.IP);
            query += "?knownfolderid=" + Uri.EscapeUriString(knownfolderid);
            query += "&packagefullname=" + Uri.EscapeUriString(packagefullname);
            query += "&path=" + Uri.EscapeUriString(path);

            //  Create http request
            var httpRequest = new HttpClient();

            httpRequest.DefaultRequestHeaders.Clear();
            httpRequest.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic",
                                                                                        EncodeTo64(connectInfo.User + ":" + connectInfo.Password));
            byte[] data = File.ReadAllBytes(filePath);
            ByteArrayContent byteContent = new ByteArrayContent(data);

            HttpResponseMessage resp = await httpRequest.PostAsync(query, byteContent);

            var responseMessage = await resp.Content.ReadAsStringAsync();

        }
        catch (Exception ex)
        {
            //log some problems
        }

    }


    // Helpers
    private static string EncodeTo64(string toEncode)
    {
        byte[] toEncodeAsBytes = Encoding.ASCII.GetBytes(toEncode);
        string returnValue = Convert.ToBase64String(toEncodeAsBytes);
        return returnValue;
    }
}

To run it I call

BuildDeployPortal.ConnectInfo co = new BuildDeployPortal.ConnectInfo("127.0.0.1:10080", "user", "pass");

//get file
BuildDeployPortal.GetFile(co, "LocalAppData", "2017-01-25-09-04-21_TestFile.csv", "app_full_package_name", "\\LocalState");

//upload file
BuildDeployPortal.UploadFile(co, "LocalAppData", "app_full_package_name", "\\LocalState",
            "C:\\Users\\myuser\\Desktop\\info.json");

The GetFile method works fine and gives me the file I want but the UploadFile method gives me the following response : StatusCode : 429, ReasonPhrase : 'Too Many Requests'. Same result if I only run one of its during execution.

I don't really understand why I have this error, I just send 1 request (or I'm missing something).

For information, REST API definition can be found there.

Thanks

2 Answers

Debugging the issue

The problem is that Chrome 62 sends an empty content-type header, while Chrome 61 did send the content-type header correctly as multipart/form-data.

The cause is that Microsoft's own code for the FileExplorer.js calls jQuery.ajax(...) with contentType: "", so the browser was left to interpret an empty string.

Excerpt from FileExplorer.js

$.ajax({url:"/api/filesystem/apps/file?"+$.param(r),cache:!1,contentType:"",processData:!1,data:n,type:"post"}).done(...)

The REST API definition you linked from Microsoft fails to specify any of the Request Headers, and fails to specify the Request Body, so there is no spec against which to determine the correct set of Headers and Body.

FWIW, Microsoft's own C# Device Portal REST API wrapper always sends the content-type header:

string contentType = string.Format("multipart/form-data; boundary={0}", boundaryString);

The Solution

I have created a bookmarklet that will upload the file correctly when on the FileExplorer page in Device Portal.

Expanded Version

(function(jQuery) {
var pathLinkData = jQuery(".pathLink:last-child").data(),
    path = pathLinkData.path,
    packagename = pathLinkData.packagename,
    knownfolderid = pathLinkData.knownfolderid,
    url = '/api/filesystem/apps/file?knownfolderid=' + knownfolderid + '&packagefullname=' + packagename + '&path=%5C%5C' + path,
    file_data = jQuery('#fileToUpload')[0].files[0],
    form_data = new FormData();

  form_data.append('file', file_data, file_data.name);

  jQuery.ajax({
    url: url,
    dataType: 'text',
    cache: false,
    contentType: false,
    processData: false,
    data: form_data,
    type: 'POST',
    error: function(xhr, textStatus, error) { console.error(error) },
    success: function(res){
      alert('uploaded');
      console.log(res);
    }
  });
})(jQuery);

Compact copyable bookmarklet version (also works in inspector/debugger console)

javascript:(function(jQuery){var pathLinkData=jQuery('.pathLink:last-child').data(),path=pathLinkData.path,packagename=pathLinkData.packagename,knownfolderid=pathLinkData.knownfolderid,url='/api/filesystem/apps/file?knownfolderid='+knownfolderid+'&packagefullname='+packagename+'&path=%5C%5C'+path,file_data=jQuery('#fileToUpload')[0].files[0],form_data=new FormData();form_data.append('file',file_data,file_data.name);jQuery.ajax({url:url,dataType:'text',cache:false,contentType:false,processData:false,data:form_data,type:'POST',error:function(xhr,textStatus,error){console.error(error)},success:function(res){alert("uploaded");console.log(res);}});})(jQuery);

(I have been developing for Hololens for over a year and discovered this as each of my computers separately upgraded to Chrome 62.)

I don't really answer to my question (why this error 429 appears) but I give an alternative solution to upload file.

I downloaded this solution to get the WindowsDevicePortalWrapper library. Then I call the following code to upload file :

IDevicePortalConnection connection = new DefaultDevicePortalConnection("http://127.0.0.1:10080", "user", "password");
DevicePortal dp = new DevicePortal(connection);
//write file
await dp.UploadFileAsync("LocalAppData", "C:\\Users\\ng1dd32\\Desktop\\info.json", "\\LocalState", "Lexington_1.0.0.0_x86__ph1m9x8skttmg");
Related