捕获并净化请求数据

捕获请求数据,了解你在前端犯了哪类错误

捕获并净化请求数据

有时,客户端代码中的错误并不像因为你的 JavaScript 代码完全无法运行而出现空白屏幕时那样明显。 有时,应用的问题在于:面对用户可能采取的某些操作,你向服务器发送的请求构造得不正确。 客户端代码可以运行,控制台里也没有任何 JS 错误,但你的后端其实并不喜欢你发送给它的内容。

使用 OpenReplay,你可以将客户端与服务器之间的通信作为标准会话回放的一部分进行捕获,并在之后进行审查。那么我们就来看看该怎么做,以及我们能从中获得什么样的好处。

为了本指南的目的,我创建了一个简单的 React 应用,它使用了 Bored API。这是一个非常简单的 API,它会根据一些参数返回一个随机的活动建议。 于是我创建了”I’m bored App”,它看起来是这样的:

你可以在 Netlify 上 在这里 查看它的在线版本;如果你想查看代码以便详细研究,它已完全公开在 GitHub 上。

这个应用由 2 个组件构成:SearchForm 组件,负责渲染那 2 个字段和按钮,并负责向 API 发送实际的请求。 而 Suggestion 组件只是把建议渲染在一个外观漂亮的框内。

我将聚焦于第一个组件,因为它是唯一一个使用 fetch 函数发送请求的组件。

请注意,这种技术同样适用于使用 Axios 执行的任何请求。

让我们快速看一下这个组件,以了解它在做什么。

这不是一个复杂的组件,但其中有一段对这个特定用例尤其相关,所以让我们快速看一下它。

import { Container, Col, Form, Row, Button } from 'react-bootstrap';

const SearchForm = ({setResult, fetcher}) => {
const getSomething = async (evt) => {
    evt.preventDefault()
    let form = evt.target

    const API_URL = "/api/activity?"
    let getParams = {}
    if(form.participants.value !== '') {
      getParams.participants = form.participants.value
    }

    if(form.priceRange.value !== '') {
      let prices = form.priceRange.value.split("_")
      getParams.minprice = prices[0]
      getParams.maxprice = prices[1]
    }

    let results = await fetcher(API_URL + new URLSearchParams(getParams), {
        mode: 'no-cors'
    })
    setResult(await results.json())

    return false
  }

    return (
        <Container>
        <Form onSubmit={getSomething}>
          <Row>
            <Col>
          <Form.Group controlId='participants' >
            <Form.Label>Participants</Form.Label>
            <Form.Control type='text' name="totalParticipants" placeholder='Leave empty if you dont care...'></Form.Control>
          </Form.Group>
          </Col>
            <Col>
          <Form.Group controlId='priceRangeId'>
            <Form.Label>Price range</Form.Label>
            <Form.Select name="priceRange" >
              <option value="" >Select one or leave empty if you dont care</option>
              <option value="0.0">Free</option>
              <option value="0.1_0.5">Cheap</option>
              <option value="0.6_1.0">Expensive</option>
            </Form.Select>
          </Form.Group>
          </Col>
          </Row>
          <Row className='m-3'>
            <Col>
            <Form.Group>
              <Button variant="primary" type="submit">Get me something!</Button>
            </Form.Group>
            </Col>
          </Row>
        </Form>
      </Container>
    )
}

export default SearchForm

请注意 getSomething 函数,大部分”魔法”都发生在这里。当表单的 submit 事件被触发时,该函数会被调用。 当这种情况发生时,函数会获取合成事件,其中关联的表单位于 target 属性内。我们只是从每个过滤器(输入字段和下拉框)中捕获值,然后用 fetch 函数执行请求。 请注意,URL 并没有直接指向 BoredAPI 的端点。这是因为,为了让请求能够正常工作而不会因为 CORS 限制被阻止,我在后端配置了一个代理,将所有从 /api 发出的请求重定向到实际的 API。

既然你已经看过代码了,那么让我们来看看如果你安装了 OpenReplay 的 tracker 而不带 fetch 插件,会得到什么。

使用 OpenReplay 进行常规数据捕获

Section titled 使用 OpenReplay 进行常规数据捕获

在这个例子中,我将使用该包的 NPM 版本;如果你不知道该怎么做,请查阅 文档,然后再回到这里。

这是默认情况下会话回放的界面。 请注意,在下半部分我已经选择了”Network”标签页,虽然它确实显示了正在发出的请求,但没有关于 它们的任何细节。即使你点击其中一个请求,你也只会得到能获得的最少细节:

那么我们能做什么呢?你可以通过 网络选项对象 启用对请求信息的捕获。 让我们来看看它。

在会话回放中捕获请求数据

Section titled 在会话回放中捕获请求数据

为此,我们要做的只是在实例化 tracker 时添加一个配置选项。 所以现在,当你写下 new tracker(...) 这一行时,你将添加一个新属性:

import Tracker from '@openreplay/tracker';

const tracker = new Tracker({
  projectKey: "<your project key>",
  network: {
    capturePayload: true //start capturing the payload of every request
  }
});

这就是我们需要做的全部;从现在起,每次你执行请求时,数据都会被 tracker 记录下来。 现在部署这个更改,测试应用,关闭标签页并等待几分钟。 会话应该很快就会出现,然后你就可以点击”play”按钮了。

检查客户端与服务器之间的通信

Section titled 检查客户端与服务器之间的通信

为了示例的目的,我们也来看看我在发布应用后开始注意到的一个问题。

请注意我在这种情况下得到的警告框:

作为编写这段代码的开发者,我知道该怎么测试它并理解 bug 在哪里。然而,作为一名用户,这个错误其实并没有告诉我太多信息,而且我可能无法以开发团队能够理解的方式来表达它。 所以,作为用户,我可以简单地向公司抱怨他们的应用无法工作,而你,作为负责该应用的开发者,可以查看我的会话,检查客户端发送的请求以及服务器返回的响应。

现在来看会话回放的界面。在 Network 标签页内,你可以看到我们一直在向外部 API 发出的请求。

现在我们要做的,就是找到我们收到错误响应的那一刻,并查看正在发出的请求。 很有可能你会在请求详情中看到问题所在。 就我们的情况而言,错误信息显示”Failed to query due to error in arguments”,意味着当我们在下拉框中选择”Free”选项时, 我们发送的并不是一个有效的请求。那么让我们来看看它的详情。

你看出问题了吗?让我来帮你:

没错,我把 undefined 作为 maxprice 属性的值发送了出去。我在自己的逻辑中完全忽略了这一点,而是在检查请求时才发现的。 诚然,既然我知道了问题所在,现在修复起来很容易,但多亏了这个流程,我本来就能够提交一份非常详细的错误报告,或者直接帮助开发者识别并解决问题,而无需自己测试和复现这个问题。

好的,让我们把这个例子再推进一点;假设我在这个请求中还需要用户的电话号码。我显然不需要,但请姑且陪我玩一会儿。

我会把这个字段添加到表单中,并更新代码以捕获该值并将其作为请求的一部分发送。

表单的 HTML 只是像这样添加一个新的 Col 元素:

<!-- previous code -->
<Col>
    <Form.Group controlId='phoneNumber'>
        <Form.Label>Phone Number</Form.Label>
        <Form.Control type='number' name="phoneNumber" placeholder='Enter your phone number here please'></Form.Control>
    </Form.Group>
</Col>
<!-- rest of the code -->

而要把这个字段的内容加入到实际的请求中,只需要一行代码:

getParams.phonenumber = form.phoneNumber.value

那么,如果我们使用这段新代码并用 OpenReplay 捕获一个会话,会发生什么呢? 嗯,有两件事:

  1. 你实际_观看_的回放会自动净化电话号码字段的内容,任何观看它的人都不会看到该内容。
  2. 然而,插件所捕获的请求信息却会显示该值。

下面的截图展示了我刚才所描述的情况:

在屏幕的右侧部分,你可以看到完整的电话号码。出现这种情况是因为:虽然普通的 tracker 能够理解电话号码字段是一个数字字段,但它不会捕获该字段的输入,以防该数字代表个人信息。但在请求这一侧,我们其实无法做出这样的假设,因为开发者可能用这些数据做了任何事情,甚至连参数名称也可能是任意的。 那么问题就来了:我们能用这个插件保护用户的隐私吗?

而答案,我很高兴地告诉大家:可以,我们能做到。

如果你回到本教程的开头,回到我配置网络选项的地方,你会发现我并没有提到任何关于净化的内容。 然而,作为这些选项的一部分,你可以指定一个用于净化数据的回调。这个回调接收一个单一的属性,其中同时包含请求对象和响应对象。然后你可以选择按你想要的任何方式编辑它们;它们不会影响实际的请求,但会改变数据在 OpenReplay 界面上的显示方式。

例如,假设我想修改”phonenumber”属性并去掉其中的数字,以避免泄露该信息。这可以这样实现:

const tracker = new Tracker({
  projectKey: "<your project id>",
  network: {
    capturePayload: true,
    sanitizer: (data) => { //we change the content of the "phonenumber" parameter from the url
      data.url = data.url.replace(/phonenumber=([0-9]+)/, "phonenumber=XXXXXX")
      return data
    }
    }
});

如你所见,这个更改很简单,我们只替换了这个属性中的数字,所以现在请求在我们的界面中看起来是这样的:

现在你的用户数据又安全了。

如果你想查看代码以详细研究这个例子,可以在 GitHub 上的这里 找到它。如果你在设置 Fetch 插件或 Tracker 本身时遇到任何问题,请通过我们的 Slack 社区 联系我们,直接向我们的开发者提问吧!