Uploading file to OctoPi through the API using javascript

What is the problem?
Uploading a file through the API using javascript

What did you already try to solve it?
I tried the following JS code

            $.ajax({
                url : "http://" + host + "/api/files/sdcard",
                type : 'POST',
                data : formData,
                processData: false,
                contentType: "multipart/form-data",
                beforeSend: function(request) {
                    request.setRequestHeader("X-Api-Key", key);
                },
                success : function(data) {
                    console.log(data);
                    alert(data);
                },
                error: function(data) {
                    console.log(data);
                    alert("Error. Check console");
                },
                failure: function(data) {
                    console.log(data);
                    alert("Error. Check console");
                }
            });

I also tried uploading the file using postman as a testing-environment, I tried all sorts of different combinations;
images on imgur, as "new users" only can upload one image

In Postman, when having the "Content-Type" set to "multipart/form-data", it "shuts down" my request immediately (saying "Could not get any response"), but when having it set to "application/x-www-form-urlencoded" it seems like it actually sends the file, as it takes some time before giving me the "No file to upload and no folder to create" error-message.

Additional information about your setup (OctoPrint version, OctoPi version, printer, firmware, octoprint.log, serial.log or output on terminal tab, ...)
octoprint.log (6.5 KB)

Any help appriciated :slight_smile: I followed the "File operations" docs instructions quite carefully, and I can't really see what I've done wrong.

1 Like

You might go to school on the following Terminal version which apparently can upload to OctoPrint using the curl command.

curl -k -H "X-Api-Key: MYOCTOPRINTAPIKEY" -F "select=false" -F "print=false" -F "file=@/some/full/path/my.gcode" "http://octopi.local/api/files/local"

I have looked at the CURL examples, as stated (and shown) in my post, I think I'm doing just that. I can't see how it helps me solve my JS or Postman issue, unless I'm missing something?

I think I would first try to upload to local rather than sdcard to see if that works. If it does, then move on to attempting to upload to SD.

You might want to try multer as a node_module (assuming that you're running Node).

Cribbing from here:

var multer  =   require('multer');
var storage =   multer.diskStorage({
                destination: function (req, file, callback) {
                  callback(null, './uploads');
                },
                filename: function(req, file, callback) {
                  callback(null, file.fieldname + '-' + Date.now());
                }
});

var upload = multer({ storage : storage}).single('userPhoto');

upload(req,res,function(err) {
  if(err) {return res.end("Error uploading file.");}
  console.log("File is uploaded");
});

And a stackoverflow post.

I'm using plain javascript - not Node. If it's the only way, I'll "concede", but unless it is a must, I would rather just use javascript - which, on paper, supports all that the API is asking for.

The "upload" api call is to be posted, right? It's a post request? If it's possible to provide the API with a URL that it can download the file from, instead of posting the file to it, that'd be fine for me too. I'm just looking for a way to do this on my website :slight_smile:

It's all client-side, so I can't use CURL from backend - I only got client-side to work with. I'll try uploading to "local".

Yes, all uploads are POST-related.

Foosel has created an entire JavaScript Client Library, not that I've ever used it. Here is the file-related section and there's an upload() function.

From her own example, this appears to be a POST'd session. Note the multipart-MIME decorations throughout. You might want to just toggle those two booleans to FALSE, though.

POST /api/files/sdcard HTTP/1.1
Host: example.com
X-Api-Key: abcdef...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDeC2E3iWbTv1PwMC

------WebKitFormBoundaryDeC2E3iWbTv1PwMC
Content-Disposition: form-data; name="file"; filename="whistle_v2.gcode"
Content-Type: application/octet-stream

M109 T0 S220.000000
T0
G21
G90
...
------WebKitFormBoundaryDeC2E3iWbTv1PwMC
Content-Disposition: form-data; name="select"

true
------WebKitFormBoundaryDeC2E3iWbTv1PwMC
Content-Disposition: form-data; name="print"

true
------WebKitFormBoundaryDeC2E3iWbTv1PwMC--

From your example it's sadly not possible to see what your formData variable actually contains, and my money would be on there lying the problem.

If we just don't look at the JS for a second and try to solve the Postman-issue, I'm sure it'll help me understand what I'm doing wrong in javascript (and Postman can generate the request code for just about any language)

From what I believe to be the correct post-format in Postman, it'd be this code in cURL;

curl -X POST \
  http://192.168.0.102/api/files/local \
  -H 'cache-control: no-cache' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -H 'postman-token: f5c1fb75-68ec-4d89-35da-d9c9617b8809' \
  -H 'x-api-key: 3504D4396F8E4DD6AB91C78CAB516DBF' \
  -F file=@test_Test.gcode \
  -F select=true \
  -F print=true

And this would be the HTTP

POST /api/files/local HTTP/1.1
Host: 192.168.0.102
X-Api-Key: 3504D4396F8E4DD6AB91C78CAB516DBF
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 9bda3364-8ea1-94bb-9d60-75b90e4b6ceb

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test_Test.gcode"
Content-Type: 


------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="select"

true
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="print"

true
------WebKitFormBoundary7MA4YWxkTrZu0gW--

The only thing I can see missing is the "Content-Type: application/octet-stream" in the HTTP request, but otherwise it looks fine to me?

Got it. This is the correct JS code for uploading, starting and printing a file;

var form = new FormData();
form.append("file", $(file_input)[0].files[0]);
form.append("select", "true");
form.append("print", "true");

var settings = {
  "async": true,
  "crossDomain": true,
  "url": host + "/api/files/local",
  "method": "POST",
  "headers": {
    "x-api-key": api_key,
    "cache-control": "no-cache",
  },
  "processData": false,
  "contentType": false,
  "mimeType": "multipart/form-data",
  "data": form
}

$.ajax(settings).done(function (response) {
  console.log(response);
});

Worked for me

2 Likes

Was it the crossDomain?

I'm glad you found your solution @AlbertMN for the file uploads. I hope this and the plugin tweaks we did gets your school printer farm working as you were hoping to. Just out of curiosity, what was the server side you were integrating the API with for managing the printers?

No, if this was the case it would've told me. The postman request still doesn't work, so I'm suspecting there's a problem with it - it might be due to the fact that Im using the old chrome app - it didn't seem to actually send the file, just the file name. I generated the code from Postman and replaced the name with the actual file, and it worked.

@jneilliii the server side is for user tracking :slight_smile: before a student uses the printer, they have to enter their name, grade/class and optionally an email address where they can be mailed when the print finishes, gets cancelled or fails. Furthermore we use it for filament tracking - we give each roll an ID, how much plastic it contains and then updates how much is left. This way we can stop the user automatically if the printer doesn't have enough filament to finish their print. To change filament, you simply press a button, it heats up the extruder, releases the PLA and instructs them how to put in a new one :slight_smile:

So all together a system that is as idiot-proof as possible!

1 Like

I do love Postman and I teach it to my students (I work at a software development bootcamp). There are times, though, when it (silently) makes things too easy. It might work in Postman and not work in curl and then I'm left to wonder what secret help it just did to make this work. Unfortunately, I can't then "take that into JavaScript" for the ultimate solution.

I use Postman as a shortcut for requests - if it works it ain't stupid :wink: Although Postman seem to have been the problem this time.

Oh and since you do JavaScript, you might try my octo-client sometime. But of course, it's Node-based.

var OctoPrint = require('octo-client');

OctoPrint.printerState(function(ptr){
  if (ptr.state.flags.operational) {
    var targetHotend1 = ptr.temperature.tool0.target;
    console.log(targetHotend1);
  }
});

As far i can see octo-client can only handle one printer ?

because only one printer is/can be hard coded to:

node_modules/octo-client/config.js

That's the fun of open source.

1 Like

@OutsourcedGuru, what's the benefit of your client over the stock one? I'm not really familiar with node.js and it's benefits so maybe that's the main difference. There is mention on the stock js client linked above about instantiating multiple instances, which would serve as a good example I think.

@jneilliii Here's an example of some small CLI programs which I've written myself from my own octo-client:

octo-temp

#!/usr/bin/env node

var OctoPrint = require('octo-client');

OctoPrint.printerState(function(response){
  if (response) {
    if (response.temperature) {
      if (response.temperature.tool0) {
        var t = response.temperature.tool0;
        console.log('The first hotend is set to ' +
          t.target.toString() +
          'C and is currently ' +
          t.actual.toString() + 'C.')
      };
    };
  };
});

Creating that in Node, adding a symlink so that it's in your path and you could then just run:

$ octo-temps
The first hotend is set to 190C and is currently 182.5C.

Similarly, you could pause a print with:

  OctoPrint.jobPause(function(response){
    console.log(response);
  });

You could both query the printer's state and then do different things as a result. I've created web services which do this on the backend since Node's good for that. I've even added Node to my OctoPrint installation so that I can run additional services there. I've got one listening to port 3000 which acts as a simple proxy; it's just easier than creating a plugin (at least if you're me).

I've got some which query on behalf of the Conky TFT interface to gather logistics.

Node.js is based upon the Chrome V8 JavaScript engine. It's got a superb asynchronous queuing mechanism which scales up to mondo levels. It's hardcore stuff and it is well-positioned to replace Java, .NET and even C/C++ (eventually).

It comes with the npm (Node Package Manager) and a huge collection of node modules in the open space area.

I'm trying to upload file to a specific folder using the curl command, how exactly do I specify the folders location when it's next under the local upload location?