Jump to content

Api Endpoint - File Upload


Recommended Posts

Anthony Musgrove
Posted

Hi all - are there any plugins out there that demonstrate creating an Api Endpoint to upload a file?

 

I have been testing this for hours, and can't seem to get it.

 

I created an Api Endpoint, which obviously works.  It exists in my swaggerUI.

 

However, I created a simple HTML document with a form to post a file for testing;

<!DOCTYPE html>
<html>
<head><title></title></head>
<body>

<form name="frmUploadPackage" method="post" action="http://192.168.1.50:8096/emby/ScripterX/Packages/UploadPackage" enctype="multipart/form-data">

	Choose package file:

	<input type="file" name="PkgFile" id="PkgFile">

	<input type="submit" name="btnSubmit" value="Upload!">

</form>

</body>
</html>

And when submitting, I basically use IRequiresRequest, and read the Request object:

 

Request.Files is empty:

 

 

2020-05-24 00:49:34.654 Info Emby ScripterX: Total files on POST DATA = 0, content length = 455, content type=multipart/form-data; boundary=----WebKitFormBoundaryyL6Z6QQQCPN5zoDp

    [Route("/ScripterX/Packages/UploadPackage", "POST", Summary = "Upload a package to ScripterX Packages")]

        public void Post(PackageFile pFile)
        {
            Plugin._iLogger.Info("Total files on POST DATA = " + Request.Files.Length + ", content length = " + Request.ContentLength + ", content type=" + Request.ContentType);
        }

What am I missing here! thank you.

@@chef

Posted (edited)

Yes, please see my Alexa endpoint plugin in GitHub

 

Chefbennyj1

 

If you look under "Api/BackgroundImageService"

 

You will find the answer.

 

You must use the IReturn<object>

 

And use the IHttpResultFactory.

 

Specifically see the "emptyPng" image request

Edited by chef
  • Like 1
Anthony Musgrove
Posted

Thank you so much mate I really appreciate your help as always :)

Anthony Musgrove
Posted

Hey mate, I checked out your code, it looks awesome for serving files out.   I'm trying to get files in, like uploading a zip file to the emby server API rather than serving them out :)

 

It's kinda odd that request files is empty even though I am posting the file to it.  

Anthony Musgrove
Posted

This part:

 

        //
        // Summary:
        //     Access to the multi-part/formdata files posted on this request
        IHttpFile[] Files { get; }
Anthony Musgrove
Posted

And I've checked the complete request coming from my browser test, and it seems very valid;

POST /emby/ScripterX/Packages/UploadPackage HTTP/1.1
Host: 192.168.1.50:5999
Connection: keep-alive
Content-Length: 455
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCWKRTMWE0OAxwgGf
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

------WebKitFormBoundaryCWKRTMWE0OAxwgGf
Content-Disposition: form-data; name="PkgFile"; filename="testpackage.zip"
Content-Type: application/x-zip-compressed

PK¸·P¿r(Utest123.txtthis is a test, 1 2 3.PK¸·P¿r(U test123.txtPK9?
------WebKitFormBoundaryCWKRTMWE0OAxwgGf
Content-Disposition: form-data; name="btnSubmit"

Upload!
------WebKitFormBoundaryCWKRTMWE0OAxwgGf--

Anthony Musgrove
Posted

Pulling my hair out here, hehe.

 

I've just tried this too.

    // DataType = "Dictionary<string, string>", 
    [Route("/ScripterX/Packages/UploadPackage", Verbs = "POST", Summary = "Upload a package to ScripterX Packages")]
    public class PackageFile : IReturnVoid
    {
        [ApiMember(Name = "TestFile", Description = "Package File", IsRequired = true, DataType = "IHttpFile", ParameterType = "body", Verb = "POST")]
        public IHttpFile TestFile { get; set; }

        
    }

and-

        public void Post(PackageFile pFile)
        {
            Plugin._iLogger.Info("Api In > File = " + pFile.TestFile.FileName);
        }

and pFile.TestFile is null.

 

I'm posting via:

<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<form name="frmUploadPackage" method="POST" action="http://192.168.1.50:8096/emby/ScripterX/Packages/UploadPackage" enctype="multipart/form-data">

	Choose package file:

	<input type="file" name="TestFile" id="TestFile">

	<input type="submit" name="btnSubmit" value="Upload!">

</form>

</body>
</html>

It's an odd issue, unless Uploading Files are disabled in the API code?

Anthony Musgrove
Posted

2020-05-24 17:01:33.477 Info HttpServer: HTTP Response 204 to 192.168.1.118. Time: 2ms. http://192.168.1.50:8096/emby/ScripterX/Packages/UploadPackage
2020-05-24 17:01:33.692 Info HttpServer: HTTP POST http://192.168.1.50:8096/emby/ScripterX/Packages/UploadPackage. UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
2020-05-24 17:01:33.694 Info Emby ScripterX: Api File In > File Count = 0
Anthony Musgrove
Posted

So if I take out the enctype="multipart/form-data", and accept PkgFile as a string value, it gets the file's name:

 

2020-05-24 17:08:02.414 Info Emby ScripterX: Package File Not Null, it is = testpackage.zip

 

Odd.

Anthony Musgrove
Posted

And obviously when I remove the enctype on the HTML form, it just sends the data through as form data, rather than uploading the file to the endpoint.  So it has to do with how the API code identifies the data being uploaded when it is a multipart/form-data request.

POST /emby/ScripterX/Packages/UploadPackage HTTP/1.1
Host: 192.168.1.50:5999
Connection: keep-alive
Content-Length: 43
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

PkgFile=testpackage.zip&btnSubmit=Upload%21
Anthony Musgrove
Posted

So I am thinking, instead of using ParameterType = "form", it probably needs to be ParameterType = "body" (because mutlipart/form-data is sent in the body of the request, not in form format).

 

And some further investigation needs to go ahead.

Anthony Musgrove
Posted

So essentially all this comes down to; how to match an ApiMember to the following data from the request body;

------WebKitFormBoundary8GU3x7O6xYtUJIMG
Content-Disposition: form-data; name="PkgFiles[]"; filename="testpackage.zip"
Content-Type: application/x-zip-compressed

PK¸·P¿r(Utest123.txtthis is a test, 1 2 3.PK¸·P¿r(U test123.txtPK9?

I have tried a multitude of options, the latest being:

        [ApiMember(Name = "PkgFiles", Description = "Package File", IsRequired = true, DataType = "Dictionary<string,object>", ParameterType = "form", Verb = "Post")]
        public Dictionary<string,object> PkgFiles { get; set; }

However, PkgFiles is always null.  It's not populating the variable.   

Anthony Musgrove
Posted

Request Headers exist ...

2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Host, value= 192.168.1.50:8096
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Connection, value= keep-alive
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Content-Length, value= 319921
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Cache-Control, value= max-age=0
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Upgrade-Insecure-Requests, value= 1
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Origin, value= null
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Content-Type, value= multipart/form-data; boundary=----WebKitFormBoundaryeUgXjRSBE0HNc6mN
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: User-Agent, value= Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Accept, value= text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Accept-Encoding, value= gzip, deflate
2020-05-24 18:01:04.150 Info Emby ScripterX: A HEADER: Accept-Language, value= en-US,en;q=0.9
Anthony Musgrove
Posted

and I'm assuming to access the 'content' part of this request, would be:

 

        //
        // Summary:
        //     Access to the multi-part/formdata files posted on this request
        IHttpFile[] Files { get; }
 
However it is empty.    It's like the IRequest isn't populating this information at all
Anthony Musgrove
Posted

Even the InputStream.  It's readable, (IRequiresRequestStream).  When I read the contents, its empty.

2020-05-24 18:31:09.968 Info Emby ScripterX: REQUEST STREAM REQSTR IS POPULATED!, Readable? = True
2020-05-24 18:31:09.968 Info Emby ScripterX: Stream Data = ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������
Anthony Musgrove
Posted (edited)

I think, still, that IRequest isn't populating .Files properly.  It states that it should contain any Files submitted using multipart/form-data but it isn't being populated.  @@Luke are you able to confirm that for me please?  Because I think IRequest may be broken for posting files (multipart/form-data).

 

(Without being able to see core source code for where the IRequest is fulfilled, I can't really debug or troubleshoot it) :)

 

Thank you so much.

Edited by Anthony.Musgrove
Anthony Musgrove
Posted (edited)

So my latest is this (and it still isn't working):

    [Route("/ScripterXUpload", "Post", Summary = "Test Upload Function 2")]
    public class XUpload : IReturnVoid
    {
        [ApiMember(Name ="UploadFile", DataType = "IHttpFile", ParameterType = "body", AllowMultiple = true)]
        public IHttpFile UploadFile { get; set; }
    }

        public void Post(XUpload request)
        {

            if(request.UploadFile == null)
            {
                Plugin._iLogger.Info("UPLOAD FILE IS NULL :(");
            }

            Plugin._iLogger.Info("Upload File !!");
        }

<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<form name="frmUploadFile" method="POST" action="http://192.168.1.50:8096/emby/ScripterXUpload" enctype="multipart/form-data">

	Choose package file:

	<input type="file" name="UploadFile">

	<input type="submit" name="btnSubmit" value="Upload!">

</form>

</body>
</html>

and output:

2020-05-25 01:09:20.448 Info HttpServer: HTTP POST http://192.168.1.50:8096/emby/ScripterXUpload. UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
2020-05-25 01:09:20.498 Info Emby ScripterX: UPLOAD FILE IS NULL 
2020-05-25 01:09:20.498 Info Emby ScripterX: Upload File !!
2020-05-25 01:09:20.498 Info HttpServer: HTTP Response 204 to 192.168.1.118. Time: 50ms. http://192.168.1.50:8096/emby/ScripterXUpload

And I notice in SwaggerUI, the body content-type for some reason is application/json.  I have a funny feeling that is also not correct :/

 

5eca8ee2ba882_swaggerupload.png

Edited by Anthony.Musgrove
Anthony Musgrove
Posted

Also tried;

        //Content-Disposition: form-data; name="PkgFile"; filename="testpackage.zip"
        //Content-Type: application/x-zip-compressed

        [ApiMember(Name="name", DataType ="string", ParameterType = "body")]
        public string name { get; set; }



Trying to get the name component from the ?body? of the request, that is null too
Anthony Musgrove
Posted

A bit more output:

2020-05-25 01:58:28.971 Info Emby ScripterX: PathInfo: /emby/ScripterXUpload
2020-05-25 01:58:28.971 Info Emby ScripterX: Items Count: 0
2020-05-25 01:58:28.971 Info Emby ScripterX: QueryString: 
2020-05-25 01:58:28.971 Info Emby ScripterX: RawURL: /emby/ScripterXUpload
2020-05-25 01:58:28.971 Info Emby ScripterX: Content Type: multipart/form-data; boundary=----WebKitFormBoundaryUpuDHxfQBCsxWOKE
2020-05-25 01:58:28.971 Info Emby ScripterX: Files Length: 0
2020-05-25 01:58:28.971 Info Emby ScripterX: Http Method: POST
2020-05-25 01:58:28.971 Info Emby ScripterX: Verb: POST

achieved via:

            Plugin._iLogger.Info("PathInfo: " + Request.PathInfo);
            Plugin._iLogger.Info("Items Count: " + Request.Items.Count);
            Plugin._iLogger.Info("QueryString: " + Request.QueryString);
            Plugin._iLogger.Info("RawURL: " + Request.RawUrl);
            Plugin._iLogger.Info("Content Type: " + Request.ContentType);
            Plugin._iLogger.Info("Files Length: " + Request.Files.Length);
            Plugin._iLogger.Info("Http Method: " + Request.HttpMethod);
            Plugin._iLogger.Info("Verb: " + Request.Verb);
Anthony Musgrove
Posted

I also noticed a typo in the summary for Request.Files;

        //
        // Summary:
        //     Access to the multi-part/formdata files posted on this request
        IHttpFile[] Files { get; }

which shows multi-part/formdata where it should be multipart/form-data 

 

I wonder if the processing for it is looking for the wrong content type because of a typo?

 

:) Thanks guys, sorry to be a PITA.

PenkethBoy
Posted

Anthony

 

to get Luke's attention - as this is a internal ApI issue is post in the Developer API sub forum

 

he tends to respond to questions like this there

 

and you can get some sleep!

:)

  • Like 1
Anthony Musgrove
Posted

Thank you so much mate.  Sorry @@Luke

Posted (edited)

@@Anthony.Musgrove

Sorry I missed your replies. 

 

Does you're endpoint have:

 [Route("/YOUR_POST_ENDPOINT", "POST", Summary = "This End Point will accept the POST request")]
    public class MyPostRequest : IRequiresRequestStream
    {
        public Stream RequestStream  { get; set; } //This is the image stream you can do something with
        
    }
Edited by chef
Anthony Musgrove
Posted

 

@@Anthony.Musgrove

Sorry I missed your replies. 

 

Does you're endpoint have:

 [Route("/YOUR_POST_ENDPOINT", "POST", Summary = "This End Point will accept the POST request")]
    public class MyPostRequest : IRequiresRequestStream
    {
        public Stream RequestStream  { get; set; } //This is the image stream you can do something with
        
    }

 

 

Thats okay mate.  I tried to use RequestStream -- and whenever I read out of it, all bytes are always null?  

Anthony Musgrove
Posted

It's coming together nicely now *grin*

 

5ecb4c436d84b_ScripterXPackageInstallerI

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...