分类 技术 下的文章

该教程的完整源代码可以从 GitHub 上找到。

欢迎回到《OpenGL 与 Go 教程》!如果你还没有看过 第一节第二节),那就要回过头去看一看。

到目前为止,你应该懂得如何创建网格系统以及创建代表方格中每一个单元的格子阵列。现在可以开始把网格当作游戏面板实现 康威生命游戏 Conway's Game of Life

开始吧!

实现康威生命游戏

康威生命游戏的其中一个要点是所有 细胞 cell 必须同时基于当前细胞在面板中的状态确定下一个细胞的状态。也就是说如果细胞 (X=3,Y=4) 在计算过程中状态发生了改变,那么邻近的细胞 (X=4,Y=4) 必须基于 (X=3,Y=4) 的状态决定自己的状态变化,而不是基于自己现在的状态。简单的讲,这意味着我们必须遍历细胞,确定下一个细胞的状态,而在绘制之前不改变他们的当前状态,然后在下一次循环中我们将新状态应用到游戏里,依此循环往复。

为了完成这个功能,我们需要在 cell 结构体中添加两个布尔型变量:

type cell struct {
    drawable uint32

    alive     bool
    aliveNext bool

    x int
    y int
}

这里我们添加了 alivealiveNext,前一个是细胞当前的专题,后一个是经过计算后下一回合的状态。

现在添加两个函数,我们会用它们来确定 cell 的状态:

// checkState 函数决定下一次游戏循环时的 cell 状态
func (c *cell) checkState(cells [][]*cell) {
    c.alive = c.aliveNext
    c.aliveNext = c.alive

    liveCount := c.liveNeighbors(cells)
    if c.alive {
        // 1. 当任何一个存活的 cell 的附近少于 2 个存活的 cell 时,该 cell 将会消亡,就像人口过少所导致的结果一样
        if liveCount < 2 {
            c.aliveNext = false
        }

        // 2. 当任何一个存活的 cell 的附近有 2 至 3 个存活的 cell 时,该 cell 在下一代中仍然存活。
        if liveCount == 2 || liveCount == 3 {
            c.aliveNext = true
        }

        // 3. 当任何一个存活的 cell 的附近多于 3 个存活的 cell 时,该 cell 将会消亡,就像人口过多所导致的结果一样
        if liveCount > 3 {
            c.aliveNext = false
        }
    } else {
        // 4. 任何一个消亡的 cell 附近刚好有 3 个存活的 cell,该 cell 会变为存活的状态,就像重生一样。
        if liveCount == 3 {
            c.aliveNext = true
        }
    }
}

// liveNeighbors 函数返回当前 cell 附近存活的 cell 数
func (c *cell) liveNeighbors(cells [][]*cell) int {
    var liveCount int
    add := func(x, y int) {
        // If we're at an edge, check the other side of the board.
        if x == len(cells) {
            x = 0
        } else if x == -1 {
            x = len(cells) - 1
        }
        if y == len(cells[x]) {
            y = 0
        } else if y == -1 {
            y = len(cells[x]) - 1
        }

        if cells[x][y].alive {
            liveCount++
        }
    }

    add(c.x-1, c.y)   // To the left
    add(c.x+1, c.y)   // To the right
    add(c.x, c.y+1)   // up
    add(c.x, c.y-1)   // down
    add(c.x-1, c.y+1) // top-left
    add(c.x+1, c.y+1) // top-right
    add(c.x-1, c.y-1) // bottom-left
    add(c.x+1, c.y-1) // bottom-right

    return liveCount
}

checkState 中我们设置当前状态(alive) 等于我们最近迭代结果(aliveNext)。接下来我们计数邻居数量,并根据游戏的规则来决定 aliveNext 状态。该规则是比较清晰的,而且我们在上面的代码当中也有说明,所以这里不再赘述。

更加值得注意的是 liveNeighbors 函数里,我们返回的是当前处于存活(alive)状态的细胞的邻居个数。我们定义了一个叫做 add 的内嵌函数,它会对 XY 坐标做一些重复性的验证。它所做的事情是检查我们传递的数字是否超出了范围——比如说,如果细胞 (X=0,Y=5) 想要验证它左边的细胞,它就得验证面板另一边的细胞 (X=9,Y=5),Y 轴与之类似。

add 内嵌函数后面,我们给当前细胞附近的八个细胞分别调用 add 函数,示意如下:

[
    [-, -, -],
    [N, N, N],
    [N, C, N],
    [N, N, N],
    [-, -, -]
]

在该示意中,每一个叫做 N 的细胞是 C 的邻居。

现在是我们的 main 函数,这里我们执行核心游戏循环,调用每个细胞的 checkState 函数进行绘制:

func main() {
    ...

    for !window.ShouldClose() {
        for x := range cells {
            for _, c := range cells[x] {
                c.checkState(cells)
            }
        }

        draw(cells, window, program)
    }
}

现在我们的游戏逻辑全都设置好了,我们需要修改细胞绘制函数来跳过绘制不存活的细胞:

func (c *cell) draw() {
    if !c.alive {
            return
    }

    gl.BindVertexArray(c.drawable)
    gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
}

如果我们现在运行这个游戏,你将看到一个纯黑的屏幕,而不是我们辛苦工作后应该看到生命模拟。为什么呢?其实这正是模拟在工作。因为我们没有活着的细胞,所以就一个都不会绘制出来。

现在完善这个函数。回到 makeCells 函数,我们用 0.01.0 之间的一个随机数来设置游戏的初始状态。我们会定义一个大小为 0.15 的常量阈值,也就是说每个细胞都有 15% 的几率处于存活状态。

import (
    "math/rand"
    "time"
    ...
)

const (
    ...

    threshold = 0.15
)

func makeCells() [][]*cell {
    rand.Seed(time.Now().UnixNano())

    cells := make([][]*cell, rows, rows)
    for x := 0; x < rows; x++ {
        for y := 0; y < columns; y++ {
            c := newCell(x, y)

            c.alive = rand.Float64() < threshold
            c.aliveNext = c.alive

            cells[x] = append(cells[x], c)
        }
    }

    return cells
}

我们首先增加两个引入:随机(math/rand)和时间(time),并定义我们的常量阈值。然后在 makeCells 中我们使用当前时间作为随机种子,给每个游戏一个独特的起始状态。你也可也指定一个特定的种子值,来始终得到一个相同的游戏,这在你想重放某个有趣的模拟时很有用。

接下来在循环中,在用 newCell 函数创造一个新的细胞时,我们根据随机浮点数的大小设置它的存活状态,随机数在 0.01.0 之间,如果比阈值(0.15)小,就是存活状态。再次强调,这意味着每个细胞在开始时都有 15% 的几率是存活的。你可以修改数值大小,增加或者减少当前游戏中存活的细胞。我们还把 aliveNext 设成 alive 状态,否则在第一次迭代之后我们会发现一大片细胞消亡了,这是因为 aliveNext 将永远是 false

现在继续运行它,你很有可能看到细胞们一闪而过,但你却无法理解这是为什么。原因可能在于你的电脑太快了,在你能够看清楚之前就运行了(甚至完成了)模拟过程。

让我们降低游戏速度,在主循环中引入一个帧率(FPS)限制:

const (
    ...

    fps = 2
)

func main() {
    ...

    for !window.ShouldClose() {
        t := time.Now()

        for x := range cells {
            for _, c := range cells[x] {
                c.checkState(cells)
            }
        }

        if err := draw(prog, window, cells); err != nil {
            panic(err)
        }

        time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
    }
}

现在你能给看出一些图案了,尽管它变换的很慢。把 FPS 加到 10,把方格的尺寸加到 100x100,你就能看到更真实的模拟:

const (
    ...

    rows = 100
    columns = 100

    fps = 10

    ...
)

 “Conway's Game of Life” - 示例游戏

试着修改常量,看看它们是怎么影响模拟过程的 —— 这是你用 Go 语言写的第一个 OpenGL 程序,很酷吧?

进阶内容?

这是《OpenGL 与 Go 教程》的最后一节,但是这不意味着到此而止。这里有些新的挑战,能够增进你对 OpenGL (以及 Go)的理解。

  1. 给每个细胞一种不同的颜色。
  2. 让用户能够通过命令行参数指定格子尺寸、帧率、种子和阈值。在 GitHub 上的 github.com/KyleBanks/conways-gol 里你可以看到一个已经实现的程序。
  3. 把格子的形状变成其它更有意思的,比如六边形。
  4. 用颜色表示细胞的状态 —— 比如,在第一帧把存活状态的格子设成绿色,如果它们存活了超过三帧的时间,就变成黄色。
  5. 如果模拟过程结束了,就自动关闭窗口,也就是说所有细胞都消亡了,或者是最后两帧里没有格子的状态有改变。
  6. 将着色器源代码放到单独的文件中,而不是把它们用字符串的形式放在 Go 的源代码中。

总结

希望这篇教程对想要入门 OpenGL (或者是 Go)的人有所帮助!这很有趣,因此我也希望理解学习它也很有趣。

正如我所说的,OpenGL 可能是非常恐怖的,但只要你开始着手了就不会太差。你只用制定一个个可达成的小目标,然后享受每一次成功,因为尽管 OpenGL 不会总像它看上去的那么难,但也肯定有些难懂的东西。我发现,当遇到一个难于理解用 go-gl 生成的代码的 OpenGL 问题时,你总是可以参考一下在网上更流行的当作教程的 C 语言代码,这很有用。通常 C 语言和 Go 语言的唯一区别是在 Go 中,gl 函数的前缀是 gl. 而不是 gl,常量的前缀是 gl 而不是 GL_。这可以极大地增加了你的绘制知识!

该教程的完整源代码可从 GitHub 上获得。

回顾

这是 main.go 文件最终的内容:

package main

import (
    "fmt"
    "log"
    "math/rand"
    "runtime"
    "strings"
    "time"

    "github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
    "github.com/go-gl/glfw/v3.2/glfw"
)

const (
    width  = 500
    height = 500

    vertexShaderSource = `
        #version 410
        in vec3 vp;
        void main() {
            gl_Position = vec4(vp, 1.0);
        }
    ` + "\x00"

    fragmentShaderSource = `
        #version 410
        out vec4 frag_colour;
        void main() {
            frag_colour = vec4(1, 1, 1, 1.0);
        }
    ` + "\x00"

    rows    = 100
    columns = 100

    threshold = 0.15
    fps       = 10
)

var (
    square = []float32{
        -0.5, 0.5, 0,
        -0.5, -0.5, 0,
        0.5, -0.5, 0,

        -0.5, 0.5, 0,
        0.5, 0.5, 0,
        0.5, -0.5, 0,
    }
)

type cell struct {
    drawable uint32

    alive     bool
    aliveNext bool

    x int
    y int
}

func main() {
    runtime.LockOSThread()

    window := initGlfw()
    defer glfw.Terminate()
    program := initOpenGL()

    cells := makeCells()
    for !window.ShouldClose() {
        t := time.Now()

        for x := range cells {
            for _, c := range cells[x] {
                c.checkState(cells)
            }
        }

        draw(cells, window, program)

        time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
    }
}

func draw(cells [][]*cell, window *glfw.Window, program uint32) {
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    gl.UseProgram(program)

    for x := range cells {
        for _, c := range cells[x] {
            c.draw()
        }
    }

    glfw.PollEvents()
    window.SwapBuffers()
}

func makeCells() [][]*cell {
    rand.Seed(time.Now().UnixNano())

    cells := make([][]*cell, rows, rows)
    for x := 0; x < rows; x++ {
        for y := 0; y < columns; y++ {
            c := newCell(x, y)

            c.alive = rand.Float64() < threshold
            c.aliveNext = c.alive

            cells[x] = append(cells[x], c)
        }
    }

    return cells
}
func newCell(x, y int) *cell {
    points := make([]float32, len(square), len(square))
    copy(points, square)

    for i := 0; i < len(points); i++ {
        var position float32
        var size float32
        switch i % 3 {
        case 0:
            size = 1.0 / float32(columns)
            position = float32(x) * size
        case 1:
            size = 1.0 / float32(rows)
            position = float32(y) * size
        default:
            continue
        }

        if points[i] < 0 {
            points[i] = (position * 2) - 1
        } else {
            points[i] = ((position + size) * 2) - 1
        }
    }

    return &cell{
        drawable: makeVao(points),

        x: x,
        y: y,
    }
}

func (c *cell) draw() {
    if !c.alive {
        return
    }

    gl.BindVertexArray(c.drawable)
    gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
}

// checkState 函数决定下一次游戏循环时的 cell 状态
func (c *cell) checkState(cells [][]*cell) {
    c.alive = c.aliveNext
    c.aliveNext = c.alive

    liveCount := c.liveNeighbors(cells)
    if c.alive {
        // 1. 当任何一个存活的 cell 的附近少于 2 个存活的 cell 时,该 cell 将会消亡,就像人口过少所导致的结果一样
        if liveCount < 2 {
            c.aliveNext = false
        }

        // 2. 当任何一个存活的 cell 的附近有 2 至 3 个存活的 cell 时,该 cell 在下一代中仍然存活。
        if liveCount == 2 || liveCount == 3 {
            c.aliveNext = true
        }

        // 3. 当任何一个存活的 cell 的附近多于 3 个存活的 cell 时,该 cell 将会消亡,就像人口过多所导致的结果一样
        if liveCount > 3 {
            c.aliveNext = false
        }
    } else {
        // 4. 任何一个消亡的 cell 附近刚好有 3 个存活的 cell,该 cell 会变为存活的状态,就像重生一样。
        if liveCount == 3 {
            c.aliveNext = true
        }
    }
}

// liveNeighbors 函数返回当前 cell 附近存活的 cell 数
func (c *cell) liveNeighbors(cells [][]*cell) int {
    var liveCount int
    add := func(x, y int) {
        // If we're at an edge, check the other side of the board.
        if x == len(cells) {
            x = 0
        } else if x == -1 {
            x = len(cells) - 1
        }
        if y == len(cells[x]) {
            y = 0
        } else if y == -1 {
            y = len(cells[x]) - 1
        }

        if cells[x][y].alive {
            liveCount++
        }
    }

    add(c.x-1, c.y)   // To the left
    add(c.x+1, c.y)   // To the right
    add(c.x, c.y+1)   // up
    add(c.x, c.y-1)   // down
    add(c.x-1, c.y+1) // top-left
    add(c.x+1, c.y+1) // top-right
    add(c.x-1, c.y-1) // bottom-left
    add(c.x+1, c.y-1) // bottom-right

    return liveCount
}

// initGlfw 初始化 glfw,返回一个可用的 Window
func initGlfw() *glfw.Window {
    if err := glfw.Init(); err != nil {
        panic(err)
    }
    glfw.WindowHint(glfw.Resizable, glfw.False)
    glfw.WindowHint(glfw.ContextVersionMajor, 4)
    glfw.WindowHint(glfw.ContextVersionMinor, 1)
    glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
    glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

    window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
    if err != nil {
        panic(err)
    }
    window.MakeContextCurrent()

    return window
}

// initOpenGL 初始化 OpenGL 并返回一个已经编译好的着色器程序
func initOpenGL() uint32 {
    if err := gl.Init(); err != nil {
        panic(err)
    }
    version := gl.GoStr(gl.GetString(gl.VERSION))
    log.Println("OpenGL version", version)

    vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
    if err != nil {
        panic(err)
    }

    fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
    if err != nil {
        panic(err)
    }

    prog := gl.CreateProgram()
    gl.AttachShader(prog, vertexShader)
    gl.AttachShader(prog, fragmentShader)
    gl.LinkProgram(prog)
    return prog
}

// makeVao 初始化并从提供的点里面返回一个顶点数组
func makeVao(points []float32) uint32 {
    var vbo uint32
    gl.GenBuffers(1, &vbo)
    gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
    gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)

    var vao uint32
    gl.GenVertexArrays(1, &vao)
    gl.BindVertexArray(vao)
    gl.EnableVertexAttribArray(0)
    gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
    gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)

    return vao
}

func compileShader(source string, shaderType uint32) (uint32, error) {
    shader := gl.CreateShader(shaderType)

    csources, free := gl.Strs(source)
    gl.ShaderSource(shader, 1, csources, nil)
    free()
    gl.CompileShader(shader)

    var status int32
    gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
    if status == gl.FALSE {
        var logLength int32
        gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)

        log := strings.Repeat("\x00", int(logLength+1))
        gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))

        return 0, fmt.Errorf("failed to compile %v: %v", source, log)
    }

    return shader, nil
}

请在 Twitter @kylewbanks 告诉我这篇文章对你是否有帮助,或者在 Twitter 下方关注我以便及时获取最新文章!


via: https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game

作者:kylewbanks 译者:GitFuture 校对:wxy

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

NIST 最近发表了四卷 SP800-63b 数字身份指南。除此之外,它还对密码提供三个重要的建议:

  1. 不要再纠结于复杂的密码规则。它们使密码难以记住。因为人为的复杂密码很难输入,因此增加了错误。它们也没有很大帮助。最好让人们使用密码短语。
  2. 停止密码到期。这是我们以前使用计算机的一个老的想法。如今不要让人改变密码,除非有泄密的迹象。
  3. 让人们使用密码管理器。这就是处理我们所有密码的方式。

这些密码规则不能让用户安全。让系统安全才是最重要的。


作者简介:

自从 2004 年以来,我一直在博客上写关于安全的文章,以及从 1998 年以来我的每月订阅中也有。我写书、文章和学术论文。目前我是 IBM Resilient 的首席技术官,哈佛伯克曼中心的研究员,EFF 的董事会成员。


via: https://www.schneier.com/blog/archives/2017/10/changes_in_pass.html

作者:Bruce Schneier 译者:geekpi 校对:wxy

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

去年,我为 Up 规划了一份蓝图,其中描述了如何以最小的成本在 AWS 上为大多数构建块创建一个很棒的无服务器环境。这篇文章则是讨论了 Up 的初始 alpha 版本。

为什么关注 无服务器 serverless ?对于初学者来说,它可以节省成本,因为你可以按需付费,且只为你使用的付费。无服务器方式是自愈的,因为每个请求被隔离并被视作“无状态的”。最后,它可以无限轻松地扩展 —— 没有机器或集群要管理。部署你的代码就行了。

大约一个月前,我决定开始在 apex/up 上开发它,并为动态 SVG 版本的 GitHub 用户投票功能写了第一个小型无服务器示例程序 tj/gh-polls。它运行良好,成本低于每月 1 美元即可为数百万次投票服务,因此我会继续这个项目,看看我是否可以提供开源版本及商业的变体版本。

其长期的目标是提供“你自己的 Heroku” 的版本,支持许多平台。虽然平台即服务(PaaS)并不新鲜,但无服务器生态系统正在使这种方案日益萎缩。据说,AWS 和其他的供应商经常由于他们提供的灵活性而被人诟病用户体验。Up 将复杂性抽象出来,同时为你提供一个几乎无需运维的解决方案。

安装

你可以使用以下命令安装 Up,查看这篇临时文档开始使用。或者如果你使用安装脚本,请下载二进制版本。(请记住,这个项目还在早期。)

curl -sfL https://raw.githubusercontent.com/apex/up/master/install.sh | sh

只需运行以下命令随时升级到最新版本:

up upgrade

你也可以通过 NPM 进行安装:

npm install -g up

功能

这个早期 alpha 版本提供什么功能?让我们来看看!请记住,Up 不是托管服务,因此你需要一个 AWS 帐户和 AWS 凭证。如果你对 AWS 不熟悉,你可能需要先停下来,直到熟悉流程。

我遇到的第一个问题是:up(1) 与 apex(1) 有何不同?Apex 专注于部署功能,用于管道和事件处理,而 Up 则侧重于应用程序、API 和静态站点,也就是单个可部署单元。Apex 不为你提供 API 网关、SSL 证书或 DNS,也不提供 URL 重写,脚本注入等。

单命令无服务器应用程序

Up 可以让你使用单条命令部署应用程序、API 和静态站点。要创建一个应用程序,你只需要一个文件,在 Node.js 的情况下,./app.js 监听由 Up 提供的 PORT'。请注意,如果你使用的是package.json,则会检测并使用startbuild` 脚本。

const http = require('http')
const { PORT = 3000 } = process.env
http.createServer((req, res) => {
  res.end('Hello World\n')
}).listen(PORT)

额外的运行时环境也支持开箱可用,例如用于 Golang 的 “main.go”,所以你可以在几秒钟内部署 Golang、Python、Crystal 或 Node.js 应用程序。

package main

import (
 "fmt"
 "log"
 "net/http"
 "os"
)

func main() {
 addr := ":" + os.Getenv("PORT")
 http.HandleFunc("/", hello)
 log.Fatal(http.ListenAndServe(addr, nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintln(w, "Hello World from Go")
}

要部署应用程序输入 up 来创建所需的资源并部署应用程序本身。这里没有模糊不清的地方,一旦它说“完成”了那就完成了,该应用程序立即可用 —— 没有远程构建过程。

后续的部署将会更快,因为栈已被配置:

使用 up url --open 测试你的程序,以在浏览器中浏览它,up url --copy 可以将 URL 保存到剪贴板,或者可以尝试使用 curl:

curl `up url`
Hello World

要删除应用程序及其资源,只需输入 up stack delete

例如,使用 up stagingup productionup url --open production 部署到预发布或生产环境。请注意,自定义域名尚不可用,它们将很快可用。之后,你还可以将版本“推广”到其他环境。

反向代理

Up 的一个独特的功能是,它不仅仅是简单地部署代码,它将一个 Golang 反向代理放在应用程序的前面。这提供了许多功能,如 URL 重写、重定向、脚本注入等等,我们将在后面进一步介绍。

基础设施即代码

在配置方面,Up 遵循现代最佳实践,因此对基础设施的更改都可以在部署之前预览,并且 IAM 策略的使用还可以限制开发人员访问以防止事故发生。一个附带的好处是它有助于自动记录你的基础设施。

以下是使用 Let's Encrypt 通过 AWS ACM 配置一些(虚拟)DNS 记录和免费 SSL 证书的示例。

{
  "name": "app",
  "dns": {
    "myapp.com": [
      {
        "name": "myapp.com",
        "type": "A",
        "ttl": 300,
        "value": ["35.161.83.243"]
      },
      {
        "name": "blog.myapp.com",
        "type": "CNAME",
        "ttl": 300,
        "value": ["34.209.172.67"]
      },
      {
        "name": "api.myapp.com",
        "type": "A",
        "ttl": 300,
        "value": ["54.187.185.18"]
      }
    ]
  },
  "certs": [
    {
      "domains": ["myapp.com", "*.myapp.com"]
    }
  ]
}

当你首次通过 up 部署应用程序时,需要所有的权限,它为你创建 API 网关、Lambda 函数、ACM 证书、Route53 DNS 记录等。

ChangeSets 尚未实现,但你能使用 up stack plan 预览进一步的更改,并使用 up stack apply 提交,这与 Terraform 非常相似。

详细信息请参阅配置文档

全球部署

regions 数组可以指定应用程序的目标区域。例如,如果你只对单个地区感兴趣,请使用:

{
  "regions": ["us-west-2"]
}

如果你的客户集中在北美,你可能需要使用美国和加拿大所有地区:

{
  "regions": ["us-*", "ca-*"]
}

最后,你可以使用 AWS 目前支持的所有 14 个地区:

{
  "regions": ["*"]
}

多区域支持仍然是一个正在进行的工作,因为需要一些新的 AWS 功能来将它们结合在一起。

静态文件服务

Up 默认支持静态文件服务,并带有 HTTP 缓存支持,因此你可以在应用程序前使用 CloudFront 或任何其他 CDN 来大大减少延迟。

typestatic 时,默认情况下的工作目录是 .,但是你也可以提供一个 static.dir

{
  "name": "app",
  "type": "static",
  "static": {
    "dir": "public"
  }
}

构建钩子

构建钩子允许你在部署或执行其他操作时定义自定义操作。一个常见的例子是使用 Webpack 或 Browserify 捆绑 Node.js 应用程序,这大大减少了文件大小,因为 node 模块是很大的。

{
  "name": "app",
  "hooks": {
    "build": "browserify --node server.js > app.js",
    "clean": "rm app.js"
  }
}

脚本和样式表插入

Up 允许你插入脚本和样式,无论是内联方式或声明路径。它甚至支持一些“罐头”脚本,用于 Google Analytics(分析)和 Segment,只需复制并粘贴你的写入密钥即可。

{
  "name": "site",
  "type": "static",
  "inject": {
    "head": [
      {
        "type": "segment",
        "value": "API_KEY"
      },
      {
        "type": "inline style",
        "file": "/css/primer.css"
      }
    ],
    "body": [
      {
        "type": "script",
        "value": "/app.js"
      }
    ]
  }
}

重写和重定向

Up 通过 redirects 对象支持重定向和 URL 重写,该对象将路径模式映射到新位置。如果省略 status 参数(或值为 200),那么它是重写,否则是重定向。

{
  "name": "app",
  "type": "static",
  "redirects": {
    "/blog": {
      "location": "https://blog.apex.sh/",
      "status": 301
    },
    "/docs/:section/guides/:guide": {
      "location": "/help/:section/:guide",
      "status": 302
    },
    "/store/*": {
      "location": "/shop/:splat"
    }
  }
}

用于重写的常见情况是 SPA(单页面应用程序),你希望不管路径如何都提供 index.html,当然除非文件存在。

{
  "name": "app",
  "type": "static",
  "redirects": {
    "/*": {
      "location": "/",
      "status": 200
    }
  }
}

如果要强制实施该规则,无论文件是否存在,只需添加 "force": true

环境变量

密码将在下一个版本中有,但是现在支持纯文本环境变量:

{
  "name": "api",
  "environment": {
    "API_FEATURE_FOO": "1",
    "API_FEATURE_BAR": "0"
  }
}

CORS 支持

CORS 支持允许你指定哪些(如果有的话)域可以从浏览器访问你的 API。如果你希望允许任何网站访问你的 API,只需启用它:

{
  "cors": {
    "enable": true
  }
}

你还可以自定义访问,例如仅将 API 访问限制为你的前端或 SPA。

{
  "cors": {
    "allowed_origins": ["https://myapp.com"],
    "allowed_methods": ["HEAD", "GET", "POST", "PUT", "DELETE"],
    "allowed_headers": ["Content-Type", "Authorization"]
  }
}

日志

对于 $0.5/GB 的低价格,你可以使用 CloudWatch 日志进行结构化日志查询和跟踪。Up 实现了一种用于改进 CloudWatch 提供的自定义查询语言,专门用于查询结构化 JSON 日志。

你可以查询现有日志:

up logs

跟踪在线日志:

up logs -f

或者对其中任一个进行过滤,例如只显示耗时超过 5 毫秒的 200 个 GET/HEAD 请求:

up logs 'method in ("GET", "HEAD") status = 200 duration >= 5'

查询语言是非常灵活的,这里有更多来自于 up help logs 的例子

### 显示过去 5 分钟的日志
$ up logs

### 显示过去 30 分钟的日志
$ up logs -s 30m

### 显示过去 5 小时的日志
$ up logs -s 5h

### 实时显示日志
$ up logs -f

### 显示错误日志
$ up logs error

### 显示错误和致命错误日志
$ up logs 'error or fatal'

### 显示非 info 日志
$ up logs 'not info'

### 显示特定消息的日志
$ up logs 'message = "user login"'

### 显示超时 150ms 的 200 响应
$ up logs 'status = 200 duration > 150'

### 显示 4xx 和 5xx 响应
$ up logs 'status >= 400'

### 显示用户邮件包含 @apex.sh 的日志
$ up logs 'user.email contains "@apex.sh"'

### 显示用户邮件以 @apex.sh 结尾的日志
$ up logs 'user.email = "*@apex.sh"'

### 显示用户邮件以 tj@ 开始的日志
$ up logs 'user.email = "tj@*"'

### 显示路径 /tobi 和 /loki 下的错误日志
$ up logs 'error and (path = "/tobi" or path = "/loki")'

### 和上面一样,用 in 显示
$ up logs 'error and path in ("/tobi", "/loki")'

### 更复杂的查询方式
$ up logs 'method in ("POST", "PUT") ip = "207.*" status = 200 duration >= 50'

### 将 JSON 格式的错误日志发送给 jq 工具
$ up logs error | jq

请注意,and 关键字是暗含的,虽然你也可以使用它。

冷启动时间

这是 AWS Lambda 平台的特性,但冷启动时间通常远远低于 1 秒,在未来,我计划提供一个选项来保持它们在线。

配置验证

up config 命令输出解析后的配置,有默认值和推断的运行时设置 - 它也起到验证配置的双重目的,因为任何错误都会导致退出状态 > 0。

崩溃恢复

使用 Up 作为反向代理的另一个好处是执行崩溃恢复 —— 在崩溃后重新启动服务器,并在将错误的响应发送给客户端之前重新尝试该请求。

例如,假设你的 Node.js 程序由于间歇性数据库问题而导致未捕获的异常崩溃,Up 可以在响应客户端之前重试该请求。之后这个行为会更加可定制。

适合持续集成

很难说这是一个功能,但是感谢 Golang 相对较小和独立的二进制文件,你可以在一两秒中在 CI 中安装 Up。

HTTP/2

Up 通过 API 网关支持 HTTP/2,对于服务很多资源的应用和站点可以减少延迟。我将来会对许多平台进行更全面的测试,但是 Up 的延迟已经很好了:

错误页面

Up 提供了一个默认错误页面,如果你要提供支持电子邮件或调整颜色,你可以使用 error_pages 自定义。

{
  "name": "site",
  "type": "static",
  "error_pages": {
    "variables": {
      "support_email": "[email protected]",
      "color": "#228ae6"
    }
  }
}

默认情况下,它看上去像这样:

如果你想提供自定义模板,你可以创建以下一个或多个文件。特定文件优先。

  • error.html – 匹配任何 4xx 或 5xx
  • 5xx.html – 匹配任何 5xx 错误
  • 4xx.html – 匹配任何 4xx 错误
  • CODE.html – 匹配一个特定的代码,如 404.html

查看文档阅读更多有关模板的信息。

伸缩和成本

你已经做了这么多,但是 Up 怎么伸缩?目前,API 网关和 AWS 是目标平台,因此你无需进行任何更改即可扩展,只需部署代码即可完成。你只需按需支付实际使用的数量并且无需人工干预。

AWS 每月免费提供 1,000,000 个请求,但你可以使用 http://serverlesscalc.com 来插入预期流量。在未来 Up 将提供更多的平台,所以如果一个成本过高,你可以迁移到另一个!

未来

目前为止就这样了!它可能看起来不是很多,但它已经超过 10,000 行代码,并且我刚刚开始开发。看看这个问题队列,假设项目可持续发展,看看未来会有什么期待。

如果你发现这个免费版本有用,请考虑在 OpenCollective 上捐赠我,因为我没有任何工作。我将在短期内开发早期专业版,对早期用户有年费优惠。专业或企业版也将提供源码,因此可以进行内部修复和自定义。


via: https://medium.freecodecamp.org/up-b3db1ca930ee

作者:TJ Holowaychuk 译者:geekpi 校对:wxy

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

我们在 Sky Betting&Gaming 中使用 Redis 作为共享内存缓存,用于那些需要跨 API 服务器或者 Web 服务器鉴别令牌之类的操作。在 Core Tribe 内,它用来帮助处理日益庞大的登录数量,特别是在繁忙的时候,我们在一分钟内登录数量会超过 20,000 人。这在很大程度上适用于数据存放在大量服务器的情况下(在 SSO 令牌用于 70 台 Apache HTTPD 服务器的情况下)。我们最近着手升级 Redis 服务器,此升级旨在使用 Redis 3.2 提供的原生集群功能。这篇博客希望解释为什么我们要使用集群、我们遇到的问题以及我们的解决方案。

在开始阶段(或至少在升级之前)

我们的传统缓存中每个缓存都包括一对 Redis 服务器,使用 keepalive 确保始终有一个主节点监听 浮动 IP floating IP 地址。当出现问题时,这些服务器对需要很大的精力来进行管理,而故障模式有时是非常各种各样的。有时,只允许读取它所持有的数据,而不允许写入的从属节点却会得到浮动 IP 地址,这种问题是相对容易诊断的,但会让无论哪个程序试图使用该缓存时都很麻烦。

新的应用程序

因此,这种情况下,我们需要构建一个新的应用程序,一个使用 共享内存缓存 shared in-memory cache 的应用程序,但是我们不希望对该缓存进行迂回的故障切换过程。因此,我们的要求是共享的内存缓存,没有单点故障,可以使用尽可能少的人为干预来应对多种不同的故障模式,并且在事件恢复之后也能够在很少的人为干预下恢复,一个额外的要求是提高缓存的安全性,以减少数据泄露的范围(稍后再说)。当时 Redis Sentinel 看起来很有希望,并且有许多程序支持代理 Redis 连接,比如 twemproxy。这会导致还要安装其它很多组件,它应该有效,并且人际交互最少,但它复杂而需要运行大量的服务器和服务,并且相互通信。

将会有大量的应用服务器与 twemproxy 进行通信,这会将它们的调用路由到合适的 Redis 主节点,twemproxy 将从 sentinal 集群获取主节点的信息,它将控制哪台 Redis 实例是主,哪台是从。这个设置是复杂的,而且仍有单点故障,它依赖于 twemproxy 来处理分片,来连接到正确的 Redis 实例。它具有对应用程序透明的优点,所以我们可以在理论上做到将现有的应用程序转移到这个 Redis 配置,而不用改变应用程序。但是我们要从头开始构建一个应用程序,所以迁移应用程序不是一个必需条件。

幸运的是,这个时候,Redis 3.2 出来了,而且内置了原生集群,消除了对单一 sentinel 集群需要。

它有一个更简单的设置,但 twemproxy 不支持 Redis 集群分片,它能为你分片数据,但是如果尝试在与分片不一致的集群中这样做会导致问题。有参考的指南可以使其匹配,但是集群可以自动改变形式,并改变分片的设置方式。它仍然有单点故障。正是在这一点上,我将永远感谢我的一位同事发现了一个 Node.js 的 Redis 的集群发现驱动程序,让我们完全放弃了 twemproxy。

因此,我们能够自动分片数据,故障转移和故障恢复基本上是自动的。应用程序知道哪些节点存在,并且在写入数据时,如果写入错误的节点,集群将自动重定向该写入。这是被选的配置,这让我们共享的内存缓存相当健壮,可以没有干预地应付基本的故障模式。在测试期间,我们的确发现了一些缺陷。复制是在一个接一个节点的基础上进行的,因此如果我们丢失了一个主节点,那么它的从节点会成为一个单点故障,直到死去的节点恢复服务,也只有主节点对集群健康投票,所以如果我们一下失去太多主节点,那么集群无法自我恢复。但这比我们过去的好。

向前进

随着使用集群 Redis 配置的新程序,我们对于老式 Redis 实例的状态变得越来越不适应,但是新程序与现有程序的规模并不相同(超过 30GB 的内存专用于我们最大的老式 Redis 实例数据库)。因此,随着 Redis 集群在底层得到了证实,我们决定迁移老式的 Redis 实例到新的 Redis 集群中。

由于我们有一个原生支持 Redis 集群的 Node.js Redis 驱动程序,因此我们开始将 Node.js 程序迁移到 Redis 集群。但是,如何将数十亿字节的数据从一个地方移动到另一个地方,而不会造成重大问题?特别是考虑到这些数据是认证令牌,所以如果它们错了,我们的终端用户将会被登出。一个选择是要求网站完全下线,将所有内容都指向新的 Redis 群集,并将数据迁移到其中,以希望获得最佳效果。另一个选择是切换到新集群,并强制所有用户再次登录。由于显而易见的原因,这些都不是非常合适的。我们决定采取的替代方法是将数据同时写入老式 Redis 实例和正在替换它的集群,同时随着时间的推移,我们将逐渐更多地向该集群读取。由于数据的有效期有限(令牌在几个小时后到期),这种方法可以导致零停机,并且不会有数据丢失的风险。所以我们这么做了。迁移是成功的。

剩下的就是服务于我们的 PHP 代码(其中还有一个项目是有用的,其它的最终是没必要的)的 Redis 的实例了,我们在这过程中遇到了一个困难,实际上是两个。首先,也是最紧迫的是找到在 PHP 中使用的 Redis 集群发现驱动程序,还要是我们正在使用的 PHP 版本。这被证明是可行的,因为我们升级到了最新版本的 PHP。我们选择的驱动程序不喜欢使用 Redis 的授权方式,因此我们决定使用 Redis 集群作为一个额外的安全步骤 (我告诉你,这将有更多的安全性)。当我们用 Redis 集群替换每个老式 Redis 实例时,修复似乎很直接,将 Redis 授权关闭,这样它将会响应所有的请求。然而,这并不是真的,由于某些原因,Redis 集群不会接受来自 Web 服务器的连接。 Redis 在版本 3 中引入的称为“保护模式”的新安全功能将在 Redis 绑定到任何接口时将停止监听来自外部 IP 地址的连接,并无需配置 Redis 授权密码。这被证明相当容易修复,但让我们保持警惕。

现在?

这就是我们现在的情况。我们已经迁移了我们的一些老式 Redis 实例,并且正在迁移其余的。我们通过这样做解决了我们的一些技术债务,并提高了我们的平台的稳定性。使用 Redis 集群,我们还可以扩展内存数据库并扩展它们。 Redis 是单线程的,所以只要在单个实例中留出更多的内存就会可以得到这么多的增长,而且我们已经紧跟在这个限制后面。我们期待着从新的集群中获得改进的性能,同时也为我们提供了扩展和负载均衡的更多选择。

未来怎么样?

我们解决了一些技术性债务,这使我们的服务更容易支持,更加稳定。但这并不意味着这项工作完成了,Redis 4 似乎有一些我们可能想要研究的功能。而且 Redis 并不是我们使用的唯一软件。我们将继续努力改进平台,缩短处理技术债务的时间,但随着客户群体的扩大,我们力求提供更丰富的服务,我们总是会遇到需要改进的事情。下一个挑战可能与每分钟超过 20,000次 登录到超过 40,000 次甚至更高的扩展有关。


via: http://engineering.skybettingandgaming.com/2017/09/25/redis-2-to-redis-3/

作者:Craig Stewart 译者:geekpi 校对:wxy

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

Flashback 用于测试目的来模拟 HTTP 和 HTTPS 资源,如 Web 服务和 REST API。

在 LinkedIn,我们经常开发需要与第三方网站交互的 Web 应用程序。我们还采用自动测试,以确保我们的软件在发布到生产环境之前的质量。然而,测试只是在它可靠时才有用。

考虑到这一点,有外部依赖关系的测试是有很大的问题的,例如在第三方网站上。这些外部网站可能会没有通知地发生改变、遭受停机,或者由于互联网的不可靠性暂时无法访问。

如果我们的一个测试依赖于能够与第三方网站通信,那么任何故障的原因都很难确定。失败可能是因为 LinkedIn 的内部变更、第三方网站的维护人员进行的外部变更,或网络基础设施的问题。你可以想像,与第三方网站的交互可能会有很多失败的原因,因此你可能想要知道,我将如何处理这个问题?

好消息是有许多互联网模拟工具可以帮助。其中一个是 Betamax。它通过拦截 Web 应用程序发起的 HTTP 连接,之后进行重放的方式来工作。对于测试,Betamax 可以用以前记录的响应替换 HTTP 上的任何交互,它可以非常可靠地提供这个服务。

最初,我们选择在 LinkedIn 的自动化测试中使用 Betamax。它工作得很好,但我们遇到了一些问题:

  • 出于安全考虑,我们的测试环境没有接入互联网。然而,与大多数代理一样,Betamax 需要 Internet 连接才能正常运行。
  • 我们有许多需要使用身份验证协议的情况,例如 OAuth 和 OpenId。其中一些协议需要通过 HTTP 进行复杂的交互。为了模拟它们,我们需要一个复杂的模型来捕获和重放请求。

为了应对这些挑战,我们决定基于 Betamax 的思路,构建我们自己的互联网模拟工具,名为 Flashback。我们也很自豪地宣布 Flashback 现在是开源的。

什么是 Flashback?

Flashback 用于测试目的来模拟 HTTP 和 HTTPS 资源,如 Web 服务和 REST API。它记录 HTTP/HTTPS 请求并重放以前记录的 HTTP 事务 - 我们称之为“ 场景 scene ”,这样就不需要连接到 Internet 才能完成测试。

Flashback 也可以根据请求的部分匹配重放场景。它使用的是“匹配规则”。匹配规则将传入请求与先前记录的请求相关联,然后将其用于生成响应。例如,以下代码片段实现了一个基本匹配规则,其中测试方法“匹配”此 URL的传入请求。

HTTP 请求通常包含 URL、方法、标头和正文。Flashback 允许为这些组件的任意组合定义匹配规则。Flashback 还允许用户向 URL 查询参数,标头和正文添加白名单或黑名单标签。

例如,在 OAuth 授权流程中,请求查询参数可能如下所示:

oauth_consumer_key="jskdjfljsdklfjlsjdfs",
oauth_nonce="ajskldfjalksjdflkajsdlfjasldfja;lsdkj",
oauth_signature="asdfjaklsdjflasjdflkajsdklf",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="asdjfkasjdlfajsdklfjalsdjfalksdjflajsdlfa",
oauth_version="1.0"

这些值许多将随着每个请求而改变,因为 OAuth 要求客户端每次为 oauth_nonce 生成一个新值。在我们的测试中,我们需要验证 oauth_consumer_keyoauth_signature_methodoauth_version 的值,同时确保 oauth_nonceoauth_signatureoauth_timestampoauth_token 存在于请求中。Flashback 使我们有能力创建我们自己的匹配规则来实现这一目标。此功能允许我们测试随时间变化的数据、签名、令牌等的请求,而客户端没有任何更改。

这种灵活的匹配和在不连接互联网的情况下运行的功能是 Flashback 与其他模拟解决方案不同的特性。其他一些显著特点包括:

  • Flashback 是一种跨平台和跨语言解决方案,能够测试 JVM(Java虚拟机)和非 JVM(C++、Python 等)应用程序。
  • Flashback 可以随时生成 SSL/TLS 证书,以模拟 HTTPS 请求的安全通道。

如何记录 HTTP 事务

使用 Flashback 记录 HTTP 事务以便稍后重放是一个比较简单的过程。在我们深入了解流程之前,我们首先列出一些术语:

  • Scene :场景存储以前记录的 HTTP 事务 (以 JSON 格式),它可以在以后重放。例如,这里是一个Flashback 场景示例。
  • Root Path :根路径是包含 Flashback 场景数据的目录的文件路径。
  • Scene Name :场景名称是给定场景的名称。
  • Scene Mode :场景模式是使用场景的模式, 即“录制”或“重放”。
  • Match Rule :匹配规则确定传入的客户端请求是否与给定场景的内容匹配的规则。
  • Flashback Proxy :Flashback 代理是一个 HTTP 代理,共有录制和重放两种操作模式。
  • HostPort :代理主机和端口。

为了录制场景,你必须向目的地址发出真实的外部请求,然后 HTTPS 请求和响应将使用你指定的匹配规则存储在场景中。在录制时,Flashback 的行为与典型的 MITM(中间人)代理完全相同 - 只有在重放模式下,连接流和数据流仅限于客户端和代理之间。

要实际看下 Flashback,让我们创建一个场景,通过执行以下操作捕获与 example.org 的交互:

1、 取回 Flashback 的源码:

git clone https://github.com/linkedin/flashback.git

2、 启动 Flashback 管理服务器:

./startAdminServer.sh -port 1234

3、 注意上面的 Flashback 将在本地端口 5555 上启动录制模式。匹配规则需要完全匹配(匹配 HTTP 正文、标题和 URL)。场景将存储在 /tmp/test1 下。

4、 Flashback 现在可以记录了,所以用它来代理对 example.org 的请求:

curl http://www.example.org -x localhost:5555 -X GET

5、 Flashback 可以(可选)在一个记录中记录多个请求。要完成录制,关闭 Flashback

6、 要验证已记录的内容,我们可以在输出目录(/tmp/test1)中查看场景的内容。它应该包含以下内容

在 Java 代码中使用 Flashback也很容易。

如何重放 HTTP 事务

要重放先前存储的场景,请使用与录制时使用的相同的基本设置。唯一的区别是将“场景模式”设置为上述步骤 3 中的“播放”

验证响应来自场景而不是外部源的一种方法,是在你执行步骤 1 到 6 时临时禁用 Internet 连接。另一种方法是修改场景文件,看看响应是否与文件中的相同。

这是 Java 中的一个例子

如何记录并重播 HTTPS 事务

使用 Flashback 记录并重放 HTTPS 事务的过程非常类似于 HTTP 事务的过程。但是,需要特别注意用于 HTTPS SSL 组件的安全证书。为了使 Flashback 作为 MITM 代理,必须创建证书颁发机构(CA)证书。在客户端和 Flashback 之间创建安全通道时将使用此证书,并允许 Flashback 检查其代理的 HTTPS 请求中的数据。然后将此证书存储为受信任的源,以便客户端在进行调用时能够对 Flashback 进行身份验证。有关如何创建证书的说明,有很多类似这样的资源是非常有帮助的。大多数公司都有自己的管理和获取证书的内部策略 - 请务必用你们自己的方法。

这里值得一提的是,Flashback 仅用于测试目的。你可以随时随地将 Flashback 与你的服务集成在一起,但需要注意的是,Flashback 的记录功能将需要存储所有的数据,然后在重放模式下使用它。我们建议你特别注意确保不会无意中记录或存储敏感成员数据。任何可能违反贵公司数据保护或隐私政策的行为都是你的责任。

一旦涉及安全证书,HTTP 和 HTTPS 之间在记录设置方面的唯一区别是添加了一些其他参数。

  • RootCertificateInputStream: 表示 CA 证书文件路径或流。
  • RootCertificatePassphrase: 为 CA 证书创建的密码。
  • CertificateAuthority: CA 证书的属性

查看 Flashback 中用于记录 HTTPS 事务的代码,它包括上述条目。

用 Flashback 重放 HTTPS 事务的过程与录制相同。唯一的区别是场景模式设置为“播放”。这在此代码中演示。

支持动态修改

为了测试灵活性,Flashback 允许你动态地更改场景和匹配规则。动态更改场景允许使用不同的响应(如 successtime_outrate_limit 等)测试相同的请求。场景更改仅适用于我们已经 POST 更新外部资源的场景。以下图为例。

 title=

能够动态更改匹配规则可以使我们测试复杂的场景。例如,我们有一个使用情况,要求我们测试 Twitter 的公共和私有资源的 HTTP 调用。对于公共资源,HTTP 请求是不变的,所以我们可以使用 “MatchAll” 规则。然而,对于私人资源,我们需要使用 OAuth 消费者密码和 OAuth 访问令牌来签名请求。这些请求包含大量具有不可预测值的参数,因此静态 MatchAll 规则将无法正常工作。

使用案例

在 LinkedIn,Flashback 主要用于在集成测试中模拟不同的互联网提供商,如下图所示。第一张图展示了 LinkedIn 生产数据中心内的一个内部服务,通过代理层,与互联网提供商(如 Google)进行交互。我们想在测试环境中测试这个内部服务。

 title=

第二和第三张图表展示了我们如何在不同的环境中录制和重放场景。记录发生在我们的开发环境中,用户在代理启动的同一端口上启动 Flashback。从内部服务到提供商的所有外部请求将通过 Flashback 而不是我们的代理层。在必要场景得到记录后,我们可以将其部署到我们的测试环境中。

 title=

在测试环境(隔离并且没有 Internet 访问)中,Flashback 在与开发环境相同的端口上启动。所有 HTTP 请求仍然来自内部服务,但响应将来自 Flashback 而不是 Internet 提供商。

 title=

未来方向

我们希望将来可以支持非 HTTP 协议(如 FTP 或 JDBC),甚至可以让用户使用 MITM 代理框架来自行注入自己的定制协议。我们将继续改进 Flashback 设置 API,使其更容易支持非 Java 语言。

现在为一个开源项目

我们很幸运能够在 GTAC 2015 上发布 Flashback。在展会上,有几名观众询问是否将 Flashback 作为开源项目发布,以便他们可以将其用于自己的测试工作。

Google TechTalks:GATC 2015 - 模拟互联网

我们很高兴地宣布,Flashback 现在以 BSD 两句版许可证开源。要开始使用,请访问 Flashback GitHub 仓库

该文原始发表在LinkedIn 工程博客上。获得转载许可

致谢

Flashback 由 Shangshang FengYabin KangDan Vinegrad 创建,并受到 Betamax 启发。特别感谢 Hwansoo LeeEran LeshemKunal KandekarKeith DsouzaKang Wang 帮助审阅代码。同样感谢我们的管理层 - Byron MaYaz ShimizuYuliya AverbukhChristopher HazlettBrandon Duncan - 感谢他们在开发和开源 Flashback 中的支持。


作者简介:

Shangshang Feng - Shangshang 是 LinkedIn 纽约市办公室的高级软件工程师。在 LinkedIn 他从事了三年半的网关平台工作。在加入 LinkedIn 之前,他曾在 Thomson Reuters 和 ViewTrade 证券的基础设施团队工作。


via: https://opensource.com/article/17/4/flashback-internet-mocking-tool

作者: Shangshang Feng 译者:geekpi 校对:jasminepeng

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

本教程介绍如何在一个 U 盘上安装多个 Linux 发行版。这样,你可以在单个 U 盘上享受多个 现场版 live Linux 发行版了。

我喜欢通过 U 盘尝试不同的 Linux 发行版。它让我可以在真实的硬件上测试操作系统,而不是虚拟化的环境中。此外,我可以将 USB 插入任何系统(比如 Windows 系统),做任何我想要的事情,以及享受相同的 Linux 体验。而且,如果我的系统出现问题,我可以使用 U 盘恢复!

创建单个可启动的现场版 Linux USB 很简单,你只需下载一个 ISO 文件并将其刻录到 U 盘。但是,如果你想尝试多个 Linux 发行版呢?你可以使用多个 U 盘,也可以覆盖同一个 U 盘以尝试其他 Linux 发行版。但这两种方法都不是很方便。

那么,有没有在单个 U 盘上安装多个 Linux 发行版的方式呢?我们将在本教程中看到如何做到这一点。

如何创建有多个 Linux 发行版的可启动 USB

How to install multiple linux distributions on a single USB

我们有一个工具正好可以做到在单个 U 盘上保留多个 Linux 发行版。你所需要做的只是选择要安装的发行版。在本教程中,我们将介绍如何在 U 盘中安装多个 Linux 发行版用于 现场会话 live session

要确保你有一个足够大的 U 盘,以便在它上面安装多个 Linux 发行版,一个 8 GB 的 U 盘应该足够用于三四个 Linux 发行版。

步骤 1

MultiBootUSB 是一个自由、开源的跨平台应用程序,允许你创建具有多个 Linux 发行版的 U 盘。它还支持在任何时候卸载任何发行版,以便你回收驱动器上的空间用于另一个发行版。

下载 .deb 包并双击安装。

下载 MultiBootUSB

步骤 2

推荐的文件系统是 FAT32,因此在创建多引导 U 盘之前,请确保格式化 U 盘。

步骤 3

下载要安装的 Linux 发行版的 ISO 镜像。

步骤 4

完成这些后,启动 MultiBootUSB。

MultiBootUSB

主屏幕要求你选择 U 盘和你打算放到 U 盘上的 Linux 发行版镜像文件。

MultiBootUSB 支持 Ubuntu、Fedora 和 Debian 发行版的持久化,这意味着对 Linux 发行版的现场版本所做的更改将保存到 USB 上。

你可以通过拖动 MultiBootUSB 选项卡下的滑块来选择持久化大小。持久化为你提供了在运行时将更改保存到 U 盘的选项。

MultiBootUSB persistence storage

步骤 5

单击“安装发行版”选项并继续安装。在显示成功的安装消息之前,需要一些时间才能完成。

你现在可以在已安装部分中看到发行版了。对于另外的操作系统,重复该过程。这是我安装 Ubuntu 16.10 和 Fedora 24 后的样子。

MultiBootSystem with Ubuntu and Fedora

步骤 6

下次通过 USB 启动时,我可以选择任何一个发行版。

Boot Menu

只要你的 U 盘允许,你可以添加任意数量的发行版。要删除发行版,请从列表中选择它,然后单击卸载发行版。

最后的话

MultiBootUSB 真的很便于在 U 盘上安装多个 Linux 发行版。只需点击几下,我就有两个我最喜欢的操作系统的工作盘了,我可以在任何系统上启动它们。

如果你在安装或使用 MultiBootUSB 时遇到任何问题,请在评论中告诉我们。


via: https://itsfoss.com/multiple-linux-one-usb/

作者:Ambarish Kumar 译者:geekpi 校对:wxy

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