Go和Elixir极简HTTP服务对比

Go 和 Elixir 都是我非常喜欢的编程语言,这次来对比下它们实现一个原生极简 HTTP 服务的过程。

Go 语言标准库自带了网络服务库,只需要简单几行代码就可以实现一个网络服务,这也是一开始它吸引我的一个方面。而 Elixir 标准库本身没有网络服务的库,而是通过 Plug 库提供了一套标准网络服务编写规范,虽然它不是标准库,但也是由官方开发和维护的。

新建工程

首先我们从新建工程开始。Go 并没有什么严格的工程管理工具和规范,我们只需要新建一个 .go 文件就可以开始编程了。Elixir 程序的运行方式就比较多样了,既可以做为脚本直接运行,也可以在交互式环境中运行,还可以编译成 beam 文件加载到 Beam 虚拟机运行。而且 Elixir 还提供了工程管理工具 Mix ,用来创建和运行工程,以及打包测试等。这里我们需要一个 OTP 应用,因此我们使用 mix 来创建工程。

GoElixir
新建 main.gomix new example --sup

对于 Elixir 来说,我们还需要在 mix.exs 中添加 plug_cowboy 依赖:

defp deps do[{:plug_cowboy, "~> 2.0"}]
end

然后运行 mix deps.get 来获取依赖。

处理器

Web 应用的关键是”处理器”,用来处理具体的 http 请求。

在 Go 语言中,处理器是一个接口:

type Handler interface {ServeHTTP(http.ResponseWriter, *http.Request)
}

我们只需要实现一个签名为 func(w http.ResponseWriter, r *http.Request) 的函数即可。

package mainimport "net/http"func main() {http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World from Go!\n"))},))
}

而在 Elixir 中,我们需要实现的是 Plug 行为,它包含以下两个函数:

@callback init(opts) :: opts
@callback call(conn :: Plug.Conn.t(), opts) :: Plug.Conn.t()

首先我们在 lib/example 目录下创建一个 hello_world_plug.ex 文件,然后定义一个模块来实现 Plug 行为。

defmodule Example.HelloWorldPlug doimport Plug.Conndef init(options), do: optionsdef call(conn, _opts) doPlugconn|> put_resp_content_type("text/plain")|> send_resp(200, "Hello World from Elixir!\n")end
end

然后在 application.exstart 函数中添加我们的应用。

def start(_type, _args) dochildren = [# Starts a worker by calling: Example.Worker.start_link(arg)# {Example.Worker, arg}{Plug.Cowboy, scheme: :http, plug: Example.HelloWorldPlug, options: [port: 8081]}]# See https://hexdocs.pm/elixir/Supervisor.html# for other strategies and supported optionsopts = [strategy: :one_for_one, name: Example.Supervisor]Supervisor.start_link(children, opts)end

最后运行 mix run --no-halt 启动应用即可。

可以看到在 Go 语言中,对于 HTTP 连接的读写分别由 http.ResponseWriterhttp.Request 承担,而在 Elixir 中则全部统一到了 Plug.Conn 结构体中。实际上这也是许多第三方 Go 语言 Web 框架的实现方式,它们通常叫做 Context

路由

路由器用来分别处理不同路径下的 HTTP 请求,也就是将不同路径的请求分发给不同的处理器,它本身本质上也是一个处理器。

在 Go 1.22 之前,标准库的路由器功能都还比较简单,只能匹配 HTTP 路径,不能匹配 HTTP 方法。直到 Go 1.22, http.ServeMux 终于迎来了升级,支持匹配 HTTP 方法,路径参数,通配符等。那些以前只能通过第三方库实现的功能,也能通过标准库实现了。以下是摘自官网的 Go 1.22 release note:

Enhanced routing patterns

HTTP routing in the standard library is now more expressive. The patterns used by net/http.ServeMux have been enhanced to accept methods and wildcards.

Registering a handler with a method, like "POST /items/create", restricts invocations of the handler to requests with the given method. A pattern with a method takes precedence over a matching pattern without one. As a special case, registering a handler with "GET" also registers it with "HEAD".

Wildcards in patterns, like /items/{id}, match segments of the URL path. The actual segment value may be accessed by calling the Request.PathValue method. A wildcard ending in “…”, like /files/{path...}, must occur at the end of a pattern and matches all the remaining segments.

A pattern that ends in “/” matches all paths that have it as a prefix, as always. To match the exact pattern including the trailing slash, end it with {$}, as in /exact/match/{$}.

If two patterns overlap in the requests that they match, then the more specific pattern takes precedence. If neither is more specific, the patterns conflict. This rule generalizes the original precedence rules and maintains the property that the order in which patterns are registered does not matter.

This change breaks backwards compatibility in small ways, some obvious—patterns with “{” and “}” behave differently— and some less so—treatment of escaped paths has been improved. The change is controlled by a GODEBUG field named httpmuxgo121. Set httpmuxgo121=1 to restore the old behavior.

为了保持兼容性, ServeMux 并没有提供诸如 Get(...)Post(...) 这样的方法,还是通过 HandleHandleFunc 来注册路由,只是将 HTTP 方法集成到了路径中。

目前 Go 的最新版本是 1.24,离 Go 1.22 也已过去了两个大版本,因此我们就以新版本为例来进行对比。

package mainimport "net/http"func main() {http.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World from Go!\n"))})http.ListenAndServe(":8080", nil)
}

对 Elixir 来说,路由器也是 Plug ,我们需要使用 use Plug.Router 来导入一些有用的宏。在 lib/example 目录下新建一个 router.ex 文件。

defmodule Example.Router douse Plug.Routerplug(:match)plug(:dispatch)get "/" dosend_resp(conn, 200, "Welcome!")endget("/hello", to: Example.HelloWorldPlug)match _ dosend_resp(conn, 404, "Oops!")end
end

这里我们首先用 plug(:match)plug(:dispatch) 插入两个内置的 Plug 用来匹配和分发路由。之后我们就可以使用 getpostmatch 等宏来编写处理函数了。除了直接用 :do 来书写处理程序,还可以通过 :to 来指定 Plug 。除了支持模块 Plug ,也可以是函数 Plug 。函数 Plug 是一个签名与 call 函数相同的函数。

最后我们把 application.ex 中的 start 函数中的 Plug 换成 Example.Router

def start(_type, _args) dochildren = [# Starts a worker by calling: Example.Worker.start_link(arg)# {Example.Worker, arg}# {Bandit, plug: Example.HelloWorldPlug, scheme: :http, port: 8080}{Plug.Cowboy, scheme: :http, plug: Example.Router, options: [port: 8081]}]# See https://hexdocs.pm/elixir/Supervisor.html# for other strategies and supported optionsopts = [strategy: :one_for_one, name: Example.Supervisor]Supervisor.start_link(children, opts)
end

Elixir 的路由十分灵活,表达能力也更强。

中间件

Go 的中间件是一个接收 http.HandlerFunc 并返回 http.HandlerFunc 的函数。比如我们实现一个记录日志的中间件。

func main() {http.HandleFunc("GET /hello", logHttp(func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World from Go!\n"))},))http.ListenAndServe(":8080", nil)
}func logHttp(handler http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {log.Printf("%s %s\n", r.Method, r.URL.Path)handler(w, r)}
}

Elixir 的中间件还是 Plug ,这里我们实现一个叫 log_http 的函数 Plug

defmodule Example.Router douse Plug.Routerplug(:match)plug(:dispatch)plug(:log_http)get "/" dosend_resp(conn, 200, "Welcome!")endget("/hello", to: Example.HelloWorldPlug)match _ dosend_resp(conn, 404, "Oops!")enddef log_http(conn, _opts) dorequire LoggerLogger.info("#{conn.method} #{conn.request_path}")connend
end

总结

以上就是原生极简 HTTP 服务在 Go 和 Elixir 中的实现。虽然 Elixir 的代码量更多,但是其功能和表现力也更强。Go 胜在简洁,但是过于简洁,相比于 Elixir,语言表现力还是差了一点。

如果要实现更庞大的 Web 应用,Go 有许多优秀的 Web 框架可供选择,比如 Gin,Echo等等,太多了。而 Elixir 则有鼎鼎大名的大杀器 Phoenix。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86552.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86552.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为何要学习Objective-C?从环境搭建开始

目录 前言 Swift时代为何还要学Objective-C? 开发环境搭建 1. 安装Xcode 2. 创建第一个Command Line Tool项目 初识Objective-C代码 编写"Hello, Objective-C!" 编译运行程序 为什么Objective-C中的NSLog和NSString前面都有"NS"前缀&a…

ubuntu18.04安装 gcc 9以及2019版本tbb

一、安装gcc 9 ubuntu18.04默认是用的gcc7.5 sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update sudo apt-get install gcc-9 g-9 下面是配置优先用哪个版本的gcc和g ,后面带的值越大越优先用谁,并且配置完全局生效不仅仅是在当…

JdbcUtils的三个版本以及sql注入问题

JDBC的工具类 1.0版本 JDBC的工具类 2.0版本(智能一些),编写properties属性文件,程序就可以读取属性文件 JDBC的工具类 3.0版本,加入连接池对象 我们封装jdbc工具类是为了减少代码重复,方便开发&#xff0…

AS32系列MCU芯片I2C模块性能解析与调试

国科安芯推出的AS32X601内置的I2C模块提供了符合工业标准的两线串行制接口,可用于MCU和外部IIC设备的通讯。IIC总线使用两条串行线:串行数据线SDA和串行时钟线SCL。 IIC接口模块实现了IIC协议的标准模式和快速模式,支持多主机IIC总线架构。其…

钉钉小程序开发实战:打造一个简约风格的登录页面

在上一篇文章中,我们已经介绍了如何搭建钉钉小程序的基础环境,并完成了项目的初始化配置。本文将继续深入,手把手带你实现一个简约风格的登录页面,这是大多数企业级应用不可或缺的一部分。 钉钉小程序基于前端 Web 技术栈&#x…

论文研读2-1:多GNSS双历元纯相位定位-模型建立与误差分析

后续文章: 论文研读2-2:多GNSS双历元纯相位定位-固定模糊度精度增益 论文研读2-3:多GNSS双历元纯相位定位-定位精度分析 仅相位定位中的模糊度解算问题 在卫星导航定位中,载波相位测量是实现高精度定位的基础,但如果仅使用相位测…

Python----OpenCV(图像増强——图像平滑、均值滤波、高斯滤波、中值滤波、双边滤波)

Python----计算机视觉处理(Opencv:图像噪点消除:滤波算法,噪点消除) 一、图像平滑 图像平滑处理(Smoothing Images),也称为图像模糊处理、图像滤波(Images Filtering&am…

笔记:使用EasyExcel导入csv文件出现编码问题,导致导入数据全为null的解决方法

笔记:使用EasyExcel导入csv文件出现编码问题,导致导入数据全为null的解决方法 通常情况下,我们使用excel导入,但是部分情况下或者领导要求,我们需要使用csv导入文件,但是csv文件模板下载之后会变成系统当前…

NL2SQL(Natural Language to SQL)优化之道:提升准确率与复杂查询能力

自然语言 → SQL 的转译(NL2SQL)技术,是让非技术用户与数据库“对话”的桥梁。而在实际应用中,我们不仅需要“能转”,更要“转得准、转得全、转得快”。 一、什么是 NL2SQL? NL2SQL(Natural La…

java中map的循环方式

什么是Map集合? Map是Java中的一个接口,它用于存储键-值对,并且键和值都可以是任意对象。它是Java集合框架中的一部分,并提供了一些方法来操作和访问Map中的元素。 Map中的每个键都是唯一的,这意味着不能使用相同的键…

python学习笔记(深度学习)

文章目录 1、概述2、学习内容2.1、pytorch 常见语法2.1.1、sum2.1.2、广播机制2.1.3、张量2.1.4、DataLoader 2.2、普通语法2.2.1、迭代器 1、概述 本篇博客用来记录,在深度学习过程中,常用的 python 语法内容 2、学习内容 2.1、pytorch 常见语法 2.…

力扣网C语言编程题:搜索二维矩阵(右上角->左下角解法)

一. 简介 上一篇文章关于"在二维数组中查找某个元素"的问题,提供了两种解题思路,文章如下: 力扣网C语言编程题:搜索二维矩阵的普通解法与二分查找法-CSDN博客 本文提供第三种解题思路:从左下角->右上角…

AI大模型流式输出,OkHttp Log拦截打印方案

背景: 使用okhttp框架进行网络访问时,一般会使用 HttpLoggingInterceptor 打印请求和响应的log。在使用okhttp访问AI大模型时,如果选择流式输出,那么响应的body数据使用的SSE技术,服务异步发送大模型生成的增量token&…

看数据世界的历史:全面梳理从关系库、大数据到AI时代的数据发展及展望

序章 在数据库不断发展的时代里,我们看到了关系型数据库(RDB)在一次次的数据演变过程中的占据王位,捍卫了胜利,像一个王朝更替下的“王权”的故事,精彩有趣。 本篇就来探讨下数据库的发展兴衰史&#xff0…

元宇宙与人工智能的融合:从虚拟世界到智能生态的IT新革命

文章目录 引言:前沿技术重塑数字交互体验一、元宇宙与AI融合的本质:虚拟空间与智能交互的交汇元宇宙赋能AI:AI赋能元宇宙: 二、元宇宙与AI融合的演进:从概念到产业热潮三、核心技术:元宇宙与AI融合的基石与…

问卷调查[mqtt dht]

任务 this code uses esp32-wroom-32 and dht11 to read the humidty and temperature, besieds, it will send the meassage to the cloud platform. All communication is conducted through MQTT. 打分标准 您应该对以下代码进行评级,并且必须遵守如…

swift 对象转Json

在 Swift 中将对象转换为 JSON 可以通过以下方法实现: 使用 Codable 协议 Swift 的 Codable 协议(Encodable 和 Decodable 的组合)是处理 JSON 编码和解码的推荐方式。 struct Person: Codable {var name: Stringvar age: Int }let person…

Python学习Day43

学习来源:浙大疏锦行 import torch import torch.nn as nn import torch.nn.functional as F import torchvision import torchvision.transforms as transforms import numpy as np import matplotlib.pyplot as plt from PIL import Image import os # 设置随机…

了解一下Unity AssetBundle 的几种加载方式

Unity 的 AssetBundle 系统提供了多种加载方式,以满足不同场景下的资源管理和性能需求。 同步加载(LoadFromFile) 同步加载使用 AssetBundle.LoadFromFile 方法从文件系统中直接加载 AssetBundle。这种方式会阻塞主线程,直到加载…

鸿蒙边缘智能计算架构实战:多线程图像采集与高可靠缓冲设计

目录 一、技术背景与挑战二、鸿蒙边缘计算架构的核心特性1. 分布式软总线:打破设备孤岛2. 轻量化多线程模型 三、多线程图像采集的稳定性设计1. 分层缓冲队列架构2. 线程优先级策略 四、边缘侧高可靠缓冲机制1. 基于分布式数据管理的容错设计2. 动态带宽调节 五、实…