Vercel 在官网上提供了 Go 运行时相关的文档,但是描述的不够详细,下面用一个简单的例子介绍一下如何建立一个 Go + Gin 的 Serverless function,这个例子实现的是简单的 telegram bot api 转发功能。

api/index.go

首先为这个工程建立一个文件夹,其中的 api 文件夹中建立一个 index.go 文件。

package api

import (
    "net/http"
)

func Listen(w http.ResponseWriter, r *http.Request) {
    // serve http request
}

如上是最简单的一个 Go Serverless Function,其中包含一个函数,函数的名称无所谓,但参数必须是 (w http.ResponseWriter, r *http.Request),因为它实现的是 http.HandlerFunc

当 Vercel 的服务器需要处理请求,就会调用文件里的这个 http.HandlerFunc 去处理。

The entry point of this Runtime is a global matching .go files that export a function that implements the http.HandlerFunc signature.

Gin

如果想用 Gin 去处理 HTTP 请求,则需要作如下更改:

package api

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "strings"
)

var router *gin.Engine

var apiUrl = "https://api.telegram.org"

func init() {
    router = gin.Default()
    router.Any("/*path", func(context *gin.Context) {
        uri := context.Param("path")
        if !strings.Contains(uri, "bot") {
            context.String(http.StatusNotFound, "404 Not found")
            return
        }
        url := apiUrl + uri
        req, err := http.NewRequestWithContext(context, context.Request.Method, url, context.Request.Body)
        if err != nil {
            fmt.Println(err)
            context.String(http.StatusBadRequest, err.Error())
            return
        }
        req.Header = context.Request.Header
        req.PostForm = context.Request.PostForm
        req.Form = context.Request.Form
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            fmt.Println(err)
            context.String(http.StatusBadRequest, err.Error())
            return
        }
        context.DataFromReader(resp.StatusCode, resp.ContentLength, "application/json", resp.Body, nil)
    })
}

func Listen(w http.ResponseWriter, r *http.Request) {
    router.ServeHTTP(w, r)
}

这段代码在初始化函数 init 里定义了一个 Gin 服务器,内容是监听所有的路径,检查是否是 telegram bot api 的请求,如果是则转发到 telegram 官方服务器(因为 telegram 官方服务器因为某些原因无法直连)。

然后在 Listen 函数中,使用 router.ServeHTTP(w, r) 将请求直接交给 Gin 处理。

至于 init 函数中的代码,与正常 Gin 框架代码开发相同。

vercel.json

在工程根目录下建立 vercel.json 文件,它将告诉 Vercel 服务器如何接受和处理请求。

{
  "routes": [
    {
      "src": "/.*",
      "dest": "/api/router.go"
    }
  ]
}

这里直接匹配所有路径 /.*(路径匹配遵循 glob 模式匹配),全部转发到文件 /api/router.go 处理。

git

接下来,目录结构应该是类似下面的样子:

vercel-go-telegram-proxy$ tree
.
├── api
│   └── router.go
├── go.mod
├── go.sum
└── vercel.json

1 directory, 4 files
vercel-go-telegram-proxy$

接着,建立一个 Git Repo,无论是 GitHub 还是其它平台都可以,我这里上传到了 https://github.com/minoic/vercel-go-telegram-proxy ,如下图所示:

file

vercel

接下来,注册或登录 Vercel 账号,点击 Add New… -> Project,创建一个项目:

file

如果你绑定 GitHub 账号,接下来会在左边看到你刚刚建立的 Repo:

file

直接点击 Import,或手动输入 Git 链接。

file

接下来,Framework Preset 选择 Other,如果没有额外需求的话,就可以直接部署了,点击 Deploy。

局限

Vercel 的免费额度还是很慷慨的,不过如果你的业务比较复杂, 则不推荐在同一个 go 文件或 gin 实例下建立大量的路由和业务,可以寻求其它方式。

而且需要注意的是,如果你使用的请求需要很长时间来回复,那么免费额度很可能会不够用。例如使用 telegram 的请求 API 而不是 Webhook 来获取消息,这种请求会保持长连接在后台并等待消息,导致消耗 Vercel 的内存时间。降低单请求实例的内存分配大小,或缩短请求处理时间(业务尽量集中在等待 CPU 处理而不是等待 IO),以便解决内存时间额度不够用的问题。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注