16分钟阅读

驾驶室发热编码:Node.js后端教程

凯文在全堆栈,桌面和独立游戏开发中有20多年。他最近专注于PostgreSQL,JavaScript,Perl和Haxe。

Covid-19锁定有很多人在家里困住,也许希望小屋发烧是我们经历的最糟糕的发烧。我们中的许多人都会消耗更多的视频内容。虽然运动尤其重要,但有时候,当笔记本电脑超越时,有银河游戏官方首页良好的,老式遥控器的奢侈品的怀旧。

这就是这个项目进来的地方:机会改变任何智能手机 - 即使是旧的旧电机,否则缺乏更新 - 进入下银河游戏官方首页Netflix / YouTube / Amazon Prime视频/等的方便遥控器。狂欢观察。它也是银河游戏官方首页node.js后端教程:有机会使用快速框架和哈巴狗(以前的jade)模板引擎学习后端JavaScript的基础知识。

如果这听起来令人生畏,完整 node.js. 项目将在最后呈现;读者只需要像他们对学习感兴趣的那样学习,并且沿着更多有经验的读者跳过的方式,一些基础的更大的温和解释。

为什么不只是......?

读者可能想知道,“为什么要进入编码节点。然后回来结束?” (除了学习机会,当然。)“不在那里是银河游戏官方首页应用程序吗?”

肯定 - 很多。但有两种主要原因可能是不可取的:

  1. 对于那些试图重新播放旧电话的人来说,这可能 不再是银河游戏官方首页选择了,与我想要使用的Windows Phone 8.1设备一样。 (App Store于2019年底正式关闭。)
  2. 信任(或缺乏其)。 与在任何移动平台上都有那么多的应用程序一样,他们经常随着用户授予更多的权限而不是应用程序所需的所需的应用程序。但是即使这方面是适当的限制,遥控器应用程序的性质也意味着用户仍然必须信任该应用程序开发人员通过包括间谍软件或其他恶意软件,因此可以信任应用程序开发人员在解决方案的桌面结束上滥用其权限。

这se issues have been around a long time and were even the motivation for a similar project from 2014 found on GitHub. nvm makes it easy to install older versions of Node.js, and even if a few dependencies needed upgrading, Node.js had a great reputation for being backward-compatible.

不幸的是,Bitroot赢了。顽固的方法和node.js后端兼容性对于旧版本的旧版本,培训和数十个其他组件中的旧版本中不可能折旧和不可能依赖循环。几个小时后,超出了,从划痕开始 - 这位作者自己的建议会更容易 重新发明轮子 notwithstanding.

来自旧的新Gizmos:使用Node.js后端将手机作为远程控件重新播放

首先,请注意,此Node.js项目目前特定于Linux开发和在Linux薄荷19和Linux薄荷19.3上测试,特别是可以添加对其他平台的支持。它 可能 已经在Mac上工作。

假设现代版本 node.js. 已安装,并在将作为项目root作为项目root的新目录中打开命令提示符,我们已准备好开始使用Express:

npx express-generator --view=pug

Note: Here, npx is a handy tool that comes with npm, the Node.js package manager that ships with Node.js. We’re using it to run Express’ application skeleton generator. As of this writing, the generator makes an Express/Node.js project that, by default, still pulls in a template engine called Jade, even though the Jade project 重命名为“哈巴狗” from version 2.0 onward. So to be current and use Pug straightaway—plus, avoid deprecation warnings—we tack on --view=pug, a command-line option for the express-generator script being run by npx.

Once that’s done, we need to install some packages from our Node.js project’s newly populated dependency list in package.json. The traditional way to do this is to run npm i (i for “install”). But some still prefer the speed of , so if you have that installed, simply run yarn with no parameters.

在这种情况下,忽略(希望很快固定)应该是安全的 弃用警告 从帕格的子依赖项之一,只要访问到本地网络的访问量即可。

A quick yarn start or npm start, followed by 导航到 localhost:3000 in a browser, shows that our basic Express-based Node.js back end works. We can kill it with Ctrl+C.

node.js.后端教程,步骤2:如何在主机上发送击键

与之 偏僻的 一部分中途完成,让我们注意 控制 部分。我们需要以编程方式控制机器的东西,我们将在键盘上运行我们的node.js后端,假装它在键盘上的按键。

For that, we’ll install xdotool using 它的官方指示。在终端中快速测试其示例命令:

xdotool search "Mozilla Firefox" windowactivate --sync key --clearmodifiers ctrl+l

…should do exactly what it says, assuming Mozilla Firefox is open at the time. That’s good! It’s easy to get our Node.js project to call command-line tools like xdotool, as we’ll soon see.

node.js.后端教程,步骤3:功能设计

对于每个人来说,这可能不是真的,但就个人而言,我发现许多现代的物理遥控器有大约五倍的按钮,因为我将使用的许多按钮。所以对于这个项目,我们正在寻找银河游戏官方首页全屏布局,具有三个漂亮,大,易于目标的按钮的三个三个网格。这取决于个人偏好,这些九个按钮可能是什么。

事实证明,键盘快捷键即使是最简单的函数也不相同 netflix., YouTube, 和 亚马逊Prime视频。这些服务也没有像本机音乐播放器应用程序的通用媒体键合作。此外,所有服务可能无法使用某些功能。

因此,我们需要做的是为每个服务定义不同的遥控布局,并提供在它们之间切换的方法。

定义远程控制布局并将其映射到键盘快捷键

Let’s get a quick prototype working with a handful of presets. We’ll put them in common/preset_commands.js—“common” because we’ll include this data from more than one file:

module.exports = {
  // We could use ⏯️ but some older phones (e.g., Android 5.1.1) won't show it, hence ▶️ instead
  'Netflix': {
    commands: {
      '-': 'Escape',        '+': 'f',             '🔊': 'Up',
      '⇤': 'XF86Back',      '▶️': 'Return',        '🔉': 'Down',
      '⏪': 'Left',         '⏩': 'Right',        '🔇': 'm',
    },
  },
  'YouTube': {
    commands: {
      '⇤': 'shift+p',       '⇥': 'shift+n',       '🔊': 'Up',
      'CC': 'c',            '▶️': 'k',             '🔉': 'Down',
      '⏪': 'j',            '⏩': 'l',            '🔇': 'm',
    },
  },
  'Amazon Prime Video': {
    window_name_override: 'Prime Video',
    commands: {
      '⇤': 'Escape',        '+': 'f',              '🔊': 'Up',
      'CC': 'c',            '▶️': 'space',          '🔉': 'Down',
      '⏪': 'Left',         '⏩': 'Right',         '🔇': 'm',
    },
  },
  'Generic / Music Player': {
    window_name_override: '',
    commands: {
      '⇤': 'XF86AudioPrev', '⇥': 'XF86AudioNext',  '🔊': 'XF86AudioRaiseVolume',
      '🔀': 'r',            '▶️': 'XF86AudioPlay',  '🔉': 'XF86AudioLowerVolume',
      '⏪': 'Left',         '⏩': 'Right',         '🔇': 'XF86AudioMute',
    },
  },
};

这keycode values can be 找到使用 xev。 (对我来说,“音频静音”和“音频播放”,其中没有使用此方法发现,所以我也咨询了 媒体键列表。)

Readers may notice the difference in case between space and Return—regardless of the reason for this, this detail must be honored for xdotool to work correctly. Related to this, we have a couple of definitions written explicitly—e.g., shift+p even though P would also work—just to keep our intentions clear.

node.js后端教程,第4步:我们的API的“键”端点(PARDON双关语)

We’ll need an endpoint to POST to, which in turn will simulate keystrokes using xdotool. Since we’ll have different groups of keys we can send (one for each service), we’ll call the endpoint for a particular one group. We’ll repurpose the generated users endpoint by renaming routes/users.js to routes/group.js, 和 making the corresponding changes in app.js:

// ...

var indexRouter = require('./routes/index');
var groupRouter = require('./routes/group');

// ...

app.use('/', indexRouter);
app.use('/group', groupRouter);

// ...

钥匙 functionality is using xdotool via a system shell call in routes/group.js. We’ll hard-code YouTube as the menu of choice for the moment, just for testing purposes.

const express = require('express');
const router = express.Router();
const debug = require('debug')('app');
const cp = require('child_process');
const preset_commands = require('../common/preset_commands');

/* POST keystroke to simulate */
router.post('/', function(req, res, next) {

  const keystroke_name = req.body.keystroke_name;
  const keystroke_code = preset_commands['YouTube'].commands[keystroke_name];
  const final_command = `xdotool \
  search "YouTube" \
  windowactivate --sync \
  key --clearmodifiers ${keystroke_code}`;

  debug(`Executing ${final_command}`);
  cp.exec(final_command, (err, stdout, stderr) => {
    debug(`Executed ${keystroke_name}`);
    return res.redirect(req.originalUrl);
  });
});

module.exports = router;

Here, we grab the requested key “name” from the POST request’s body (req.body) under the parameter named 钥匙troke_name. That’ll be something like ▶️. We then use that to look up the corresponding code from preset_commands['YouTube']’s commands object.

这final command is on more than one line, so the \s at the end of each line joins all the pieces into a single command:

  • search "YouTube" 在标题中使用“youtube”获取第银河游戏官方首页窗口。
  • windowactivate --sync 激活获取的窗口并等待直到它准备好接收击键。
  • 钥匙 --clearmodifiers ${keystroke_code} 发送击键,确保暂时清除像Caps Lock这样的修改器键,这可能会干扰我们所发送的内容。

此时,代码假定我们在喂食它有效输入 - 我们稍后会更加谨慎。

为简单起见,代码还将假设只有银河游戏官方首页应用程序窗口在其标题中打开了“youtube” - 如果有多个匹配,则无法保证我们将keystrokes发送到预期的窗口。如果这是银河游戏官方首页问题,它可能有助于只需通过遥控的所有窗口切换浏览器选项卡即可简单地更改窗口标题。

With that ready, we can start our server again, but this time with debugging enabled so we can see the output of our debug calls. To do that, simply run DEBUG=old-fashioned-remote:* yarn start or DEBUG=old-fashioned-remote:* npm start. Once it’s running, play a video on YouTube, open another terminal window, and try a cURL call:

curl --data "钥匙troke_name=▶️" http://localhost:3000/group

That sends a POST request with the requested keystroke name in its body to our local machine on port 3000, the port our back end is listening on. Running that command should output notes about Executing and Executed in the npm window, and more importantly, bring up the browser and pause its video. Executing that command again should give the same output and unpause it.

node.js后端教程,步骤5:多个远程控制布局

我们的后端并没有完成。我们还需要它能够:

  1. Produce a list of remote control layouts from preset_commands.
  2. Produce a list of keystroke “names” from once we’ve chosen a particular remote control layout. (We could also have chosen to use common/preset_commands.js directly on the front end, since it’s JavaScript already, and filtered there. That’s one of the potential advantages of a Node.js back end, we just don’t use it here.)

这两个功能都是我们的node.js后端教程与基于帕格的前端相交,我们将建立。

使用PUG模板呈现遥控器列表

这back-end part of the equation means modifying routes/index.js to look like this:

const express = require('express');
const router = express.Router();
const preset_commands = require('../common/preset_commands');

/* GET home page. */
router.get('/', function(req, res, next) {
  const group_names = Object.keys(preset_commands);
  res.render('index', {
    title: 'Which Remote?',
    group_names,
    portrait_css: `.group_bar {
      height: calc(100%/${Math.min(4, group_names.length)});
      line-height: calc(100vh/${Math.min(4, group_names.length)});
    }`,
    landscape_css: `.group_bar {
      height: calc(100%/${Math.min(2, group_names.length)});
      line-height: calc(100vh/${Math.min(2, group_names.length)});
    }`,
  });
});

module.exports = router;

Here, we grab our remote control layout names (group_names) by calling Object.keys on our preset_commands file. We then send them and some other data we’ll need to the Pug template engine that’s automatically called via res.render().

Careful not to confuse the meaning of 钥匙 here with the key笔画 we’re sending: Object.keys gives us an array (an ordered list) containing all the 钥匙键值对 在JavaScript中构成银河游戏官方首页对象:

const my_object = {
  'a key': 'its corresponding value',
  'another key': 'its separate corresponding value',
};

If we look at common/preset_commands.js, we’ll see the above pattern, and our 钥匙 (in the object sense) are the names of our groups: 'Netflix', 'YouTube', etc. Their corresponding values aren’t simple strings as my_object has above—they’re entire objects themselves, with their own keys, i.e., commands and possibly window_name_override.

在此处传递的自定义CSS是允许的,这是一点黑客。我们需要它的原因而不是使用现代的Flexbox的解决方案,可以更好地兼容移动浏览器的美妙世界,以更加美妙的旧的化身。在这种情况下,主要值得注意的是,在横向模式下,我们通过显示每个屏幕上不超过两个选项来保持纽扣;在纵向模式下,四个。

But where does that actually get turned into HTML to be sent to the browser? That’s where views/index.pug comes in, which we’ll want to look like this:

extends layout

block header_injection
  style(media='(orientation: portrait)') #{portrait_css}
  style(media='(orientation: landscape)') #{landscape_css}

block content
  each group_name in group_names
    span(class="group_bar")
      a(href='/group/?group_name=' + group_name) #{group_name}

这very first line is important: extends layout means that Pug will be taking this file in the context of views/layout.pug, which is sort of a parent template we’ll reuse here and also in another view. We’ll need to add a couple of lines after the link line so that the final file looks like this:

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    block header_injection
    meta(name='viewport', content='user-scalable=no')

  body
    block content

我们不会进入HTML的基础知识,但对于读者不熟悉它们,这个帕格代码镜像在无处不在地发现的标准票价。这 模板 aspect of it starts with title= title, which sets the HTML title to whatever value corresponding to the title 钥匙 of the object we pass Pug via res.render.

We can see a different aspect of templating two lines later with a block we’re naming header_injection. Blocks like these are placeholders that can be replaced by templates that extend the current one. (Unrelated, the meta line is simply a quick workaround to mobile browsers, so when users tap the volume controls a bunch of times in a row, the phone refrains from zooming in or out.)

Back to our blocks: This is why views/index.pug defines its own blocks with the same names found in views/layout.pug. In this case of header_injection, this lets us use CSS specific to portrait or landscape orientations the phone will be in.

content 我们是否在此情况下放置了网页的主要可见部分:

  1. Loops through the group_names array we pass it,
  2. creates a <span> element for each one with the CSS class group_bar applied to it, and
  3. creates a link within each <span> based on the group_name.

这CSS class group_bar we can define in the file pulled in via views/layout.pug, namely, public/stylesheets/style.css:

html, body, form {
  padding: 0;
  margin: 0;
  height: 100%;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

.group_bar, .group_bar a, .remote_button {
  box-sizing: border-box;
  border: 1px solid white;
  color: greenyellow;
  background-color: black;
}

.group_bar {
  width: 100%;
  font-size: 6vh;
  text-align: center;
  display: inline-block;
}

.group_bar a {
  text-decoration: none;
  display: block;
}

At this point, if npm start is still running, going to http://localhost:3000/ in a desktop browser should show two very large buttons for Netflix and YouTube, with the rest available by scrolling down.

使用桌面浏览器的遥控布局选择器的测试,显示Netflix和YouTube的两个非常大的按钮。

But if we click them at this point, they won’t work, because we haven’t yet defined the route they link to (the GETting of /group。)

显示所选择的遥控布局

To do so, we’ll add this to routes/group.js just before the final module.exports line:

router.get('/', function(req, res, next) {
  const group_name = req.query.group_name || '';
  const group = preset_commands[group_name];

  return res.render('group', {
    keystroke_names: Object.keys(group.commands),
    group_name,
    title: `${group_name.match(/([A-Z])/g).join('')}-Remote`
  });
});

This will get the group name sent to the endpoint (e.g., by putting ?group_name=Netflix on the end of /group/), and use that to get the value of the commands from the corresponding group. That value (group.commands) is an object, and the keys of that object are the names (钥匙troke_names) we’ll display on our remote control layout.

Note: Inexperienced developers won’t need to get into the details of how it works, but the value for title uses a bit of 常用表达 to turn our group/layout names into acronyms—for example, our YouTube remote will have the browser title YT-Remote. That way, if we’re debugging on our host machine before trying things out on a phone, we won’t have xdotool grabbing the remote control browser window itself, instead of the one we’re trying to control. Meanwhile, on our phones, the title will be nice and short, should we want to bookmark the remote control.

As with our previous encounter with res.render, this one is sending its data to mingle with the template views/group.pug. We’ll create that file and fill it with this:

extends layout

block header_injection
  script(type='text/javascript', src='/javascript/group-client.js')

block content
  form(action="/group?group_name=" + group_name, method="post")
    each keystroke_name in keystroke_names
      input(type="submit", name="钥匙troke_name", value=keystroke_name, class="偏僻的_button")

As with views/index.pug, we’re overriding the two blogs from views/layout.pug. This time, it’s not CSS we’re putting in the header, but some client-side JavaScript, which we’ll get to shortly. (And yes, in a moment of persnicketiness, I renamed the incorrectly pluralized javascripts…)

这main content here is an HTML form made of a bunch of different submit buttons, one for each 钥匙troke_name. Each button submits the form (making a POST request) using the keystroke name it’s displaying as the value it’s sending with the form.

我们的主要样式表文件中还需要更多的CSS:

.remote_button {
  float: left;
  width: calc(100%/3);
  height: calc(100%/3);
  font-size: 12vh;
}

早些时候,当我们设置端点时,我们完成了处理请求:

return res.redirect(req.originalUrl);

这有效地意味着当浏览器提交表单时,Node.js后端通过告诉浏览器将返回页面从-i.e.,主遥控布局提交表单。没有切换页面会更优雅;但是,我们希望与Durepit Mobile浏览器的奇怪和美妙的世界兼容最大兼容性。这样,即使没有任何前端JavaScript工作,我们的Node.js后端项目 应该 still function.

前端javascript的划线

使用表单提交keystrokes的缺点是浏览器必须等待,然后执行额外的往返:页面及其依赖项,然后请从我们的node.js后端请求并传递。然后,需要由浏览器再次呈现它们。

读者可能想知道这可能有多少效果。毕竟,页面是微小的,其依赖项是非常最小的,我们的最终节点项目将在本地WiFi连接上运行。应该是银河游戏官方首页低延迟设置,对吗?

As it turns out—at least when testing on older smartphones running Windows Phone 8.1 and Android 4.4.2—the effect is unfortunately quite noticeable in the common case of rapidly tapping to raise or lower playback volume by a few notches. Here’s where JavaScript can help, without taking away from our graceful fallback of manual POSTs via HTML forms.

At this point, our final client JavaScript (to be put in public/javascript/group-client.js) needs to be compatible with old, no-longer-supported mobile browsers. But we don’t need much of it:

(function () {
  function form_submit(event) {
    var request = new XMLHttpRequest();
    request.open('POST', window.location.pathname + window.location.search, true);
    request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    request.send('keystroke_name=' + encodeURIComponent(event.target.value));
    event.preventDefault();
  }
  window.addEventListener("DOMContentLoaded", function() {
    var inputs = document.querySelectorAll("input");
    for (var i = 0; i < inputs.length; i++) {
      inputs[i].addEventListener("click", form_submit);
    }    
  });
})();

Here, the form_submit function just sends the data via an asynchronous call, and the last line prevents the normal send behavior of browsers, whereby a new page loads based on the server response. The latter half of this snippet simply waits until the page loads and then hooks up every submit button to use form_submit. The whole thing is wrapped in 一名概要.

最后的触感

有很多 变化 在我们的Node.js后端教程代码的最终版本中的上述片段中,主要是为了更好的错误处理:

  • 这Node.js back end now checks the names of groups and keystrokes sent to it to make sure they exist. This code is in a function that’s reused for both the GET and POST functions of routes/group.js.
  • We make use of the Pug error template if they don’t.
  • 这front-end JavaScript and CSS now make buttons temporarily outline in grey while waiting for a response from the server, green as soon as the signal went all the way through xdotool and back without trouble, and red if anything didn’t work as expected.
  • 如果它模具,则Node.js后端将打印堆栈跟踪,这将不太可能给出上面的可能性。

欢迎读者来仔细阅读(和/或克隆)完整的node.js项目 在GitHub上.

node.js.后端教程,第5步:银河游戏官方首页真实的测试

It’s time to try it out on an actual phone attached to the same wifi network as the host that’s running npm start and a movie or music player. It’s just a matter of pointing a smartphone’s web browser to the host’s local IP address (with :3000 suffixed to it), which is probably easiest found by running hostname -I | awk '{print $1}' in a terminal on the host.

One problem Windows Phone 8.1 users might notice is that attempting to navigate to something like 192.168.2.5:3000 will give an error popup:

标题为Windows手机错误消息的屏幕截图"Unsupported address," saying "Internet Explorer Mobile不'T支持这种类型的地址和可以't display this page.

Thankfully, there’s no need to be discouraged: Simply prefixing with http:// or adding a trailing / gets it to fetch the address without further complaint.

这remote control layout selection screen.

选择银河游戏官方首页选项应该为我们带来工作遥控器。

这"Generic/Music Player"遥控屏幕。

为了增加方便,用户可能希望调整他们的路由器的DHCP设置,以始终为主机分配相同的IP地址,并将布局选择屏幕和/或任何喜爱的布局添加书签。

拉请求欢迎

很可能不是每个人都喜欢这个项目。以下是一些改进的想法,对于那些想要进一步挖掘代码的人来说:

  • 调整布局应该是直接的,或为其他服务添加新的服务,如迪斯尼加。
  • 也许有些人更喜欢“光模式”布局和在介于之间切换的选项。
  • 退出Netflix,因为它是不可逆转的,可以真正使用“你确定吗?”确认某种类型。
  • 这project would surely benefit from 视窗 support.
  • xdotool文档确实提到了OSX - 这是否(或者可以这个)项目在现代Mac上工作?
  • 对于高级休闲,一种搜索和浏览电影的方法,而不是必须挑选银河游戏官方首页Netflix / Amazon Prime视频电影,而不是在计算机上创建YouTube播放列表。
  • 在任何建议的更改中断的情况下,自动测试套件打破了原始功能。

我希望您享受此Node.js后端教程和结果改进的媒体体验。快乐的流和编码!

理解基础知识

是后端的node.js吗?

是的。 Node.js是运行JavaScript代码的命令行程序,它通常在Web主机上使用以提供网页,连接到数据库,等等。

node.js是否足够了解后端?

绝对地。正确归档,银河游戏官方首页节点.js后端可以扩展以及任何技术都可以。也就是说,它通常与其他重要组成部分集成,例如访问应用程序的数据库层。

什么是Express.js?

Express是节点.js的模块,可减少编写常用Web服务器功能所需的样板代码量。它拥有银河游戏官方首页成熟的亚生态系统。大多数Node.js Web服务器使用Express。

什么是哈巴狗/玉?

哈巴狗(以前的翡翠)是一款与Express集成的模板引擎。事实上,多年来,它是Express Project Generator在新项目中包含的默认模板引擎。

什么是xdotool?

命令行程序XDOTOOL模拟它在运行的计算机上的击键。此项目允许手机通过网页对计算机执行此类操作,将其转换为遥控器。