Node.js, против Electron :: Запустить внешнюю команду и показать стандартный вывод на интерфейсе - PullRequest
2 голосов
/ 08 января 2020

Я новичок в node.js и Electron и пытаюсь запустить внешнюю команду в Electron и показать результат с помощью HTML.

Я сделал версию HTTP-сервер-клиент с node.js, который отлично работает Но не удалось заставить его работать с Электроном, даже после следующих многочисленных ответов, таких как

Ни один из них не работал для меня в Electron.

Node.js версия работает.

Я показываю свой рабочий node.js код следующим образом. Этот код после запуска node index.js и открытия localhost:8888/start в браузере покажет вывод ls -al на веб-странице:


// index.js

var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");

var handle = {};
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;

server.start(router.route, handle);

Сервер HTTP:

// server.js

var http = require("http");
var url = require("url");

function start(route, handle) {
    http.createServer(function(request, response) {
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");

        route(handle, pathname, response);
}).listen(8888);

    console.log("Server has started.");
}

exports.start = start;

Маршрутизатор, который обрабатывает два запроса, т.е. start/upload, по-разному:

//router.js

function route(handle, pathname, response) {
    console.log("About to route a request for " + pathname);
    if (typeof handle[pathname] == 'function') {
        handle[pathname](response);
    } else {
        console.log("No request handler found for " + pathname);

        // send an HTTP status and content-type in the HTTP response *header*
        // back to the browser that requested your server).
        response.writeHead(404, {"Content-Type": "text/plain"});
        // send text in the HTTP response *body*.
        response.write("404 Not found");
        // finish the response.
        response.end();
    }
}

exports.route=route;

Фактические обработчики запросов:


// requestHandlers.js

var exec = require("child_process").exec;

function start(response) {
    console.log("Request handler 'start' was called.");
    var content = "empty";

    exec("ls -al", 
        {timeout: 10000, maxBuffer: 20000*1024},
        function(error, stdout, stderr) {
            // send an HTTP status 200 and content-type in the HTTP response *header*
            // back to the browser that requested your server).
            response.writeHead(200, {"Content-Type": "text/plain"});

            // send text in the HTTP response *body*.
            response.write(stdout);

            // finish the response.
            response.end();
        });
}

function upload(response) {
    console.log("Request handler 'upload' was called.");
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("You've uploaded data");
    response.end();
}

exports.start = start;
exports.upload = upload;

Приведенный выше код работал для меня в Safari.

Сбой версии Electron

Теперь я хотел сделать то же самое с Electron: введите команду в поле ввода, запустите ее через кнопку отправки и отобразите результат на той же странице ниже элементы управления. Вот мой основной процесс:


// main.js

const {app, BrowserWindow} = require('electron');

let mainWindow = null;

app.on('ready', () => {
    console.log('Hello from Electron');
    mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    });

    mainWindow.webContents.openDevTools()

    mainWindow.webContents.loadFile('./app/index.html');

    // mainWindow events, within app lifecycle
    mainWindow.webContents.on('did-fail-load', function() {
        console.log("Failed to load index.html");
    })

})

Теперь процесс рендеринга:


// renderer.js

const { shell } = require('electron');

const parser = new DOMParser();

const resultSection = document.querySelector('.results');
const errorMessage = document.querySelector('.error-message');
const newCmdForm = document.querySelector('.new-cmd-form');
const newCmd = document.querySelector('.new-external-cmd');
const newCmdSubmit = document.querySelector('.new-cmd-run');
const clearStorageButton = document.querySelector('.clear-results');

newLinkForm.addEventListener('submit', (event) => {
    const cmd = newCmd.value;
    processCmd(cmd);
});

const processCmd = (cmd) => {
    var exec = require('child_process').exec;

    exec("ls -al", {timeout: 10000, maxBuffer: 20000*1024},
        function(error, stdout, stderr) {
            var out = stdout.toString();
            var result = 
                '<div class="text"' +
                `<h3>${out}</h3>` +
                '</div>';
            resultSection.innerHTML = result;
            console.log(result)
        });
}

const renderResults = () => {
    resultSection.innerHTML = '';
};

renderResults();


Вот страница:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="
        default-src 'self';
        script-src 'self' 'unsafe-inline';
        connect-src *">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Cmdlet</title>
    <link rel="stylesheet" href="style.css" type="text/css">
</head>

<h1>Cmdlet</h1>
<div class="error-message"></div>
<section class="input-new-cmd">
    <form class="new-cmd-form">
        <input type="text" class="new-external-cmd" placeholder="default command" size="100" required>
        <input type="submit" class="new-cmd-run" value="Run">
    </form>
</section>
<section class="results"></section>
<section class="controls">
    <button class="clear-results">Clear</button>
</section>
<script>
    require('./renderer');
</script>

</html>

Зная, что вызов внешней команды isyn c, я поместил код обновления рендерера в обратный вызов. Однако этот код показывает [object Object] в целевой области вместо вывода ls -al.

Где я был не прав?

Ответы [ 2 ]

1 голос
/ 09 января 2020
// renderer.js

const { shell } = require('electron');

const parser = new DOMParser();

const resultSection = document.querySelector('.results');
const errorMessage = document.querySelector('.error-message');
const newCmdForm = document.querySelector('.new-cmd-form');
const newCmd = document.querySelector('.new-external-cmd');
const newCmdSubmit = document.querySelector('.new-cmd-run');
const newLinkForm = document.querySelector('.new-cmd-form');
const clearStorageButton = document.querySelector('.clear-results');

newLinkForm.addEventListener('submit', (event) => {
    event.preventDefault()
    const cmd = newCmd.value;
    console.log(event)
    processCmd('cmd');
});

const processCmd = (cmd) => {
    var exec = require('child_process').exec;

    exec("ls -al", {timeout: 10000, maxBuffer: 20000*1024},
        function(error, stdout, stderr) {
            var out = stdout.toString();
            const outArray = out.split('\n');

            let result = '<div class="text"'
            outArray.forEach(e => {
                result += `<h3>${e}</h3>`
            });
            result += '</div>';
            resultSection.innerHTML = result;
            console.log(result)
        });
}

const renderResults = () => {
    resultSection.innerHTML = '';
};

renderResults();

Попробуйте использовать это. Я добавил некоторые изменения в вашем рендерере. js Это будет работать хорошо.

Но я бы порекомендовал выполнять подобные операции в основном процессе. Вы знаете, что мы можем использовать IP C api для связи между Renderer и Main process.

Посмотрите на этот https://www.christianengvall.se/main-and-renderer-process-in-electron/

Но лично я обычно не запускаю exec в процессе Renderer. Для этой операции я предпочитаю использовать такие exec или spawn в основном процессе. Мы можем использовать IP C для связи между Main и Renderer process.

IPCMain используется для прослушивания события из Renderer и доступны только в главном процессе. И IPCRenderer используется для отправки событий из Renderer в Main process. Так что.

Используя IPCRenderer, вы можете отправить событие как это на Renderer process.

const { ipcRenderer } = require('electron');

async function runCommand(cmd) {
  const res = await ipcRenderer.sendSync('runCommand', cmd);
  return res;
}

Затем на Main process. (main. js)

// Listen runCommand event from the Renderer
// And return the result to Renderer.
ipcMain.on('runCommand', async (event, arg) => {
  event.returnValue = await runCommand(arg);
});

(вы можете объявить функцию runCommand (arg) самостоятельно в соответствии со своими потребностями) Не стесняйтесь использовать все, что захотите.

1 голос
/ 08 января 2020
  1. Использование подхода узла
  2. Использование IPCRenderer и IPCMain для опроса / ответа о том, завершена ли команда
  3. Когда IPCRenderer сообщают, что содержимое готово, инициируйте обмен AJAX для заполнения рендерера / веб-сайта

PS: документация по использованию IP C в Electron показалась мне неясной - и некоторые из них, кажется, не работают правильно! - так что если вам нужен простой и рабочий пример, вот запуска моего приложения spla sh и вот код приложения , который отвечает (см., в основном, строки 96-109 последняя ссылка).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...