完善 Golang Gin 框架的静态中间件:Gin-Static

news2024/11/15 19:53:19

Gin 是 Golang 生态中目前最受用户欢迎和关注的 Web 框架,但是生态中的 Static 中间件使用起来却一直很不顺手。

所以,我顺手改了它,然后把这个改良版开源了。

写在前面

soulteary/gin-static

Gin-static 的改良版,我开源在了 soulteary/gin-static,也发布在了 Go 软件包市场:pkg.go.dev/github.com/soulteary/gin-static,有需要可以自取。

提到改良优化,那么就不得不提 Go-Gin 和原版的 Gin-Static 对于静态文件的处理。

关于 Go-Gin 和 Gin 社区的静态文件处理

在 Gin 的官方文档中,关于如何使用 Gin 来处理“静态文件相关请求” 写的很清楚:

func main() {
	router := gin.Default()
	router.Static("/assets", "./assets")
	router.StaticFS("/more_static", http.Dir("my_file_system"))
	router.StaticFile("/favicon.ico", "./resources/favicon.ico")

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}

不过,这个例子中,官方只考虑到了静态资源都存放于二级目录,并且静态资源目录只存在静态资源的情况。

如果我们的静态资源需要使用 / 根目录,或者在静态目录所在的 /assets/* 中,存在需要 Golang后端程序要进行处理的“动态逻辑”,或者我们希望使用通配符来处理某些静态文件路由,这个玩法就失效了。而这个情况,在很多前端比较重的应用中非常常见,尤其是我们希望用 Golang 来优化 Node 或者纯前端实现的项目时。

这个问题在社区的反馈中有提到过,“#21,不能够在 / 根目录使用静态文件”、“#360,通配符和静态文件冲突”。

所以,在八年前 gin-contrib 社区出现了一个专注于处理静态程序的中间件:gin-contrib/static ,帮助我们解决了这个问题,使用的方法也很简单:

package main

import (
  "github.com/gin-contrib/static"
  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  // ...
  r.Use(static.Serve("/", static.LocalFile("/tmp", false)))
  // ...
}

不过,当基础功能完备后,这个插件就陷入了沉睡状态,版本号停留在 0.0.1 直至现在。

时过境迁,Golang 的版本已经升到了 1.21,这个中间件中引用的一些软件也变的陈旧,甚至被废弃,社区中也挂起了一些很好的功能实现(比如,“#19,Go 原生文件嵌入实现”),但是因为作者比较忙碌或者没有相同的痛点,所以 PR 一直未能合并。

在若干年后批判古早的代码毫无意义,所以我们就不扯出代码一行行审阅了,我个人认为相对靠谱的动作是帮助它解决问题。

在早些时候,《深入浅出 Golang 资源嵌入方案:前篇》、《深入浅出 Golang 资源嵌入方案:go-bindata篇》这两篇文章中,我提到过的 Golang 官方和社区排名靠前的资源嵌入方案,对于制作性能靠谱、方便分发的单文件应用非常有价值。

所以,结合社区里存在的 PR 提交(feat: Implement embed folder and a better organisation),我提交了一个新的 PR(#46),对之前的程序和 PR 实现的代码都做了一些完善,并且确保这个中间件测试覆盖率是 100%,使用起来能够更安心。

下载 gin-static 优化版

和其他社区软件一样,使用下面的一句话命令,可以完成 gin-static 的下载了:

go get github.com/soulteary/gin-static

如果你是全新使用,在你的在程序中添加下面的引用内容即可:

import "github.com/soulteary/gin-static"

// 或
import (
	static "github.com/soulteary/gin-static"
)

如果你已经使用了社区的 github.com/gin-gonic/gin-static 软件包,并且不想修改已有程序的引用和行为,那么我们可以用另外一种方法。

在你的 go.mod 文件中,我们应该能够看到类似下面的内容:

module your-project

go 1.21.2

require (
	github.com/gin-gonic/gin v1.9.1
	github.com/gin-gonic/gin-static v0.0.1
)

我们只需要在 require 之前,添加一条依赖替换规则即可:

module your-project

go 1.21.2

replace (
	github.com/gin-gonic/gin-static v0.0.1 => github.com/soulteary/gin-static v0.0.5
)

require (
	github.com/gin-gonic/gin v1.9.1
	github.com/gin-gonic/gin-static v0.0.1
)

完成内容添加后,我们执行 go mod tidy,完成依赖的更新即可。不论是哪一种使用方式,当你执行完命令后,我们就能够使用支持 Go 原生嵌入文件使用啦。

使用 gin-static 优化版

在项目的示例目录中,我提交了两个使用示例程序,分别包含“基础使用(simple)” 和 支持“文件嵌入”的例子(embed):

├── embed
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   └── public
│       └── page
└── simple
    ├── go.mod
    ├── go.sum
    ├── main.go
    └── public
        └── index.html

基础使用

程序的基础使用,和之前社区版本的接口一致,如果我们想在程序中直接使用本地的静态文件:

package main

import (
	"log"

	"github.com/gin-gonic/gin"
	static "github.com/soulteary/gin-static"
)

func main() {
	r := gin.Default()

    // 静态文件在默认根路径
	r.Use(static.Serve("/", static.LocalFile("./public", false)))

    // 其他路径 /other-place
    // r.Use(static.Serve("/other-place", static.LocalFile("./public", false)))

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "test")
	})

	// Listen and Server in 0.0.0.0:8080
	if err := r.Run(":8080"); err != nil {
		log.Fatal(err)
	}
}

实际使用过程中,我们还可以对根目录做一些额外的逻辑,使用 r.[Method] 来覆盖默认的静态文件路由:

// 将静态资源注册到根目录,使用本地的 Public 作为“数据源”
r.Use(static.Serve("/", static.LocalFile("public", false)))
// 允许添加其他的路由规则处理根目录
r.GET("/", func(c *gin.Context) {
  c.Redirect(http.StatusMovedPermanently, "/somewhere")
})

文件嵌入

在早些时候,《深入浅出 Golang 资源嵌入方案:前篇》、《深入浅出 Golang 资源嵌入方案:go-bindata篇》这两篇文章中,我提到过的 Golang 官方和社区排名靠前的资源嵌入方案,对于制作性能靠谱、方便分发的单文件应用非常有价值。

使用 gin-static 来处理嵌入文件非常简单,并且支持多种用法:

package main

import (
	"embed"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

//go:embed public
var EmbedFS embed.FS

func main() {
	r := gin.Default()

	// Method 1: use as Gin Router
	// trim embedfs path `public/page`, and use it as url path `/`
	r.GET("/", static.ServeEmbed("public/page", EmbedFS))

	// OR, Method 2: use as middleware
	// trim embedfs path `public/page`, the embedfs path start with `/`
	r.Use(static.ServeEmbed("public/page", EmbedFS))

	// OR, Method 2.1: use as middleware
	// trim embedfs path `public/page`, the embedfs path start with `/public/page`
	r.Use(static.ServeEmbed("", EmbedFS))

	// OR, Method 3: use as manual
	// trim embedfs path `public/page`, the embedfs path start with `/public/page`
	// staticFiles, err := static.EmbedFolder(EmbedFS, "public/page")
	// if err != nil {
	// 	log.Fatalln("initialization of embed folder failed:", err)
	// } else {
	// 	r.Use(static.Serve("/", staticFiles))
	// }

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "test")
	})

	r.NoRoute(func(c *gin.Context) {
		fmt.Printf("%s doesn't exists, redirect on /\n", c.Request.URL.Path)
		c.Redirect(http.StatusMovedPermanently, "/")
	})

	// Listen and Server in 0.0.0.0:8080
	r.Run(":8080")
}

上面的代码中,我们首先使用 //go:embed public 将本地的 public 目录读入 Golang 程序中,转换为程序可以访问的对象。然后你就可以根据你自己的具体情况,使用上面程序中的任意一种用法了。

当我们使用 go build 构建程序后,就能够得到一个包含了所有依赖静态文件的单一可执行文件啦。

个人倾向用法

我个人在使用的过程中,倾向于将上面两种用法合并在一起,当我们在开发的时候,使用本地文件系统(前者),而当我们构建的时候,则使用 Go 内嵌文件系统(后者)。

这样可以确保我们在玩的时候,静态文件支持所见即所得的修改立即生效,下面是我个人喜欢的用法示例:

if debugMode {
	r.Use(static.Serve("/", static.LocalFile("public", false)))
} else {
	r.NoRoute(
		// 例如,对存在的具体目录进行一些特殊逻辑处理
		func(c *gin.Context) {
			if c.Request.URL.Path == "/somewhere/" {
				c.Data(http.StatusOK, "text/html; charset=utf-8", []byte("custom as you like"))
				c.Abort()
			}
		},
		static.ServeEmbed("public", EmbedFS),
	)
	// 或者,不需要额外处理和拦截存在的静态文件
	// r.NoRoute(static.ServeEmbed("public", EmbedFS))
}

在上面的代码里,我们将本地的静态文件,在开发时默认挂载在 / 根目录,用于“兜底访问(fallback)”,这些文件允许被各种其他的路由覆盖。当我们进行构建或设置 debugMode=false 的时候,我们将静态文件挂载低优先级的 NoRoute 路由中,用于“兜底访问(fallback)”,如果我们需要调整或覆盖一些真实存在的静态文件,那么我们需要在路由前做额外的处理。

最后

好了,这个中间件就是这么简单,我们已经聊完了 80% 相关的内容啦。有机会我们在聊聊更有趣的 Embed 文件优化的故事。

–EOF


本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2024年01月03日
统计字数: 6357字
阅读时间: 13分钟阅读
本文链接: https://soulteary.com/2024/01/03/golang-gin-static-middleware-improves.html

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

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

相关文章

第二十五章 JDBC 和数据库连接池

一、JDBC 概述(P821) 1. 基本介绍 (1)JDBC 为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。 (2)Java 程序员使用 JDBC,可以连接任何提供了 JDBC 驱动程序的数据库系统…

【网络技术】【Kali Linux】Wireshark嗅探(六)地址解析协议(ARP)

一、实验目的 本次实验使用Wireshark流量分析工具进行网络嗅探,旨在了解地址解析协议(ARP)的工作原理。 二、ARP协议概述 地址解析协议(Address Resolution Protocol, ARP)位于网络层(IP层)&…

金和OA SAP_B1Config.aspx存在未授权访问漏洞

产品简介 金和网络是专业信息化服务商,为城市监管部门提供了互联网监管解决方案,为企事业单位提供组织协同OA系统升开发平台,电子政务一体化平台智慧电商平合等服务 漏洞概述 金和OA SAP_B1Config.aspx存在未授权访问漏洞,攻击…

看完,你还会学鸿蒙吗?

是不是前端程序员的春天,我们可以分析鸿蒙现在的市场和布局。其实不仅仅只是前端,还有Android、Java、Python等等开发人员都可以把鸿蒙当做新的出路。 2024年程序员为什么一定要学鸿蒙? 首先,鸿蒙作为一个新系统的出现。它的结构…

Android studio BottomNavigationView 应用设计

一、新建Bottom Navigation Activity项目&#xff1a; 二、修改bottom_nav_menu.xml: <itemandroid:id"id/navigation_beijing"android:icon"drawable/ic_beijing_24dp"android:title"string/title_beijing" /><itemandroid:id"i…

test ui-04-testcomplete 入门介绍

About TestComplete TestComplete是一款适用于各种应用程序类型和技术的自动化测试环境&#xff0c;包括&#xff08;但不限于&#xff09;Windows、.NET、WPF、Visual C、Visual Basic、Delphi、CBuilder、Java以及Web应用程序和服务。 TestComplete既适用于功能测试&#x…

线程的深入学习(一)

前言 前面文章讲述了线程的部分基本知识&#xff0c;这篇是对线程的深入学习&#xff0c;包含线程池&#xff0c;实现框架等。 1.学习如何使用Executor框架创建线程池。 2.并发工具类如CountDownLatch、CyclicBarrier等。 3.线程安全和并发集合&#xff1a; 4.学习如何使用Jav…

python入门第一讲:认识python

目录 什么是计算机 什么是编程 编程语言有哪些 python是咋来的 python能干啥 python的优缺点 什么是计算机 什么叫计算机&#xff0c;当你把问这个问题问家里的老人的时候&#xff0c;他们很可能指着计算器告诉你说&#xff0c;这个就是计算机&#xff0c;这个很明显&am…

CEEMDAN +组合预测模型(Transformer - BiLSTM+ ARIMA)

目录 往期精彩内容&#xff1a; 前言 1 风速数据CEEMDAN分解与可视化 1.1 导入数据 1.2 CEEMDAN分解 2 数据集制作与预处理 3 基于CEEMADN的 Transformer - BiLSTM 模型预测 3.1 定义CEEMDAN-Transformer - BiLSTM预测模型 3.2 设置参数&#xff0c;训练模型 4 基于A…

HubSpot电子邮件自动化的关键功能和流程!

HubSpot提供了强大的电子邮件自动化工具&#xff0c;使用户能够创建、执行和跟踪复杂的电子邮件市场营销活动。以下是HubSpot电子邮件自动化的一些关键功能和流程&#xff1a; 1.电子邮件工作流程&#xff08;Email Workflows&#xff09;&#xff1a; 用户可以使用HubSpot的工…

Lumerical Script------for语句

Lumerical------for语句 正文正文 关于 Lumerical 中 for 语句的用法这里不做过多说明了,仅仅做一个记录,具体用法如下: 通常我们用的比较多的形式是第一种步长值为 1 的情况。对于其他步长值的情况,我们可以使用第二种用法。对于 while 的类似使用方法可以使用第三种。 …

2_并发编程同步锁(synchronized)

并发编程带来的安全性同步锁(synchronized) 1.他的背景 当多个线程同时访问&#xff0c;公共共享资源的时候&#xff0c;这时候就会出现线程安全&#xff0c;代码如&#xff1a; public class AtomicDemo {int i0;//排他锁、互斥锁public void incr(){ //synchronizedi; …

Allins 官网正式上线,铭文赛道进入 AMM 交易时代

“Allins 正在通过全新的 AMM 方案为BRC20及多链铭文资产拓展 DeFi 场景&#xff0c;官网的全新上线意味着铭文资产的交易正式进入 AMM 时代。” 在 2023 年 1 月开始&#xff0c; Ordinals 协议的推出成为了铭文赛道发展的开端&#xff0c;并为比特币这类非图灵完备的生态&…

虾皮马来站点选品:在虾皮(Shopee)5个热门品类和市场特点

在虾皮&#xff08;Shopee&#xff09;马来西亚站点选择商品时&#xff0c;卖家应该考虑一些热门品类和市场特点&#xff0c;以确保他们的产品能够满足当地消费者的需求并取得良好的销售业绩。以下是在虾皮&#xff08;Shopee&#xff09;马来西亚站点销售商品时需要考虑的五个…

大模型实战营第二期——1. 书生·浦语大模型全链路开源开放体系

文章目录 1. 实战营介绍2. 书生浦语大模型介绍2.1 数据2.2 预训练2.3 微调2.4 评测2.5 部署2.6 智能体(应用) 1. 实战营介绍 github链接&#xff1a;https://github.com/internLM/tutorialInternLM&#xff1a;https://github.com/InternLM书生浦语官网&#xff1a;https://in…

c 编码(进行中)

编码出来的jpeg图片只有红&#xff0c;绿色。排查中 ​​​​​​​ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #in…

nestjs入门教程系列(三):动态路由到底该怎么用

如何获取前端请求中的参数 引子 我们在处理前端发送过来的请求时&#xff0c;需要访问客户端的request信息&#xff0c;那么我们该怎么操作呢?由于nestjs提供对底层平台的访问即express&#xff0c;所以我们可以通过注入的方式来方位请求对象 TIPS:需要从nestjs/common包里…

【C程序设计】C判断

判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 C 语言把任何非零和非空的值假定为 true&#xff0c;把零或 null 假定为 false。 下面…

自动化测试框架 —— pytest框架入门到入职篇

01、pytest框架介绍 pytest 是 python 的第三方单元测试框架&#xff0c;比自带 unittest 更简洁和高效&#xff0c;支持非常丰富的插件&#xff0c;同时兼容 unittest 框架。这就使得我们在 unittest 框架迁移到 pytest 框架的时候不需要重写代码。 pytest框架优点 1、简单…

网络故障排查和流量分析利器-Tcpdump命令

Tcpdump是一个在Unix/Linux系统上广泛使用的命令行网络抓包工具。它能够捕获经过网络接口的数据包&#xff0c;并将其以可读的格式输出到终端或文件中。Tcpdump是一个强大的命令行工具&#xff0c;能够捕获和分析网络数据包&#xff0c;为网络管理员和安全专业人员提供了深入了…