如何创建Golang REST API:项目布局配置[第3部分]

好的蛋糕是您可以轻松切成薄片且不会碎屑的蛋糕。 这就是这个项目的全部内容:3个简单的零件,没有讨厌的添加剂。 在第1部分第2部分中,我解释了使用docker设置golang项目,创建可配置服务器,与DB交互,添加路由和处理程序的基础知识。

当多个处理程序不得不重用同一逻辑时会发生什么?

解决此问题的最优雅方法是什么?

这是中间件发挥作用的地方,它将是我在这里的主要重点。

在路由处理程序的上下文中使用中间件的想法是,我们从多个小功能(中间件)构造处理程序。 这样可以轻松实现代码的重用性,并使处理程序的推理变得更加容易。

我将通过向其添加更多逻辑来继续处理先前文章中的用户资源。 每个处理程序可能具有的一件事是请求日志记录。 在实现它之前,有一个小的实用程序函数可以帮助我构造处理程序:

// pkg/middleware/middleware.go 

package middleware

import (
	"github.com/julienschmidt/httprouter"
)

type Middleware func (httprouter.Handle) httprouter . Handle

func Chain (f httprouter.Handle, m ...Middleware) httprouter . Handle {
	if len (m) == 0 {
		return f
	}

	return m[ 0 ](Chain(f, m[ 1 :]...))
}

Chain函数将接受任何数量的中间件类型的函数,并一一调用它们,从而使我们可以完全控制流程,稍后再进行介绍。 现在,我已经准备好了,我将添加简单的请求日志记录中间件。 由于它将在多个处理程序中重用,因此将其放在pkg / middleware目录中。 所有特定于处理程序的中间件都将位于处理程序本身旁边:

// pkg/middleware/logging.go

package middleware

import (
	"net/http"

	"github.com/boilerplate/pkg/logger"
	"github.com/julienschmidt/httprouter"
)

func LogRequest (next httprouter.Handle) httprouter . Handle {
	return func (w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		logger.Info.Printf( "%s - %s %s %s" , r.RemoteAddr, r.Proto, r.Method, r.URL.RequestURI())
		next(w, r, p)
	}
}

一个简单的高阶函数,接受并返回httprouter.Handle类型的函数,并从两者之间的请求中读取一些数据。 接下来,我计划接受用户ID作为url参数,并且我想确保它是有效的数字字符串。 如果不是,我将仅用状态代码412响应客户端。听起来像这样的逻辑可以作为中间件实现:

// cmd/api/handlers/getuser/validarequest.go
 
package getuser

import (
	"context"
	"fmt"
	"net/http"
	"strconv"

	"github.com/boilerplate/cmd/api/models"
	"github.com/julienschmidt/httprouter"
)

func validateRequest (next httprouter.Handle) httprouter . Handle {
	return func (w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		uid := p.ByName( "id" )

		id, err := strconv.Atoi(uid)
		if err != nil {
			w.WriteHeader(http.StatusPreconditionFailed)
			fmt.Fprintf(w, "malformed id" )
			return
		}

		ctx := context.WithValue(r.Context(), models.CtxKey( "userid" ), id)
		r = r.WithContext(ctx)
		next(w, r, p)
	}
}

与记录中间件相同的原理。 上下文是在中间件之间共享数据的一种标准方式,出于演示目的,我在其中添加了用户ID。 如果客户端发送格式错误的ID,我将跳过中间件链中的所有后续步骤,方法是不调用next函数并直接从此处响应客户端。

下一步是getuser.Do处理程序本身:

// cmd/api/handlers/getuser/getuser.go

package getuser

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/boilerplate/cmd/api/models"
	"github.com/boilerplate/pkg/application"
	"github.com/boilerplate/pkg/middleware"
	"github.com/julienschmidt/httprouter"
)

func getUser (app *application.Application) httprouter . Handle {
	return func (w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		defer r.Body.Close()

		id := r.Context().Value(models.CtxKey( "userid" ))
		user := &models.User{ID: id.( int )}

		if err := user.GetByID(r.Context(), app); err != nil {
			if errors.Is(err, sql.ErrNoRows) {
				w.WriteHeader(http.StatusPreconditionFailed)
				fmt.Fprintf(w, "user does not exist" )
				return
			}

			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Oops" )
			return
		}

		w.Header().Set( "Content-Type" , "application/json" )
		response, _ := json.Marshal(user)
		w.Write(response)
	}
}

func Do (app *application.Application) httprouter . Handle {
	mdw := []middleware.Middleware{
		middleware.LogRequest,
		validateRequest,
	}

	return middleware.Chain(getUser(app), mdw...)
}

我首先创建所有必需中间件的一部分,然后将其变成可变参数函数参数。 middleware.Chain的实现方式是从右到左调用函数,因此首先将调用LogRequest ,然后是validateRequest和getUser

这里没有什么要指出的。 请注意, mdw slice中的中间件的签名与getUser的签名略有不同。 这是因为getUser是链中的最后一步,它不会调用next函数来跳转到链的下一步。 第二件事是我如何从上下文中读取价值。 让我们在测试处理程序之前启动服务:

docker-compose up --build

现在,让我们以foob​​ar作为用户ID进行请求。 由于foob​​ar不是数字字符串,因此我希望响应中的id消息格式错误:

现在,让我们尝试有效的ID。 目前,我在用户表中没有ID为2记录,因此希望用户不存在该消息:

您可以在这里找到整个项目。 我真的希望这对您来说是一个很好的起点,并有助于构建新的,令人兴奋的东西。

保持安全,吃蛋糕并保持编码!

From: https://hackernoon.com/how-to-create-golang-rest-api-project-layout-configuration-part-3-pr453ylt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值