对于 Web 应用,您的程序是一个运行在 Native 运行时中的 HTTP Server 服务。本文为您介绍本地开发该 HTTP Server 需要遵循的规范和方法。
在本地开发 HTTP Server 时需要遵循以下规范:
服务本身必须是无状态的
如需状态可存储至远端 redis 或 mysql,服务启动不依赖本地内存 cache 或落盘的数据。
启动命令
服务需要提供启动命令(后续在控制台 创建 Web 应用 时配置启动命令)。
注意
启动脚本必须具有相应的可执行权限,否则发布服务时会出现 permission denied 报错。可参考以下命令给 Linux 系统所有的用户组添加脚本的执行权限。
# 注意是在本地执行该命令 # 假设启动脚本为 run.sh chmod a+x run.sh
监听端口
服务需要监听端口。
function_start_timeout function process start timeout: function start timed out (120s) with command `./main --port 3000`, function may not listen on address *:8000, logs: current working directory: /opt/bytefaas Server listen on address: :3000. Please check your log(runtime_log)/code and then retry, or contact oncall
_FAAS_RUNTIME_PORT
来获取指定的监听端口。不鼓励后台进程
不鼓励服务启动后台进程或线程(即请求已经返回了 Response,但后台仍有任务异步执行)。函数服务根据请求量对后端实例进行动态扩缩容,无法保证后台进程、线程的存活性。
在 Native 运行时中,函数服务会将请求 Method、Path、Body、Query 以及 Headers 转发给您的 HTTP Server。您可以直接使用入参请求头(Headers)和请求体(Body)来编写函数的业务逻辑。
以 Golang 语言为例,一个完整的 HTTP Server 代码示例如下:
package main import ( "flag" "fmt" "log" "net/http" "os" "os/signal" "syscall" ) func hello(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("Hello World from VeFaaS")) } func main() { // 从命令行参数或者环境变量中获取监听端口 var port string flag.StringVar(&port, "port", "", "http server listen port") flag.Parse() if port == "" { port = os.Getenv("_FAAS_RUNTIME_PORT") } // 配置 HTTP Server 的请求处理 handler http.HandleFunc("/v1/hello", hello) addr := fmt.Sprintf("0.0.0.0:%s", port) srv := http.Server{Addr: addr} // 启动 HTTP Server go func() { log.Printf("Start HTTP Server at: %s", addr) if err := srv.ListenAndServe(); err != nil { log.Println("ListenAndServe: ", err) } }() // 监听 SIGINT, SIGTERM 信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit // 收到信号后进行优雅退出 log.Println("Shutting down server...") // 执行您的 Graceful Shutdown 逻辑 log.Println("Doing your graceful shutdown logic") if err := srv.Shutdown(nil); err != nil { log.Fatal("Server shutdown error: ", err) } log.Println("Server gracefully stopped") }
您可以通过命令行参数传递端口,或者通过环境变量 _FAAS_RUNTIME_PORT
来获取配置的监听端口(默认为 8000),以下代码展示了从命令参数以及环境变量中获取端口号的逻辑。
注意
服务监听的端口必须与您在控制台配置的监听端口相匹配。
var port string flag.StringVar(&port, "port", "", "http server listen port") flag.Parse() if port == "" { port = os.Getenv("_FAAS_RUNTIME_PORT") }
得到监听端口号 port 后,您需要保证启动的 HTTP Server 监听 0.0.0.0:port
,否则会导致部署失败。以下代码展示了拼接服务监听地址并启动 HTTP Server 的逻辑。
// 拼接 0.0.0.0:port 地址 addr := fmt.Sprintf("0.0.0.0:%s", port) srv := http.Server{Addr: addr} // 启动 HTTP Server go func() { log.Printf("Start HTTP Server at: %s", addr) if err := srv.ListenAndServe(); err != nil { log.Println("ListenAndServe: ", err) } }()
针对 Native 运行时的 HTTP Server 而言,由于您本身已经实现了一个 HTTP Server,因此您可以在 HTTP Server 的逻辑中对您的请求处理程序进行自定义处理。以下代码展示了简单的请求处理接口。
func hello(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("Hello World from VeFaaS")) }
以下代码展示了请求处理的路由。
// 配置 HTTP Server 的请求处理 handler http.HandleFunc("/v1/hello", hello)
在函数实例退出的时候,函数服务会给您的 HTTP Server 服务发送 SIGINT 信号,您可以捕获该信号并执行自定义的优雅退出逻辑。以下代码展示了从信号捕获到 HTTP Server 关闭的流程。
// 监听 SIGINT, SIGTERM 信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit // 收到信号后进行优雅退出 log.Println("Shutting down server...") // 执行您的 Graceful Shutdown 逻辑 log.Println("Doing your graceful shutdown logic") if err := srv.Shutdown(nil); err != nil { log.Fatal("Server shutdown error: ", err) } log.Println("Server gracefully stopped")
完成代码开发和编译后,您需要在控制台 创建 Web 应用 时配置启动命令与监听端口。下图假设服务二进制名为http-server
,监听端口为 3000。
您可以在控制台配置函数运行时所需的环境变量,并在代码中读取对应的环境变量,用于函数处理逻辑。对于所配置的环境变量,veFaaS 会将其注入到函数运行所在的容器中,程序可以通过语言的内置库进行读取。 例如:若您函数配置中环境变量的键(key)为 envKey
,运行环境读取该环境变量的代码示例如下。
import "os" envValue := os.Getenv("envKey")