※初心者のため、間違いや何かご存じの方がいましたら、教えていただけると助かります。
概要
以下のサイトに従って、ginによる認証を実装をしていたところ、
「サインアップ」エンドポイント ユーザー情報登録の項目で、POSTしたら、以下の 500 Internal Server Errorが発生した。
main.goは以下
package main
import (
"jwt-gin/controllers"
"jwt-gin/models"
"github.com/gin-gonic/gin"
)
func main(){
models.ConnectDataBase()
router := gin.Default()
public := router.Group("/api")
public.POST("/register", controllers.Register)
router.Run(":8080")
}
setup.goは以下
package models
import(
"fmt"
"log"
"os"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/joho/godotenv"
)
var DB *gorm.DB
func ConnectDataBase(){
err := godotenv.Load()
if err != nil {
log.Fatalf("Error loading .env file")
}
driver := os.Getenv("DB_DRIVER")
dbUser := os.Getenv("DB_USER")
dbPass := os.Getenv("DB_PASS")
dbName := os.Getenv("DB_NAME")
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
dbURI := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Tokyo", dbHost, dbUser, dbPass, dbName, dbPort)
fmt.Println(dbURI)
fmt.Println(driver)
DB, err := gorm.Open(driver, dbURI)
if err != nil{
log.Fatal("Could not connect to the database:", err)
}
DB.AutoMigrate(&User{})
}
auth.go(ユーザー登録するRegister関数)は以下
package controllers
import (
"fmt"
"net/http"
"jwt-gin/models"
"github.com/gin-gonic/gin"
)
type RegisterInput struct{
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func Register(c *gin.Context){
var input RegisterInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Printf("%+v\n",input) //inputの確認
user := models.User{
Username: input.Username, Password: input.Password,
}
fmt.Printf("%+v\n", user) //userの確認 ← ここは問題なし。
user, err := user.Save() //← これが実行されないエラー箇所
fmt.Printf("%+v\n", user) //userの確認 ← これが実行されない。
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": user.PrepareOutput(),
})
}
user.go(user.Save()の中身)は以下
package models
import (
"strings"
"github.com/jinzhu/gorm"
"golang.org/x/crypto/bcrypt"
)
type User struct {
gorm.Model
Username string `gorm:"size:255;not null;unique" json:"username"`
Password string `gorm:"size:255;not null;" json:"password"`
}
func (u User) Save() (User, error) {
err := DB.Create(&u).Error
if err != nil {
return User{}, err
}
return u, nil
}
func (u *User) BeforeSave() error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hashedPassword)
u.Username = strings.ToLower(u.Username)
return nil
}
func (u User) PrepareOutput() User {
u.Password = ""
return u
}
環境
windows11
docker-desktop
go 1.22
エラーメッセージ
//main.goで8080番でサーバー立ち上げ
# go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /api/register --> jwt-gin/controllers.Register (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
/*
ここで、http://localhost:8080/api/registerへPOSTリクエスト。bodyには以下のJSON形式
{"username":"admin", "password":"pass"}
*/
2024/09/16 16:07:30 [Recovery] 2024/09/16 - 16:07:30 panic recovered:
POST /api/register HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: close
Content-Length: 38
Content-Type: application/json
User-Agent: got (https://github.com/sindresorhus/got)
runtime error: invalid memory address or nil pointer dereference
/usr/local/go/src/runtime/panic.go:261 (0x4558b7)
panicmem: panic(memoryError)
/usr/local/go/src/runtime/signal_unix.go:881 (0x455885)
sigpanic: panicmem()
/go/pkg/mod/github.com/jinzhu/gorm@v1.9.16/main.go:853 (0x74d777)
(*DB).clone: dialect: newDialect(s.dialect.GetName(), s.db),
/go/pkg/mod/github.com/jinzhu/gorm@v1.9.16/main.go:204 (0x746e25)
(*DB).NewScope: dbClone := s.clone()
/go/pkg/mod/github.com/jinzhu/gorm@v1.9.16/main.go:482 (0x74a8c4)
(*DB).Create: scope := s.NewScope(value)
/app/jwt-gin/models/user.go:17 (0x9719eb)
User.Save: err := DB.Create(&u).Error
/app/jwt-gin/controllers/auth.go:33 (0x97194e)
Register: user, err := user.Save()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x968059)
(*Context).Next: c.handlers[c.index](c)
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102 (0x968047)
CustomRecoveryWithWriter.func1: c.Next()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x967184)
(*Context).Next: c.handlers[c.index](c)
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/logger.go:249 (0x96716b)
LoggerWithConfig.func1: c.Next()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x966571)
(*Context).Next: c.handlers[c.index](c)
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633 (0x965fe0)
(*Engine).handleHTTPRequest: c.Next()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589 (0x965b11)
(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:3142 (0x6cb6ed)
serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:2044 (0x6c6aa7)
(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1695 (0x472e00)
goexit: BYTE $0x90 // NOP
[GIN] 2024/09/16 - 16:07:30 | 500 | 85.893591ms | 172.20.0.1 | POST "/api/register"
解決策
ユーザー登録Registerのuser.Save()時に、DBへの接続ができないエラーらしい。
しかし、DBには以下のsetup.goからUserテーブルを作ることはできていた。
結論、原因としては、setup.goの以下で引っかかっていた。
DB, err := gorm.Open(driver, dbURI)
⇒ 正しくは以下
DB, err = gorm.Open(driver, dbURI)
DBを:=で書いたことで代入ではなく宣言扱いになってしまい、他の関数からDBにアクセスできなくなったこと(スコープミス)が原因と思われた。
以下は、bingAIに聞いた回答。
`DB, err := gorm.Open(driver, dbURI)` と `DB, err = gorm.Open(driver, dbURI)` の違いは、変数のスコープにあります。
1. **`DB, err := gorm.Open(driver, dbURI)`**:
- これは新しい変数 `DB` と `err` を関数内で宣言しています。この場合、関数内で宣言された `DB` 変数は、パッケージレベルで宣言された `DB` 変数とは異なるため、パッケージレベルの `DB` 変数は更新されません。
- その結果、関数外で `DB` を参照すると `nil` のままになり、エラーが発生します。
2. **`DB, err = gorm.Open(driver, dbURI)`**:
- これは既にパッケージレベルで宣言されている `DB` 変数と `err` 変数に値を代入しています。この場合、パッケージレベルの `DB` 変数が更新されるため、関数外でも正しく初期化された `DB` を参照できます。
したがって、`DB, err = gorm.Open(driver, dbURI)` を使用することで、パッケージレベルの `DB` 変数が正しく初期化され、他の関数からもアクセスできるようになります。