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
英文地址,請注明出處:http://en.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. 動態帶寬調節 五、實…