Posting to the API calls

@foosel

I found this interesting when writing an interface to the OctoPrint API and it seems a bit unexpected to me. I honestly spent a long time troubleshooting this but finally found a good work-around. I don't know enough about the receiving code in OctoPrint to find out where these calls are landing.

I'll first introduce the code then describe what I was seeing. The code is in nodejs, btw.

/**
 * Send the specified command to the indicated printer.
 *
 * @param {Number} - The printer ordinal from the global array
 * @param {String} - The API endpoint
 * @param {object} - The JSON printer command to send
 */
function sendToPrinter(iPrinterOrdinal, endPoint, objPrinterCommand, callback) {
    var response =      "";
    var objPrinter =    globalArrayPrinterProfiles[iPrinterOrdinal];
    var apiKey =        objPrinter.apiKey;
    var url =           "http://" + objPrinter.hostName + endPoint;

    //console.log(objPrinterCommand);
    $$.ajaxSetup({ headers: { 'X-Api-Key': apiKey }});
    $$.ajax({
        url:         url,
        method:      'POST',
        contentType: 'application/json',
        data:        JSON.stringify(objPrinterCommand),
        success: function(response){
            if (typeof callback === "function") {
                callback('Status 204');
            }
        },
        error: function(xhr, status){
            if (typeof callback === "function") {
                callback(JSON.stringify(status));
            }
        }
    });
};

A typical call to this function then would be:

            var objPrinterCommand = { "command": "home", "axes": ["x","y","z"] };
            sendToPrinter(printerOrdinal, endPoint, objPrinterCommand, function(data){
                //console.log('Home command sent to printer: ' + data);
            });

The work-around was to call JSON.stringify() to convert the command from json to a string before sending it on to your API. If you don't do this, the printer always returns a 400 status code.

The docs indicate that it wants application/json as the Content-Type and that is being set of course. The API just doesn't like the data if it's not in a string format in the BODY. I've tried using a variety of Node modules for the http call but it seems to be the same, regardless.

I note this in case others are trying to exercise the API to control the printer.

It would be interesting to know what your nodejs code actually pushes over the connection in the request body if you don't run it through JSON.stringfy first. It sounds like what ever it serializes to isn't JSON (so your request header would say "hi, I'm in JSON" but your request body would be something else entirely).

Are you saying that this line

data: JSON.stringify(objPrinterCommand),

won't work like so:

data: objPrinterCommand,

Isn't that expected since objPrinterCommand:

var objPrinterCommand = { "command": "home", "axes": ["x","y","z"] };

isn't json, or am I missing something?

@FormerLurker The version with JSON.parse(whatever) also didn't work, by the way.

@foosel Yeah... I would have liked to have seen that, too. I didn't know OctoPrint well enough at that point to have known how to log what was received.

It's this code, btw. And no, it's not the official Robo 3D phone app; it's my own spin on that.

So, JSON.stringify(jsObject) will convert a javascript object to a json string, and JSON.parse(jsonString) will convert the json string to a javascript object. Parse would fail in this case since objPrinterCommand is not a string.

You could replace objPrinterCommand with this:

strPrinterCommand = "{ "command": "home", "axes": ["x","y","z"] }";

and then:

data: strPrinterCommand,

which would work without stringify, but that is probably not helpful, but it's a lot of fun to discuss!

The JSON.parse() didn't fail earlier (and yes, I made it a string first). What I'm saying is that when I did it the way you described it seemed to fail with a 400 at the printer API once sent. Hope that make sense.

Sorry @OutsourcedGuru, but I still don't understand. The code I described did not use JSON.parse, but rather JSON.stringify instead.

In any case, having to serialize the object representation into JSON first is pretty common behaviour. jQuery for example defaults to serializing provided objects into form data, even if you set a JSON header. Funnily enough it will automatically deserialize JSON though, if you tell it to expect this via the dataType property.

To remove this constant boiler plate code, OctoPrint's own JS client lib contains helper methods for this kind of stuff.

1 Like

I guess next time I ought to use your client library rather than trying to roll my own. Thanks.