artilleryio / artillery

The complete load testing platform. Everything you need for production-grade load tests. Serverless & distributed. Load test with Playwright. Load test HTTP APIs, GraphQL, WebSocket, and more. Use any Node.js module.
https://www.artillery.io
Mozilla Public License 2.0
8.04k stars 511 forks source link

TypeError: Method get TypedArray.prototype.length called on incompatible receiver [object Object] or indefinite hang when trying to modify requestParams.body #489

Open brianjenkins94 opened 6 years ago

brianjenkins94 commented 6 years ago

I'm trying to upload a file via a PUT request with no luck. As far as I can tell this is the way that it is being done in artillery-plugin-http-file-uploads. I suspect the TypeError is coming from when either artillery or request is trying to calculate the length of the request body. Is this a known issue? Has anyone been able to successfully upload a file using artillery?

artillery-plugin-http-file-uploads/index.js:

traverse(requestParams).forEach(function(o) {
    if (o && o.fromFile) {
        const filename = path.resolve(
            basePath,
            renderVariables(o.fromFile, userContext.vars));
        debug(`Updating`, o, `with`, filename);
        const stream = fs.createReadStream(filename);
        // in-place update:
        this.update(stream);
    }
});

app.ts:

import { config } from "./config";

import * as artillery from "./node_modules/artillery/lib/commands/run";
import * as fs from "fs";
import * as temp from "tmp";

let script = {
    "config": {
        "target": "http://" + config.get("pasUrl") + ":" + config.get("pasPort"),
        "phases": [
            {
                "duration": 1,
                "arrivalCount": 1
            }
        ],
        "processor": "../scenarios/scenario1.js",
        "mode": "uniform"
    },
    "scenarios": [
        {
            "flow": [
                {
                    "post": {
                        "url": "/ViewingSession",
                        "headers": {
                            "Content-Type": "application/json"
                        },
                        "beforeRequest": "setRequestBodyForViewingSessionPost",
                        "capture": {
                            "json": "$.viewingSessionId",
                            "as": "viewingSessionId"
                        }
                    }
                },
                {
                    "put": {
                        "url": "/ViewingSession/u{{ viewingSessionId }}/SourceFile",
                        "headers": {
                            "Accusoft-Secret": "mysecretkey",
                            "Content-Type": "application/octet-stream"
                        },
                        "beforeRequest": "setRequestBodyForViewingSessionPut",
                    }
                }
            ]
        }
    ]
};

let tempfile = temp.fileSync({ "dir": "temp" });
fs.writeFileSync(tempfile.name, JSON.stringify(script, undefined, 4), { "flag": "w" });

// artillery(filename, options)
artillery(tempfile.name, {});

scenario1.ts:

export function setRequestBodyForViewingSessionPut(requestParams, context, ee, next) {
    requestParams.body = fs.createReadStream(path.join(__dirname, "..", "documents", "creating-wireframes.pdf"));

    return next(); // Artillery hangs indefinetly
}

export function setRequestBodyForViewingSessionPut2(requestParams, context, ee, next) {
    let readStream = fs.createReadStream(path.join(__dirname, "..", "documents", "creating-wireframes.pdf"));

    let bufferArray = [];

    readStream.on("data", function(data) {
        bufferArray.push(data);
    });

    readStream.on("close", function() {
        requestParams.body = Buffer.concat(bufferArray);

        return next(); // TypeError: Method get TypedArray.prototype.length called on incompatible receiver [object Object]
    });
}
brianjenkins94 commented 6 years ago
requestParams.body = fs.readFileSync(path.join(__dirname, "..", "documents", "creating-wireframes.pdf"));

also yields the error:

TypeError: Method get TypedArray.prototype.length called on incompatible receiver [object Object]
hassy commented 6 years ago

@brianjenkins94 file uploads are known to work well. There's a DIY solution here: https://github.com/shoreditch-ops/artillery/issues/320#issuecomment-329448748. If you need a solution which just works out of the box, and also works when running distributed tests from AWS, check out Artillery Pro.

brianjenkins94 commented 6 years ago

Looks like #320 is sending Content-Type: multipart/form-data. I'm looking to send Content-Type: application/octet-stream.

hassy commented 6 years ago

Which version of Artillery are you running?

brianjenkins94 commented 6 years ago

"artillery": "^1.6.0-19"

mnebuerquo commented 1 year ago

Looks like artillery is using 'got' by Sindre Sorhus as its request library.

Artillery doesn't expose that to the hook so we can't change how we upload a file. Ideally we could stream it, but that would require piping a stream to the request instance here: https://github.com/artilleryio/artillery/blob/main/packages/core/lib/engine_http.js#L668

Look at the docs for got: https://github.com/sindresorhus/got/blob/HEAD/documentation/3-streams.md

Specifically this:

// For POST, PUT, PATCH, and DELETE methods, `got.stream` returns a `stream.Writable`.
// This example POSTs the contents of a file to a URL.
await streamPipeline(
    fs.createReadStream('index.html'),
    got.stream.post('https://sindresorhus.com'),
    new stream.PassThrough()
);

So we can't stream an upload from artillery without changes to support passing a stream and piping it to the got request.

Since this issue has been idle for a long time, I'm guessing this isn't an in-demand feature.