英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论