Golang Gin: 头部信息已经被写入。想要用状态码200覆盖状态码301。

huangapple go评论97阅读模式
英文:

Golang Gin: Headers were already written. Wanted to override status code 301 with 200

问题

我正在开发一个控制面板,并雇佣了一些人来为我构建它。他们都放鸽子了,现在我只能自己来整理这个混乱的代码。

我需要做的是:

  • 打开登录页面
  • 检查登录信息并提交表单
  • 提交成功后,重定向到仪表盘页面

只是一个简单的登录过程。问题是,当登录成功时,控制台进入了下面的重定向循环:

[GIN] 2023/02/21 - 15:43:32 | 301 |  1.224601041s |             ::1 | POST     "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:33 | 200 |    787.3905ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:33 | 200 |  197.989875ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:34 | 200 |  817.293166ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:34 | 200 |  206.107791ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:35 | 200 |  792.954375ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:35 | 200 |  201.972708ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:36 | 200 |  840.773625ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:36 | 200 |  198.680125ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:37 | 200 |  897.679708ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:37 | 200 |  200.759917ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:38 | 200 |   795.39975ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:38 | 200 |     196.538ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:39 | 200 |  844.680709ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:39 | 200 |  180.598084ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:40 | 200 |  814.666208ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:40 | 200 |     210.281ms |             ::1 | GET      "/login"

现在,由于我要接手之前开发人员的工作,我还在学习和使用Golang和Gin,所以请谅解...

据我了解,main()函数配置了路由、中间件、加载模板,然后运行引擎。

main.go

func main() {
	//gin.SetMode(gin.ReleaseMode) // uncomment for production

	// Startup Tasks
	startup()
	logging.LogInfo("Ran Startup Tasks...")

	// Configure Engine
	hostPort := fmt.Sprintf(
		"%s:%d",
		dataManagers.LoadConfig().Bshost,
		dataManagers.LoadConfig().Bsport)
	webEngine := gin.Default()
	webEngine.SetTrustedProxies([]string{hostPort})
	logging.LogInfo("Configured Engine...")

	// Load Middleware
	store := cookie.NewStore([]byte(randstr.String(64)))
	webEngine.Use(sessions.Sessions("session", store))
	webEngine.Use(errorHandler.ErrorsHandler500())
	logging.LogInfo("Loaded Middleware...")

	// Configure Routes
	pubRoutes := webEngine.Group("/")
	privRoutes := webEngine.Group("/")
	routes.PublicRoutes(pubRoutes)
	privRoutes.Use(middleware.AuthRequired)
	routes.PrivateRoutes(privRoutes)
	logging.LogInfo("Configured Routes...")

	// Non Routables
	webEngine.NoRoute(errorHandler.ErrorsHandler404())
	logging.LogInfo("Configured Non-Routables...")

	// Load Template Files
	LoadTemplates(webEngine)
	logging.LogInfo("Loaded Templates...")

	// Start the Gin Engine
	err := webEngine.Run(hostPort)
	logging.LogInfo("...BlockSuite-WebUI Loaded")
	logging.Catch(err)
}

当访问/时,我会被重定向到/login,然后显示登录表单。

我使用有效的凭据提交表单,然后被重定向到/dashboard。我不知道在成功登录后是否应该重定向,这是原始开发人员做的,而且一切正常。

routes.go

func PublicRoutes(webEngine *gin.RouterGroup) {
	webEngine.GET("/login", entry.LoginGetHandler)
	webEngine.POST("/login", entry.LoginPostHandler)
	webEngine.GET("/", other.IndexGetHandler())
}
func PrivateRoutes(webEngine *gin.RouterGroup) {
	dashboardRoutes := webEngine.Group("/dashboard")
	{
		dashboardRoutes.GET("/", dashboard.DashboardGetHandler)
	}
}

login.go

func LoginGetHandler(context *gin.Context) {
	user := utility.GetUserSession(context).Get("userEmail")
	if user != nil {
		context.Redirect(http.StatusMovedPermanently, "/dashboard")
	}
	context.HTML(http.StatusOK, "login.html", gin.H{
		"siteKey":    dataManagers.GetRecaptchaSettings().SiteKey,
		"enabled":    dataManagers.GetRecaptchaSettings().Enabled,
		"content":    "",
		"success":    "",
		"serverLogo": brand.GetBrandLogo(),
		"title":      "Welcome back",
	})
}
func LoginPostHandler(context *gin.Context) {
	user := utility.GetUserSession(context).Get("userEmail")
	if user != nil {
		context.Redirect(http.StatusMovedPermanently, "/dashboard")
		//return
	}
	userEmail := utility.Sanitize(context.PostForm("email"))
	password := utility.Sanitize(context.PostForm("password"))
	rememberme := utility.Sanitize(context.PostForm("rememberme"))
	//captcha := context.PostForm("g-recaptcha-response")
	if !utility.IsEmailValid(userEmail) {
		context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Please enter a valid email address"})
		return
	}
	/*if helpers2.RecaptchaCheck(captcha) || dataManagers.GetConfig().SiteKey != "" {
		// success
	} else {
		if dataManagers.GetConfig().Enabled {
			context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Please verify captcha"})
			return
		}
	}*/
	if utility.EmptyUserPass(userEmail, password) {
		context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Email and password cannot be empty"})
		return
	}

	if utility.CheckForWhiteSpaces(userEmail, password) != nil {
		context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Username and password can't contain spaces"})
		return
	}
	if !utility.CheckUserPass(userEmail, password) {
		context.HTML(http.StatusUnauthorized, "login.html", gin.H{"content": "Incorrect username or password"})
		return
	}
	utility.NewUserSession(context, userEmail)
	if rememberme == "yes" {
		utility.SetSessionAge(context)
	}
	context.Redirect(http.StatusMovedPermanently, "/dashboard")
}

然后,应该加载/dashboard页面。

dashboard.go

func DashboardGetHandler(context *gin.Context) {
	user := utility.GetUserSession(context).Get("userEmail")
	db := dataManagers.GetDB()
	if user == nil {
		context.Redirect(http.StatusMovedPermanently, "/login")
	}
    [...]
	context.HTML(http.StatusOK, "dashboard.html", gin.H{
		"info":       info,
		"imageUrl":   utility.GetImage(user),
		"serverLogo": brand.GetBrandIcon(),
		"title":      "Dashboard",
		"serverName": dataManagers.GetServerInfo().Servername,
	})
}

(在dashboard.go代码中,我省略了将数据加载到仪表盘的代码,因为它很长,我认为它不是那么必要。)

  • 我已经注释掉了dashboard.go中的所有数据查询,并添加了一个简单的“hi”响应,但仍然出现了重定向循环。所以,我知道这个go文件获取数据的方式没有问题。
  • 我尝试使用不同的HTTP响应代码,如http.StatusOK,但没有成功。
  • 我在dashboard.go中验证了会话数据确实被写入和保存。在加载仪表盘GET处理程序函数时,我能够输出会话数据。所以,我可以确定会话正常运行。
  • 我更改了处理程序的编写方式。之前的代码如下所示:

func DashboardGetHandler() gin.HandlerFunc {
	return func(context *gin.Context) {
    [...]
    }
}

我完全没有主意,不知道从哪里开始。谢谢!

英文:

I'm developing a control panel and had hired some people to build it for me. They all bailed and I'm left having to cleanup the spaghetti.

What I need to do is this:

  • Pull up Login page
  • Check login info and post form
  • After post is successful, redirect to dashboard page

Just a simple login process. Problem is that when the login succeeds, the console enters into this redirect loop as you can see below:

[GIN] 2023/02/21 - 15:43:32 | 301 |  1.224601041s |             ::1 | POST     "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:33 | 200 |    787.3905ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:33 | 200 |  197.989875ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:34 | 200 |  817.293166ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:34 | 200 |  206.107791ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:35 | 200 |  792.954375ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:35 | 200 |  201.972708ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:36 | 200 |  840.773625ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:36 | 200 |  198.680125ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:37 | 200 |  897.679708ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:37 | 200 |  200.759917ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:38 | 200 |   795.39975ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:38 | 200 |     196.538ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:39 | 200 |  844.680709ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:39 | 200 |  180.598084ms |             ::1 | GET      "/login"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:40 | 200 |  814.666208ms |             ::1 | GET      "/dashboard"
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 301 with 200
[GIN] 2023/02/21 - 15:43:40 | 200 |     210.281ms |             ::1 | GET      "/login"

Now, since I'm picking up the slack of old devs, I'm still studying/new to Golang and Gin so bare with me...

From what I understand, main() is configuring the routes, middleware, loading templates then running the engine.

main.go

func main() {
	//gin.SetMode(gin.ReleaseMode) // uncomment for production

	// Startup Tasks
	startup()
	logging.LogInfo("Ran Startup Tasks...")

	// Configure Engine
	hostPort := fmt.Sprintf(
		"%s:%d",
		dataManagers.LoadConfig().Bshost,
		dataManagers.LoadConfig().Bsport)
	webEngine := gin.Default()
	webEngine.SetTrustedProxies([]string{hostPort})
	logging.LogInfo("Configured Engine...")

	// Load Middleware
	store := cookie.NewStore([]byte(randstr.String(64)))
	webEngine.Use(sessions.Sessions("session", store))
	webEngine.Use(errorHandler.ErrorsHandler500())
	logging.LogInfo("Loaded Middleware...")

	// Configure Routes
	pubRoutes := webEngine.Group("/")
	privRoutes := webEngine.Group("/")
	routes.PublicRoutes(pubRoutes)
	privRoutes.Use(middleware.AuthRequired)
	routes.PrivateRoutes(privRoutes)
	logging.LogInfo("Configured Routes...")

	// Non Routables
	webEngine.NoRoute(errorHandler.ErrorsHandler404())
	logging.LogInfo("Configured Non-Routables...")

	// Load Template Files
	LoadTemplates(webEngine)
	logging.LogInfo("Loaded Templates...")

	// Start the Gin Engine
	err := webEngine.Run(hostPort)
	logging.LogInfo("...BlockSuite-WebUI Loaded")
	logging.Catch(err)
}

When / is accessed, I'm redirected to /login which brings up the login form.

I submit the form with valid credentials and it redirects me to /dashboard. I don't know if redirect is the correct thing to do after a successful sign-in, that is what the original dev did and it worked fine.

routes.go

func PublicRoutes(webEngine *gin.RouterGroup) {
	webEngine.GET("/login", entry.LoginGetHandler)
	webEngine.POST("/login", entry.LoginPostHandler)
	webEngine.GET("/", other.IndexGetHandler())
}
func PrivateRoutes(webEngine *gin.RouterGroup) {
	dashboardRoutes := webEngine.Group("/dashboard")
	{
		dashboardRoutes.GET("/", dashboard.DashboardGetHandler)
	}
}

login.go

func LoginGetHandler(context *gin.Context) {
	user := utility.GetUserSession(context).Get("userEmail")
	if user != nil {
		context.Redirect(http.StatusMovedPermanently, "/dashboard")
	}
	context.HTML(http.StatusOK, "login.html", gin.H{
		"siteKey":    dataManagers.GetRecaptchaSettings().SiteKey,
		"enabled":    dataManagers.GetRecaptchaSettings().Enabled,
		"content":    "",
		"success":    "",
		"serverLogo": brand.GetBrandLogo(),
		"title":      "Welcome back",
	})
}
func LoginPostHandler(context *gin.Context) {
	user := utility.GetUserSession(context).Get("userEmail")
	if user != nil {
		context.Redirect(http.StatusMovedPermanently, "/dashboard")
		//return
	}
	userEmail := utility.Sanitize(context.PostForm("email"))
	password := utility.Sanitize(context.PostForm("password"))
	rememberme := utility.Sanitize(context.PostForm("rememberme"))
	//captcha := context.PostForm("g-recaptcha-response")
	if !utility.IsEmailValid(userEmail) {
		context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Please enter a valid email address"})
		return
	}
	/*if helpers2.RecaptchaCheck(captcha) || dataManagers.GetConfig().SiteKey != "" {
		// success
	} else {
		if dataManagers.GetConfig().Enabled {
			context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Please verify captcha"})
			return
		}
	}*/
	if utility.EmptyUserPass(userEmail, password) {
		context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Email and password cannot be empty"})
		return
	}

	if utility.CheckForWhiteSpaces(userEmail, password) != nil {
		context.HTML(http.StatusBadRequest, "login.html", gin.H{"content": "Username and password can't contain spaces"})
		return
	}
	if !utility.CheckUserPass(userEmail, password) {
		context.HTML(http.StatusUnauthorized, "login.html", gin.H{"content": "Incorrect username or password"})
		return
	}
	utility.NewUserSession(context, userEmail)
	if rememberme == "yes" {
		utility.SetSessionAge(context)
	}
	context.Redirect(http.StatusMovedPermanently, "/dashboard")
}

And then, the /dashboard page is supposed to be loaded.

dashboard.go

func DashboardGetHandler(context *gin.Context) {
	user := utility.GetUserSession(context).Get("userEmail")
	db := dataManagers.GetDB()
	if user == nil {
		context.Redirect(http.StatusMovedPermanently, "/login")
	}
    [...]
	context.HTML(http.StatusOK, "dashboard.html", gin.H{
		"info":       info,
		"imageUrl":   utility.GetImage(user),
		"serverLogo": brand.GetBrandIcon(),
		"title":      "Dashboard",
		"serverName": dataManagers.GetServerInfo().Servername,
	})
}

(In the dashboard.go code, I omitted the code that pulls data into the dashboard as it is long and don't think it is that necessary.)

  • I have commented out all of the data queries from the dashboard.go and added a simple "hi" response and it still did a redirect look. So, I know there is nothing wrong with the way this gofile is pulling data.
  • I tried using different HTTP response codes like http.StatusOK and no dice.
  • I verified in dashboard.go that session data is indeed being written and saved. When loading the dashboard GET handler func, I was able to output session data. So, I know for a fact the session is operating fine.
  • I changed how the handlers were written. Previously, it was coded as follows:

func DashboardGetHandler() gin.HandlerFunc {
	return func(context *gin.Context) {
    [...]
    }
}

I'm completely out of ideas and don't know where to go from here. Thanks!

答案1

得分: 0

感谢所有帮助的人。我和之前的开发人员联系了,他帮助我找出了问题所在。

在他的代码中,他创建了一个中间件函数,出于某种原因,它再次检查了会话。那段代码检查了在会话cookie中不存在的旧变量。因此,我被踢回到登录界面。

所以,我只是移除了那个中间件,因为我在login.go中已经处理了那部分。

英文:

Thanks everyone that pitched in to help. I got with the previous dev and he helped me figure out what was wrong.

In his code, he had created a middleware func that, for some reason, checked the session again. That piece of code was checking old vars that did not exist in the session cookies. Because of that, I was getting kicked back to the login screen.

So, all I did was remove that middleware since I was handling that in login.go anyways.

huangapple
  • 本文由 发表于 2023年2月22日 06:30:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/75526514.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定