分类 技术 下的文章

编程过程有时候就像一场与丧尸群之间的战斗。在这个系列文章中,我将带你了解怎样将 ZOMBIES 方法应用到实际工作中。

 title=

很久以前,在我还是一个萌新程序员的时候,我们曾经被分配一大批工作。我们每个人都被分配了一个编程任务,然后回到自己的小隔间里噼里啪啦地敲键盘。我记得团队里的成员在自己的小隔间里一呆就是几个小时,为打造无缺陷的程序而奋斗。当时流行的思想是:能一次性做得越多,能力越强。

对于我来说,能够长时间编写或者修改代码而不用中途停下来检验这些代码是否有效,就像荣誉勋章一样。那个时候我们都认为停下来检验代码是否工作是能力不足的表现,菜鸟才这么干。一个“真正的开发者”应该能一口气构建起整个程序,中途不用停下来检查任何东西!

然而事与愿违,当我停止在开发过程中测试自己的代码之后,来自现实的检验狠狠地打了我的脸。我的代码要么无法通过编译,要么构建失败,要么无法运行,或者不能按预期处理数据。我不得不在绝望中挣扎着解决这些烦人的问题。

避开丧尸群

如果你觉得旧的工作方式听起来很混乱,那是因为它确实是这样的。我们一次性处理所有的任务,在问题堆里左砍右杀,结果只是引出更多的问题。着就像是跟一大群丧尸间的战斗。

如今我们已经学会了避免一次性做太多的事情。在最初听到一些专家推崇避免大批量地开发的好处时,我觉得这很反直觉,但我已经从过去的犯错中吸取了教训。我使用被 James Grenning 称为 ZOMBIES 的方法来指导我的软件开发工作。

ZOMBIES 方法来救援!

ZOMBIES 表示以下首字母缩写:

  • Z – 最简场景(Zero)
  • O – 单元素场景(One)
  • M – 多元素场景(Many or more complex)
  • B – 边界行为(Boundary behaviors)
  • I – 接口定义(Interface definition)
  • E – 处理特殊行为(Exercise exceptional behavior)
  • S – 简单场景用简单的解决方案(Simple scenarios, simple solutions)

我将在本系列文章中对它们进行分析讲解。

最简场景

最简场景指可能出现的最简单的情况。

人们倾向于最开始的时候使用硬编码值,因为这是最简单的方式。通过在编码活动中使用硬编码值,可以快速构建出一个能即时反馈的解决方案。不需要几分钟,更不用几个小时,使用硬编码值让你能够马上与正在构建的系统进行交互。如果你喜欢这个交互,就朝这个方向继续做下去。如果你发现不喜欢这种交互,你可以很容易抛弃它,根本没有什么可损失。

本系列文章将以构建一个简易的购物系统的后端 API 为例进行介绍。该服务提供的 API 允许用户创建购物筐、向购物筐添加商品、从购物筐移除商品、计算商品总价。

首先,创建项目的基本结构(将购物程序的代码和测试代码分别放到 apptests 目录下)。我们的例子中使用开源的 xUnit 测试框架。

现在撸起你的袖子,在实践中了解最简场景吧!

[Fact]
public void NewlyCreatedBasketHas0Items() {    
    var expectedNoOfItems = 0;
    var actualNoOfItems = 1;
    Assert.Equal(expectedNoOfItems, actualNoOfItems);
}

这是一个伪测试,它测试的是硬编码值。新创建的购物筐是空的,所以购物筐中预期的商品数是 0。通过比较期望值和实际值是否相等,这个预期被表示成一个测试(或者称为断言)。

运行该测试,输出结果如下:

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.57] tests.UnitTest1.NewlyCreatedBasketHas0Items [FAIL]
  X tests.UnitTest1.NewlyCreatedBasketHas0Items [4ms]
  Error Message:
   Assert.Equal() Failure
Expected: 0
Actual: 1
[...]

这个测试显然无法通过:期望商品数是 0,但是实际值被硬编码为了 1。

当然,你可以马上把硬编码的值从 1 改成 0,这样测试就能通过了:

[Fact]
public void NewlyCreatedBasketHas0Items() {
    var expectedNoOfItems = 0;
    var actualNoOfItems = 0;
    Assert.Equal(expectedNoOfItems, actualNoOfItems);
}

与预想的一样,运行测试,测试通过:

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 1.0950 Seconds

你也许会认为执行一个被强迫失败的测试完全没有意义,但是不管一个测试多么简单,确保它的可失败性是绝对有必要的。只有这样才能够保证如果在后续工作中不小心破坏了程序的处理逻辑时该测试能够给你相应的警告。

现在停止伪造数据,将硬编码的值替换成从 API 中获取的值。我们已经构造了一个能够可靠地失败的测试,它期望一个空的购物筐中有 0 个商品,现在是时候编写一些应用程序代码了。

就跟常见的软件建模活动一样,我们先从构造一个简单的接口开始。在 app 目录下新建文件 IShoppingAPI.cs(习惯上接口名一般以大写 I 开头)。在该接口中声明一个名为 NoOfItems() 的方法,它以 int 类型返回商品的数量。下面是接口的代码:

using System;

namespace app {    
    public interface IShoppingAPI {
        int NoOfItems();
    }
}

当然这个接口什么事也做不了,在你需要实现它。在 app 目录下创建另一个文件 ShoppingAPI。在其中将 ShoppingAPI 声明为一个实现了 IShoppingAPI 的公有类。在类中定义方法 NoOfItems 返回整数 1:

using System;

namespace app {
    public class ShoppingAPI : IShoppingAPI {
        public int NoOfItems() {
            return 1;
        }
    }
}

从上面代码中你发现自己又在通过返回硬编码值 1 的方式来伪造代码逻辑。现阶段这是一件好事,因为你需要保持一切超级无敌简单。现在还不是仔细构想如何实现购物筐的处理逻辑时候。这些工作后续再做!到目前为止,你只是通过构建最简场景来检验自己是否满意现在的设计。

为了确定这一点,将硬编码值换成这个 API 在运行中收到请求时应该返回的值。你需要通过 using app; 声明来告诉测试你使用的购物逻辑代码在哪里。

接下来,你需要 实例化 instantiate IShoppingAPI 接口:

IShoppingAPI shoppingAPI = new ShoppingAPI();

这个实例用来发送请求并接收返回的值。

现在,代码变成了这样:

using System;
using Xunit;
using app;

namespace tests {
    public class ShoppingAPITests {
        IShoppingAPI shoppingAPI = [new][3] ShoppingAPI();
 
        [Fact]        
        public void NewlyCreatedBasketHas0Items() {
            var expectedNoOfItems = 0;
            var actualNoOfItems = shoppingAPI.NoOfItems();
            Assert.Equal(expectedNoOfItems, actualNoOfItems);
        }
    }
}

显然执行这个测试的结果是失败,因为你硬编码了一个错误的返回值(期望值是 0,但是返回的是 1)。

同样的,你也可以通过将硬编码的值从 1 改成 0 来让测试通过,但是现在做这个是在浪费时间。现在设计的接口已经跟测试关联上了,你剩下的职责就是编写代码实现预期的行为逻辑。

在编写应用程序代码时,你得决定用来表示购物筐得数据结构。为了保持设计的简单,尽量选择 C# 中表示集合的最简单类型。第一个想到的就是 ArrayList。它非常适合目前的使用场景——可以保存不定个数的元素,并且易于遍历访问。

因为 ArrayListSystem.Collections 包的一部分,在你的代码中需要声明:

using System.Collections;

然后 basket 的声明就变成这样了:

ArrayList basket = new ArrayList();

最后将 NoOfItems() 中的因编码值换成实际的代码:

public int NoOfItems() {
    return basket.Count;
}

这次测试能够通过了,因为最初购物筐是空的,basket.Count 返回 0。

这也是你的第一个最简场景测试要做的事情。

更多案例

目前的课后作业是处理一个丧尸,也就是第 0 个丧尸。在下一篇文章中,我将带你了解单元素场景和多元素场景。不要错过哦!

(题图:MJ/7917bc47-5325-4c0f-a2dd-4e444f57a46c)


via: https://opensource.com/article/21/2/development-guide

作者:Alex Bunardzic 选题:lujun9972 译者:toknow-gh 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本文中,我们将向你展示如何在 RHEL、Rocky Linux 或 AlmaLinux 上安装和配置 FreeIPA 客户端。

为了演示,我们在 RHEL 系统上集成了 FreeIPA 服务器,使用 FreeIPA 进行集中认证。

FreeIPA 服务器是一个开源的身份管理解决方案,为 Linux 系统提供集中的身份验证、授权和帐户信息。

先决条件

  • 已预装 RHEL 9/8 或 Rocky Linux 9/8 或 AlmaLinux 9/8
  • 具有 sudo 权限的普通用户
  • RHEL 系统的有效订阅。
  • 互联网连接

事不宜迟,让我们深入了解 FreeIPA 客户端安装和配置步骤,

1、在 FreeIPA 服务器上创建一个用户

登录到 FreeIPA 服务器并创建一个用户以进行集中身份验证,这里我使用以下命令使用创建了一个用户 opsadm

$ sudo kinit admin
$ sudo ipa user-add opsadm --first=Ops --last=Admin --password
Password:
Enter Password again to verify:
-------------------
Added user "opsadm"
-------------------
  User login: opsadm
  First name: Ops
  Last name: Admin
  Full name: Ops Admin
  Display name: Ops Admin
  Initials: OA
  Home directory: /home/opsadm
  GECOS: Ops Admin
  Login shell: /bin/bash
  Principal name: [email protected]
  Principal alias: [email protected]
  User password expiration: 20230502010113Z
  Email address: [email protected]
  UID: 464600004
  GID: 464600004
  Password: True
  Member of groups: ipausers
  Kerberos keys available: True
$

2、为 RHEL、Rocky Linux 或 AlmaLinux 添加 DNS 记录

下一步是为我们想要与 FreeIPA 服务器集成以集中身份验证的机器添加 DNS 记录。在 FreeIPA 服务器上,运行以下命令:

$ sudo ipa dnsrecord-add linuxtechi.lan rhel.linuxtechi.lan --a-rec 192.168.1.2

注意:在上述命令中替换为你自己的 IP 地址和主机名。

现在登录到 RHEL 客户端并在 /etc/hosts 文件中添加以下条目:

192.168.1.102 ipa.linuxtechi.lan ipa
192.168.1.2   rhel.linuxtechi.lan rhel

保存并退出文件。

3、在 RHEL、RockyLinux 和 AlmaLinux 上安装和配置 FreeIPA 客户端

FreeIPA 客户端及其依赖项在默认软件包仓库(AppStream 和 BaseOS)中可用,因此要安装 FreeIPA 客户端,请运行:

$ sudo dnf install freeipa-client -y

安装完成后,配置 FreeIPA 客户端,运行以下命令:

$ sudo ipa-client-install --hostname=`hostname -f` --mkhomedir --server=ipa.linuxtechi.lan --domain linuxtechi.lan  --realm LINUXTECHI.LAN

根据你的设置替换 FreeIPA 服务器的主机名、域名和领域。

输出:

完美,上面的输出确认 freeipa-client 命令已成功执行。要测试 FreeIPA 客户端集成,请从当前用户注销并尝试以我们在 IPA 服务器上创建的 opsadm 用户身份登录。

4、测试 FreeIPA 客户端

试着在你刚刚配置了 FreeIPA 客户端的 RHEL 系统上使用 opsadm 用户通过 SSH 登录。

$ ssh opsadm@<IPAddress-RHEL>

当我们第一次登录系统时,由于密码过期政策,它会提示你设置新密码。

修改密码后,再次尝试登录。这次你应该可以登录了。

很好,上面的输出确认我们可以使用 opsadm 用户登录。这确认 FreeIPA 客户端安装和配置成功。

以上就是这篇文章的全部内容,希望你发现它提供了丰富的信息,请在下面的评论部分中发表你的疑问和反馈。

(题图:MJ/583ee400-3bad-4036-a725-f9d2078d69ab)


via: https://www.linuxtechi.com/install-freeipa-client-on-rhel-rockylinux-almalinux/

作者:Pradeep Kumar 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

尝试用这个 KDE 程序做专业的视频编辑。

无论是雪日、季节性假期,或是任何假期,都是在你电脑前专心发挥创造力的好时候。我最喜欢的一种消遣就是剪视频。有时,我为了讲个故事来剪;其他时候,我则是为了表达我的心情、观点、为我发现或创作的音乐提供视觉效果。也许这是因为我在学校为从事这一领域的职业学习了剪视频,或就只是因为我喜欢强大的开源工具。至今,我最喜欢的视频剪辑程序是优秀的 Kdenlive,这是一个强大而专业的剪辑工具,提供了直观的工作流、大量的特效和转场。

在 Linux 上安装 Kdenlive

Kdenlive 可以通过大部分的 Linux 发行版的包管理器安装。在 Fedora、Mageia 或类似的发行版:

$ sudo dnf install kdenlive

在 Elementary、Mine 或其他基于 Debian 的发行版:

$ sudo apt install kdenlive

不过,我用 Flatpak 来安装 Kdenlive。

如何籍视频讲故事

到底“编辑”视频是什么意思?

剪辑视频有些夸张的误解。当然,它是使鸿篇巨制的大片影响全世界数百万人的过程,但当你在你的笔记本前坐下时,你不必那样想。剪辑视频就是一个十分简单的,移除“坏的”部分,直到只剩下“好的”部分的工作。

什么是“好”镜头还是“坏”镜头,完全取决于你自己的品味,甚至可能根据你想用你的创作 “说” 的内容而改变。如果你在剪辑你在后院发现的野生动物的镜头,你可能会剪掉那些突出你的垃圾桶或你踩着耙子的镜头。剩下的部分肯定会使你的后院看起来像一个神奇的秘密花园,里面有蜂鸟、蝴蝶、好奇的兔子和一只俏皮的狗。另一方面,留下这些 “坏” 镜头,你就可以创造一部喜剧,讲述一个郊区人在清理垃圾时,踩到了耙子上,把所有的动物都吓跑了,总之是在捣乱。这没有对错之分。无论你切掉什么,没有人知道曾经存在过。无论你保留什么,都会有一个故事。

导入镜头

当你启动 Kdenlive,你会有个空项目。Kdenlive 窗口包括在左上角 项目箱 Project Bin 、一个在中间的信息框,以及一个在右上的 项目监视器 Project Monitor 。在下面的是十分重要的部分—— 时间轴 Timeline 。时间轴是创建你的故事的地方。在你的项目结束时,时间轴中的所有内容都是你的观众所看到的。这就是你的影片。

在你开始在你的时间轴上构建故事前,你需要一些素材。假设你已经从相机或手机上获得了一些视频,你必须在项目箱中增加一些素材。右键点击项目箱面板的空位置,然后选择 添加素材或文件夹 Add Clip or Folder

 title=

裁剪镜头

Kdenlive 中有许多方式来裁剪视频镜头。

三点式编辑

以前,创建素材的正式方式是“三点式编辑”,包括如下几点:

  1. 素材监视器 Clip Monitor 中打开一个视频素材,找到你希望视频开始的点,然后点键盘上的 l 来标记 开始
  2. 然后找你想让视频停止的点,并按 O 来标记 结束
  3. 从素材监视器拖动视频素材到 Kdenlive 窗口底部的时间轴上的某一个位置。

 title=

这个方法依然在某些环境中保有重要地位,但对于很多用户来说太“书面化”了。

轴内编辑

另一个编辑的方法是拖动切片到 Kdenlive 的时间轴面板,并拖动切片的边缘,直到只留下好的部分。

 title=

离切的艺术

另一种编辑技巧是 离切 cut-away。这是个重要的技巧,它不只帮助你跳过视频切片中的坏的部分,而且可以为你的观众增加背景信息。在电影和电视中,你已经见过了许多离切,即使你不理解它。每当荧幕上的人看惊讶地抬头,然后你就能看到他们的视角,这就是离切。当一个新闻主播提到你们城市中的一处地方,然后那个地方的镜头跟随其后,这也是离切。

你可以轻易的在 Kdenlive 中完成离切操作,因为 Kdenlive 时间轴是叠层式的。默认情况下,Kdenlive 中有四个 “ 轨道 track ” ——最上面的两个分给视频,而下面的两个给伴奏的音频。当你在时间轴上放置视频素材,放在较高的视频轨道上的优先于放在下面的轨道。这意味着你可以在功能上编辑掉视频轨道的镜头,只需要通过在较高的轨道上放些更好的素材就行。

 title=

导出你的电影

当你的编辑都完成后,你可以导出你的电影,然后来把它发布到网上,让其他人看到。要做到这一点,点击在 Kdenlive 窗口顶端工具栏上的 渲染 Render 按钮。在显现的 渲染 Rendering 窗口中,选择你的视频托管服务支持的格式。WEBM 格式是近日很普遍的一种格式,除了是开源的,它也是可用于分发和存档的最佳格式之一。它能支持 4K、立体图像、广色域等更多的特性。而且所有的主流浏览器都可以播放它。

渲染需要时间,这取决于你的项目长度、你作出了多少编辑、以及你电脑的性能。

一个长效的解决方案

当我写这篇文章的时候,正好在十年前的今天,我发表了 关于 Kdenlive 的六篇介绍文章 。令我惊讶的是,这意味着我成为 Kdenlive 用户的时间比我在电影学院学习的专有编辑器的时间还要长。这是令人印象深刻的长寿,而且我今天仍然在使用它,因为它的灵活性和可靠性是其他编辑器无法比拟的。糟糕的是,我所学过的专有视频编辑器甚至都不存在了,至少不再以同样的形式存在(这让我希望我在一个开源平台上学习编辑!)。

Kdenlive 是一个功能强大的编辑器,有很多功能,但不要让这些吓倒你。我的介绍系列在今天和十年前一样相关而准确,在我看来,这是一个真正可靠的应用程序的特征。如果你想选择 Kdenlive 作为视频编辑器,一定要下载我们的 速查表,这样你就可以熟练使用键盘快捷键,减少点击次数,使编辑过程无缝进行。

现在去讲你的故事吧!

(题图:MJ/028511ed-687f-4894-b4aa-cf3f6c108a1a)


via: https://opensource.com/article/21/12/kdenlive-linux-creative-app

作者:Seth Kenlon 选题:lujun9972 译者:yjacks 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

哈喽!昨天我见识到了一种我以前从没见过的从服务器推送事件的炫酷方法: 服务器推送事件 server-sent events !如果你只需要让服务器发送事件,相较于 Websockets,它们或许是一个更简便的选择。

我会聊一聊它们的用途、运作原理,以及我昨日在试着运行它们的过程中遇到的几个错误。

问题:从服务器流式推送更新

现在,我有一个启动虚拟机的 Web 服务,客户端轮询服务器,直到虚拟机启动。但我并不想使用轮询方式。

相反,我想让服务器流式推送更新。我跟 Kamal 说我要用 Websockets 来实现它,而他建议使用服务器推送事件不失为一个更简便的选择!

我登时就愣住了——那什么玩意???听起来像是些我从来没见过的稀罕玩意儿。于是乎我就查了查。

服务器推送事件就是个 HTTP 请求协议

下文便是服务器推送事件的运作流程。我-很-高-兴-地了解到它们就是个 HTTP 请求协议。

1.客户端提出一个 GET 请求(举个例子)https://yoursite.com/events 2.客户端设置 Connection: keep-alive,这样我们就能有一个长连接 3.服务器设置设置一个 Content-Type: text/event-stream 响应头 4.服务器开始推送事件,就比如下文这样:

event: status
data: one

举个例子,这里是当我借助 curl 发送请求时,一些服务器推送事件的样子:

$ curl -N 'http://localhost:3000/sessions/15/stream'
event: panda
data: one

event: panda
data: two

event: panda
data: three

event: elephant
data: four

服务器可以根据时间推移缓慢推送事件,并且客户端也能够在它们到来时读取它们。你也可以将 JSON 或任何你想要的东西放在事件当中,就比如 data: {'name': 'ahmed'}

线路协议真的很简单(只需要设置 event:data:,或者如果你愿意,可设置为 id:retry:),所以你并不需要任何花里胡哨的服务器库来实现服务器推送事件。

JavaScript 的代码也超级简单(仅使用 EventSource)

以下是用于流式服务器推送事件的浏览器 JavaScript 的代码。(我从 服务器推送事件的 MND 页面 得到的这个范例)

你可以订阅所有事件,也可以为不同类型的事件使用不同的处理程序。这里我有一个只接受类型为 panda 的事件的处理程序(就像我们的服务器在上一节中推送的那样)。

const evtSource = new EventSource("/sessions/15/stream", { withCredentials: true })
evtSource.addEventListener("panda", function(event) {
  console.log("status", event)
});

客户端在中途不能推送更新

不同于 Websockets,服务器推送事件不允许大量的来回事件通讯。(这体现在它的字眼中 —— 服务器 推送所有事件)。初始的时候客户端发出一个请求,然后服务器发出一连串响应。

如果 HTTP 连接结束,它会自动重连

使用 EventSource 发出的 HTTP 请求和常规 HTTP 请求有一个很大的区别,MDN 文档中对此有所说明:

默认情况下,如果客户端和服务器之间的连接断开,则连接会重启。请使用 .close() 方法来终止连接。

很奇怪,一开始我真的被它吓到了:我打开了一个连接,然后在服务器端将其关闭,然后两秒过后客户端向我的传送终端发送了另一条请求!

我觉得这里可能是因为连接在完成之前意外断开了,所以客户端自动重新打开了它以防止类似情况再发生。

所以如果你不想让客户端继续重试,你就得通过调用 .close() 直截了当地关闭连接。

这里还有些其它特性

你还能在服务器推送事件中设置 id:retry: 字段。似乎,如果你在服务器推送事件上设置,那么当重新连接时,客户端将发送一个 Last-Event-ID 响应头,带有它收到的最后一个 ID。酷!

我发现 W3C 的服务器推送事件页面 令人惊讶地容易理解。

在设置服务器推送事件的时候我遇到了两个错误

我在 Rails 中使用服务器推送事件时遇到了几个问题,我认为这些问题挺有趣的。其中一个缘于 Nginx,另一个是由 Rails 引起的。

问题一:我不能在事件推送的过程中暂停

这个奇怪的错误是在我做以下操作时出现的:

def handler
    # SSE is Rails' built in server-sent events thing
    sse = SSE.new(response.stream, event: "status")
    sse.write('event')
    sleep 1
    sse.write('another event')
end

它会写入第一个事件,但不能写入第二个事件。我对此-非-常-困-惑,然后放开脑洞,试着理解 Ruby 中的 sleep 是如何运作的。但是 Cass 将我引领到一个与我有着相同困惑的 Stack Overflow 问答帖,而这里包含了让我为之震惊的回答!

事实证明,问题出在我的 Rails 服务器位于 Nginx 之后,似乎 Nginx 默认使用 HTTP/1.0 向上游服务器发起请求(为啥?都 2021 年了,还这么干?我相信这其中一定有合乎情理的解释,也许是为了向下兼容之类的)。

所以客户端(Nginx)会在服务器推送第一个事件之后直接关闭连接。我觉得如果在我推送第二个事件的过程中 没有 暂停,它继续正常工作,基本上就是服务器在连接关闭之前和客户端在争速度,争着推送第二部分响应,如果我这边推送速度足够快,那么服务器就会赢得比赛。

我不确定为什么使用 HTTP/1.0 会使客户端的连接关闭(可能是因为服务器在每个事件结尾写入了两个换行符?),但因为服务器推送事件是一个比较新的玩意儿,HTTP/1.0 (这种老旧协议)不支持它一点都会不意外。

设置 proxy_http_version 1.1 从而解决那个麻烦。好欸!

问题二:事件被缓冲

这个事情解决完,第二个麻烦接踵而至。不过这个问题实际上非常好解决,因为 Cass 已经建议将 stackoverflow 里另一篇帖的回答 作为前一个问题的解决方案,虽然它并没有是导致问题一出现的源头,但它-确-实-解-释-了问题二。

问题在这个示例代码中:

def handler
    response.headers['Content-Type'] = 'text/event-stream'
    # Turn off buffering in nginx
    response.headers['X-Accel-Buffering'] = 'no'
    sse = SSE.new(response.stream, event: "status")
    10.times do
        sse.write('event')
        sleep 1
    end
end

我本来期望它每秒返回 1 个事件,持续 10 秒,但实际上它等了 10 秒才把 10 个事件一起返回。这不是我们想要的流式传输方式!

原来这是因为 Rack ETag 中间件想要计算 ETag(响应的哈希值),为此它需要整个响应为它服务。因此,我需要禁用 ETag 生成。

Stack Overflow 的回答建议完全禁用 Rack ETag 中间件,但我不想这样做,于是我去看了 链接至 GitHub 上的议题

那个 GitHub 议题建议我可以针对仅流式传输终端应用一个解决方法,即 Last-Modified 响应头,显然,这么做可以绕过 ETag 中间件。

所以我设置为:

headers['Last-Modified'] = Time.now.httpdate

然后它起作用了!!!

我还通过设置响应头 X-Accel-Buffering: no 关闭了位于 Nginx 中的缓冲区。我并没有百分百确定我要那样做,但这么做似乎更安全。

Stack Overflow 很棒

起初,我全身心致力于从头开始调试这两个错误。Cass 为我指向了那两个 Stack Overflow 帖子,一开始我对那些帖下提出的解决方案持怀疑态度(我想:“我没有使用 HTTP/1.0 啊!ETag 响应头什么玩意,跟这一切有关系吗??”)。

但结果证明,我确实无意中使用 HTTP/1.0,并且 Rack ETag 中间件确实给我带来了问题。

因此,也许这个故事告诉我,有时候计算机就是会以奇怪的方式相互作用,其它人在过去也遇到过计算机以完全相同的奇怪方式相互作用的问题,而 Stack Overflow 有时会提供关于为什么会发生这些情况的答案 : )

我认为重要的是不要随意从 Stack Overflow 中尝试各种解决方案(当然,在这种情况下不会有人建议这样做!)。对于这两个问题,我确实需要去仔细思考,了解发生了什么,还有为什么更改这些设置会起作用。

就是这样!

今天我要继续着手实现服务器推送事件,因为昨天一整天我都沉浸在上述这些错误里。好在我学到了一个以前从未听说过的易学易用的网络技术,心里还是很高兴的。

(题图:MJ/4c08a193-086e-4efe-a662-00401c928c41)


via: https://jvns.ca/blog/2021/01/12/day-36--server-sent-events-are-cool--and-a-fun-bug/

作者:Julia Evans 选题:lujun9972 译者:Drwhooooo 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

结合开放的 API 和 Python 编程语言的力量。

围绕 API 创建封装器的开源项目正变得越来越流行。这些项目使开发人员更容易与 API 交互并在他们的应用中使用它们。openshift-python-wrapper 项目是 openshift-restclient-python 的封装器。最初是一个帮助我们的团队使用 OpenShift API 的内部包,后来变成了一个开源项目(Apache 许可证 2.0)。

本文讨论了什么是 API 封装器,为什么它很有用,以及封装器的一些例子。

为什么要使用 API 封装器?

API 封装器是位于应用和 API 之间的一层代码。它通过将一些涉及发出请求和解析响应的复杂性抽象出来,以简化 API 访问过程。封装器还可以提供 API 本身提供的功能之外的附加功能,例如缓存或错误处理。

使用 API 封装器使代码更加模块化并且更易于维护。无需为每个 API 编写自定义代码,你可以使用封装器来提供与 API 交互的一致接口。它可以节省时间,避免代码重复,并减少出错的机会。

使用 API 封装器的另一个好处是它可以保护你的代码免受 API 变化的影响。如果 API 更改了它的接口,你可以更新封装器代码而无需修改你的应用程序代码。随着时间的推移,这可以减少维护应用程序所需的工作。

安装

该应用位于 PyPi 上,因此使用 pip 命令 安装 openshift-python-wrapper

$ python3 -m pip install openshift-python-wrapper

Python 封装器

OpenShift REST API 提供对 OpenShift 平台的许多功能的编程访问。封装器提供了一个简单直观的界面,用于使用 openshift-restclient-python 库与 API 进行交互。它标准化了如何使用集群资源,并提供了统一的资源 CRUD(创建、读取、更新和删除)流程。它还提供额外的功能,例如需要由用户实现的特定于资源的功能。随着时间的推移,封装器使代码更易于阅读和维护。

简化用法的一个示例是与容器交互。在容器内运行命令需要使用 Kubernetes 流、处理错误等。封装器处理这一切并提供 简单直观的功能

>>> from ocp_resources.pod import Pod
>>> from ocp_utilities.infra import get_client
>>> client = get_client()

ocp_utilities.infra INFO Trying to get
client via new_client_from_config

>>> pod = Pod(client=client, name="nginx-deployment-7fb96c846b-b48mv", namespace="default")
>>> pod.execute("ls")

ocp_resources Pod INFO Execute ls on
nginx-deployment-7fb96c846b-b48mv (ip-10-0-155-108.ec2.internal)

'bin\nboot\ndev\netc\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n'

开发人员或测试人员可以使用这个封装器,我们的团队在编写代码的同时牢记测试。使用 Python 的上下文管理器可以提供开箱即用的资源创建和删除,并且可以使用继承来扩展特定场景的功能。Pytest fixtures 可以使用代码进行设置和拆卸,不留任何遗留物。甚至可以保存资源用于调试。可以轻松收集资源清单和日志。

这是上下文管理器的示例:

@pytest.fixture(scope="module")
def namespace():
    admin_client = get_client()
    with Namespace(client=admin_client, name="test-ns",) as ns:
        ns.wait_for_status(status=Namespace.Status.ACTIVE, timeout=240)
        yield ns

def test_ns(namespace):
    print(namespace.name)

生成器遍历资源,如下所示:

>>> from ocp_resources.node import Node
>>> from ocp_utilities.infra import get_client
>>> admin_client = get_client()
# This returns a generator
>>> for node in Node.get(dyn_client=admin_client): 
        print(node.name)

ip-10-0-128-213.ec2.internal

开源社区的开源代码

套用一句流行的说法,“如果你热爱你的代码,就应该让它自由。” openshift-python-wrapper 项目最初是作为 OpenShift 虚拟化 的实用模块。随着越来越多的项目从代码中受益,我们决定将这些程序提取到一个单独的仓库中并将其开源。套用另一句俗语,“如果代码不回到你这里,那就意味着它从未属于你。” 一旦这种情况发生,它就真正成为了开源。

更多的贡献者和维护者意味着代码属于社区。欢迎大家贡献。

(题图:MJ/5ca32a4a-2194-4b36-ade9-053433e79201)


via: https://opensource.com/article/23/4/cluster-open-source-python-api-wrapper

作者:Ruth Netser 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

为了使复杂的摄像头设置标准化,你可以对 Linux 文件系统中摄像头的位置分配施加一些特殊规则。

 title=

如果在 Linux 上用多个摄像头 使用 OBS 进行直播,你可能会注意到摄像头会在开机时按照它们被检测到的顺序加载。通常情况下你不需要特别在意,但如果你有一个固定的直播设置和复杂的 OBS 模板,你需要知道现实世界中哪个摄像头将会显示在虚拟世界的哪个屏幕上。换句话说,你不希望今天将一个设备分配为“摄像头 A”,而明天它却成为“摄像头 B”。

为了使复杂的摄像头设置标准化,你可以对 Linux 文件系统中摄像头的位置分配施加一些特殊规则。

udev 子系统

在 Linux 上处理硬件外设的系统称为 udev。它检测和管理你接入计算机的所有设备。你可能没有意识到它的存在,因为它不会吸引太多注意力。尽管当你插入 USB 闪存驱动器以在桌面上打开它或连接打印机时,你肯定与它交互过。

硬件检测

假设你有两个 USB 摄像头:一个在电脑左侧,另一个在右侧。左侧摄像头拍摄近景,右侧摄像头拍摄远景,并且在直播过程中你需要切换两个摄像头。在 OBS 中,你将每个摄像头添加到 Sources 面板中,并直观地将它们命名为 “camLEFT” 和 “camRIGHT”。

设想一种最坏的场景,你有两个 相同的 摄像头:它们是同一品牌、同一型号。这是最坏的情况,因为当两个硬件设备完全相同时,它们几乎不可能有任何独特的 ID,以便你的电脑能够将它们区分开来。

不过,这个难题有解决办法,只需要一些简单的终端命令进行一些调查。

1、获取厂商和产品 ID

首先,将一个摄像头插入你想要它分配到的 USB 端口。然后输入以下命令:

$ lsusb
Bus 006 Device 002: ID 0951:1666 Kingston Technology DataTraveler G4
Bus 005 Device 003: ID 03f0:3817 Hewlett-Packard LaserJet P2015 series
Bus 003 Device 006: ID 045e:0779 Microsoft Corp. LifeCam HD-3000
Bus 003 Device 002: ID 8087:0025 Intel Corp. 
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 046d:c216 Logitech, Inc. Dual Action Gamepad
Bus 001 Device 002: ID 048d:5702 Integrated Technology Express, Inc. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
[...]

你通常可以专门搜索字符串 cam 以缩小结果范围,因为大多数(但不是所有)摄像头都会报告为 “camera”。

$ lsusb | grep -i cam
Bus 003 Device 006: ID 045e:0779 Microsoft Corp. LifeCam HD-3000

这里有很多信息。ID 被列为 045e:0779。第一个数字是供应商 ID,第二个数字是产品 ID。把它们写下来,因为稍后你会需要它们。

2、获取 USB 标识符

你还获取了摄像头的设备路径:总线 3,设备 6。在 Linux 中有一句话:“一切皆文件”,实际上,USB 设备被描述为以 /dev/bus/usb/ 开始,以总线(本例中为 003)和设备(本例中为 006)结尾的文件路径。查看 lsusb 输出中的总线和设备号。它们告诉你该摄像头位于 /dev/bus/usb/003/006

你可以使用 udevadm 命令获取此 USB 设备的内核代号:

$ sudo udevadm info --attribute-walk /dev/bus/usb/003/006 | grep "KERNEL="

   KERNEL=="3-6.2.1"

这个例子中的内核 USB 标识符是 3-6.2.1。把你系统中的标识符记下来,因为之后也会用到它。

3、为每个摄像头重复该过程

将另一个摄像头(如果你有多个摄像头,则为每个摄像头)连接到要分配给它的 USB 端口。这与你用于另一个摄像头的 USB 端口是不同的!

重复该过程,获取供应商和产品 ID(如果摄像头是相同的品牌和型号,则应与第一个摄像头相同)以及内核 USB 标识符。

$ lsusb | grep -i cam
Bus 001 Device 004: ID 045e:0779 Microsoft Corp. LifeCam HD-3000
$ sudo udevadm info --attribute-walk dev/bus/usb/001/004 | grep "KERNEL="

   KERNEL=="1-6"

在这个例子中,我已经确定我的摄像头连接到了 1-6 和 3-6.2.1(第一个是我的机器上的 USB 端口,另一个是插在我的机器上的显示器插口的集线器,这就是为什么一个比另一个更复杂的原因)。

编写一个 udev 规则

你已经有了所需的一切,因此现在可以编写一个规则,告诉 udev 在特定的 USB 端口找到一个摄像头时给它一个一致的标识符。

创建并打开一个名为 /etc/udev/rules.d/50-camera.conf 的文件,并输入这两个规则,使用适合你自己系统的厂商和产品 ID 和内核标识符:

SUBSYSTEM=="usb", KERNEL=="1-6", ATTR{idVendor}=="045e", ATTR{idProduct}=="0779", SYMLINK+="video100"

SUBSYSTEM=="usb", KERNEL=="3-6.2.1", ATTR{idVendor}=="045e", ATTR{idProduct}=="0779", SYMLINK+="video101"

这些规则告诉 udev,当在特定的 USB 位置找到与特定供应商和产品 ID 匹配的设备时,创建一个名为 video100video101 的符号链接(有时也称为“别名”)。符号链接大多是任意的。我使用较大的数字,这样它们就容易被发现,并且数字不能与现有设备冲突。如果实际上有超过 101 个摄像头连接到计算机上,请使用 video200video201 以确保安全(记得联系我!我很想了解 项目的情况)。

重启

重新启动计算机。你现在可以让摄像头保持连接在计算机上,但实际上这并不重要。一旦 udev 加载了规则,它就会遵循这些规则,无论设备是否在启动期间附加或稍后插入。

许多人说 Linux 从不需要重启,但是 udev 在引导期间加载其规则,而且此外,你想保证你的 udev 规则在重新启动时也起作用。

计算机重新启动后,请查看摄像头注册的 /dev/video 目录:

$ ls -1 /dev/video*
/dev/video0
/dev/video1
/dev/video100
/dev/video101
/dev/video2
/dev/video3

正如你所看到的,在 video100video101 有条目。今天,这些是指向 /dev/video2/dev/video3 的符号链接,但明天它们可能是指向 /dev/video1/dev/video2 或任何其他基于 Linux 检测和分配文件的组合。

 title=

你可以在 OBS 中使用这些符号链接,这样 camLEFT 始终是 camLEFT,camRIGHT 始终是 camRIGHT。

(题图:MJ/9bb70b6d-9f49-493a-8daf-5546d207781f)


via: https://opensource.com/article/22/1/cameras-usb-ports-obs

作者:Seth Kenlon 选题:lujun9972 译者:hanszhao80 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出