Rails [UPDATED] 用 Rails 开发 WebSSH(入门版)

rocLv · 2021年08月23日 · 最后由 bill997603 回复于 2021年11月25日 · 1014 次阅读

一直想做一个在线交互式编程的学习平台,最近因为教育创业的缘故,所以按捺不住内心的冲动,终于开始撸了~

(和我一样激动的小伙伴别激动,目前只是花了一上午搞的个 Demo 而已)

我用的是Rails 6.1 版本,ruby 2.7.4

不知道有没有人一样和我发现在 Mac Big Sur 上安装Ruby 3.0+ 困难重重?

首先,请出好久不用的 rails new 命令:

$ rails new demo
      create  
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  .gitattributes
      create  Gemfile
         run  git init from "."
     ....
├─ [email protected]
├─ [email protected]
├─ [email protected]
├─ [email protected]
└─ [email protected]
Done in 4.98s.
Webpacker successfully installed 🎉 🍰

建议新手别用Rails 6, 我碰到过很多工作很多年的前端都玩不转前端那一套打包工具。。。。

再次吐槽一下,Rails 6 一点也不 Rails...

如果有必要可以再执行一下,默认会自动执行的:

$ rails webpack:install

随便生成一个scaffold

$ rails g scaffold casts title url
[WARNING] The model name 'casts' was recognized as a plural, using the singular 'cast' instead. Override with --force-plural or setup custom inflection rules for this noun before running the generator.
      invoke  active_record
      create    db/migrate/20210823080552_create_casts.rb
      create    app/models/cast.rb
      invoke    test_unit
      create      test/models/cast_test.rb
      create      test/fixtures/casts.yml
      invoke  resource_route
       route    resources :casts
      invoke  scaffold_controller
      create    app/controllers/casts_controller.rb
      invoke    erb
      create      app/views/casts
      create      app/views/casts/index.html.erb
      create      app/views/casts/edit.html.erb
      create      app/views/casts/show.html.erb
      create      app/views/casts/new.html.erb
      create      app/views/casts/_form.html.erb
      invoke    resource_route
      invoke    test_unit
      create      test/controllers/casts_controller_test.rb
      create      test/system/casts_test.rb
      invoke    helper
      create      app/helpers/casts_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/casts/index.json.jbuilder
      create      app/views/casts/show.json.jbuilder
      create      app/views/casts/_cast.json.jbuilder
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/casts.scss
      invoke  scss
   identical    app/assets/stylesheets/scaffolds.scss

然后运行数据迁移,创建数据库和表:

$ rails db:migrage
== 20210823080552 CreateCasts: migrating ======================================
-- create_table(:casts)
   -> 0.0020s
== 20210823080552 CreateCasts: migrated (0.0021s) =============================

======= 划重点啦 =======

引入xterm.js

$ yarn add xterm
yarn add v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
✨  Done in 2.54s.

引入xterm-addon-attach

$ yarn add xterm-addon-attach
yarn add v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
✨  Done in 2.47s.

app/javascript/packs/application.js 加入:

require("xterm/css/xterm.css")  // 偶个人觉得不太rails way...
import { Terminal } from "xterm"
import { AttachAddon  } from 'xterm-addon-attach';

再在app/views/layouts/application.html.erb中添加以下一行代码:

<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

完整的代码如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Demo</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

然后再在app/javascript/packs/application.js 加入:

document.addEventListener("turbolinks:load", () =>{
  var term = new Terminal();
  term.open(document.getElementById('terminal'));
  const socket = new WebSocket('ws://localhost:5020/containers/b6bb364f6d06/attach/ws?stream=1&stdout=1');
  const attachAddon = new AttachAddon(socket);

  // Attach the socket to term
  term.loadAddon(attachAddon);
  term.write("Hello from \x1B[1;3;31mxterm.js\n\x1B[0m $ ");
})

======== UPDATE:采用更通用的方法 ========

WebSocket的地址这样获得:

之前分享过一篇Caddy 相关的文章,为了让这个例子,更具有通用性,特修改一下,不用socat, 改用Caddy

首先安装 socat

$ brew install socat

其次运行以下命令:

$ socat TCP-LISTEN:5020,reuseaddr,fork UNIX-CLIENT:$HOME/Library/Containers/com.docker.docker/Data/docker.raw.sock

安装Caddy

$ brew install caddy

touch 一个 Caddyfile

$ touch Caddyfile

Caddyfile 文件中输入以下代码

:8080 {
  reverse_proxy unix//Users/i/Library/Containers/com.docker.docker/Data/docker.raw.sock
}

WebSocket的地址中的b6bb364f6d06是 container 的 ID。

理论上来说,Docker 和 host 的共用的是 /var/run/docker.sock,但是 Mac 上读取不出数据,有知道的可以解释一下?

总之,这里走的唯一的弯路就是这个sock的地址。

然后我们再编辑一下app/views/casts/index.html.erb

在最下面添加一行:

<div id="terminal" style="width:800px;height:600px"></div>

重点是添加一个 id 是terminal的 div

此时运行rails s

$ rails s

这时候我们还需要一个启动 docker 镜像,启动的命令里面要加入-i -t的参数,具体参数的意义最好参考一下官方文档。

这个对容器是有要求的,具体可以参考:https://docs.docker.com/engine/reference/commandline/attach/

到这里可以得出结论,xterm.js attach 到容器,最终能不能进行交互,和容器的 ENTRYPOINT/CMD 的命令有很大的关系。如果在创建容器时没指定 -it 或者 ENTRYPOINT/CMD 不是 bash 之类的可交互进程,xterm.js 虽然可以连接到容器,但都不可以与容器进行交互的。 而实际情况是,绝大多数的容器事务,这两个条件都没有。

我们这里贴出参考命令:

$ docker run --name test -d -it debian

然后就可以交互了:

GITHUB 仓库地址

参考文档:https://unihon.github.io/2019-05/use-xterm-js-attach-to-docker-container/

说明:这种教程,相比国外的同类教程来说已经写的非常简约了。我希望后续我基础类教程的分享甚至包括我解决小 bug 的过程。

大牛请绕道~

creating 占了太多篇幅啦 建议在帖子里删掉中间的输出吧

核心应该是 socat

jicheng1014 回复

最初是删了的,后来考虑到毕竟是科普文,最好保留一下。一旦有问题,新生可以对照比较一下。

如果能折叠就好了。

总共三个知识点:

  1. rails 6 如何通过 yarn 引入 npm 包;
  2. socat 的使用;
  3. docker 的 socket。

当然对新手来说知识点就太多了

支持楼主

感觉这里的核心功能是 socat 和那两个 js 包,和 rails 关系不大~,毕竟 6 的 webpacker 槽点也很多

其实我看到这个标题,是想知道实现原理是什么。 而不是,你先这样,再那样,就 OK 了……

wuhuaji0 回复

你要的原理应该都有了,希望先认真看看。。。

我觉得现在 Ruby China 的水平还不至于到连装两个包都要水一篇帖子👀

kowalskidark 回复

总会有一些新人需要的...

莫以善小而不为,莫以恶小而为之

趁午休在本地把 webssh 跑起来了,玩了一下,很酷。

这两句话放到一起会不会好点?看到第一句话时我在想前面什么时候启动了 container,怎么拿到 container id。

WebSocket 的地址中的 b6bb364f6d06 是 container 的 ID。

.

$ docker run --name test -d -it debian

kowalskidark 回复

RubyChina 应该也需要介绍工具的文章,这类文章第一步就是让读者能快速在本地运行工具,接着在折腾该工具的其他功能。

我喜欢 rails tutorial 和 railscast 的原因就是它们提供了手把手教程,可以让我很快在本地把功能跑起来。

PS

的确可以删除常见命令的输出,比如:rails new demo, rails g scaffold, yarn install

hjiangwen 回复

分开写是为了在不影响运行的情况下,保持关注的连续性,以免造成不必要的跳跃

我觉得可以完善下 README 吧

需要 登录 后方可回复, 如果你还没有账号请 注册新账号