Go言語初心者向けWEB講座その1:neoフレームワーク (簡単な認証、サインアップ、ログインを準備)
2020年3月19日Go言語
Go言語も様々なフレームワークがありますが、あえて情報ソースが少なく学習コストが低いものを選んでみました。このneoは単純で1日で覚えられます。フルスタックのフレームワークならRevelを使うと思いますが、どの言語もメジャーなものを選択するとちゃんと理解しないでコピペで終わってしまいそうなので私は常にマイナーなものを選びます。言語を覚えているのか、フレームワークを覚えているのかがわからなくなるのでGo言語に限らずどの言語もフレームワークやその言語を深く理解しようと思う場合、マイナーなものを選択します。そうすることで後々の改造や言語を詳しくしるきっかけになるからです。
neoフレームワークのインストール
1 2 3 4 5 |
$ go get github.com/ivpusic/neo <p>最初にneoフレムワークをダウンロードしプロジェクトフォルダーmyappを自動生成させます。</p> $ go get github.com/ivpusic/neo/cmd/neo $ neo new myapp $ cd myapp |
neoのコンフィギューレーションファイル
neoでプロジェクトフォルダーを自動生成するとその配下にconfig.tomlとmain.goの簡単なWEBサーバ起動ファィルができていることを見つけるでしょう。config.tomlはneoのコンフィギュレーションファイルです。このファイルを編集する機会があるとするなら多くはリッスンするIPアドレスとポート番号ではないかと思います。以下に別のIPアドレスに変更した場合のサンプルを用意します。
config.toml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# # settings related to recompiling and reruning app when source changes # [hotreload] # file suffixes to watch for changes suffixes = [".go"] # files/directories to ignore ignore = [] # # general application settings # [app] # additional application arguments args = [] addr = ":8070" <---8070番Portでリッスン、IPアドレスの省略は"0.0.0.0"のany addr listenを意味する。 [app.logger] level = "DEBUG" name = "application" # # neo settings # [neo] [neo.logger] level = "INFO" |
ファイル構成
尚、全体のプロジェクトのファイル構成は以下の通り。
メインのソースコード
初回の仕様としてはログイン情報のサインアップとログインする仕組みを提供する必要最低限を実装します。セッションの機能やユーザデータはMySQLで処理します。尚、今回はユーザパスワードは生のテキストで実装しますが、次の「その2」では暗号化する処理を入れた内容で実施する予定です。
セッション管理にはSCSを使用しています。SCS: HTTP Session Management for Go予めインストールしておきましょう。また、mysqlのgo-sql-driverも合わせて導入してください。
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
package main import ( "myapp/user" _ "github.com/go-sql-driver/mysql" "github.com/ivpusic/golog" "github.com/ivpusic/neo" "github.com/ivpusic/neo/middlewares/logger" "github.com/alexedwards/scs" "github.com/alexedwards/scs/memstore" ) var ( log = golog.GetLogger("application") session *scs.Session ) // Set path "/" func Webroot(ctx *neo.Ctx)(int, error){ return 200, ctx.Res.Tpl("index",nil) } // Set path "/page01" func Pages(ctx *neo.Ctx)(int, error){ msg := session.GetString(ctx.Req.Context(), "message") if msg == "" { msg = "not Login status." } return 200, ctx.Res.Tpl("page",msg) } // Set path "/signup" func Signup(ctx *neo.Ctx)(int, error){ return 200,ctx.Res.Tpl("signup",nil) } // Set path "/login" GET action func Login(ctx *neo.Ctx)(int, error){ return 200,ctx.Res.Tpl("login",nil) } // Set path "/login" POST action func Login_post(ctx *neo.Ctx)(int, error){ var ret_code int data := make(map[string]string) data["username"] = ctx.Req.FormValue("username") data["password"] = ctx.Req.FormValue("password") data["ret_code"] = "auth success" if ret := user.User_chk(data); ret == true { ret_code = 200 session.Put(ctx.Req.Context(),"message","hello from s session!") } else { ret_code = 404 data["ret_code"] = "auth failed" } return ret_code,ctx.Res.Tpl("logined",data) } // Set path "/logout" func Logout(ctx *neo.Ctx)(int, error){ session.Destroy(ctx.Req.Context()) return 200, ctx.Res.Tpl("index",nil) } func main() { log.Info("Regards from Neo") session = scs.NewSession() session.Store = memstore.New() // Make Application Instance app := neo.App() // Template Assign app.Templates( "templates/*.tpl", ) // Set Static Path app.Serve("/static", "asset") app.Use(logger.Log) // URL Routing dispatch process routine app.Get("/",Webroot) app.Get("/signup",Signup) app.Get("/login",Login) app.Get("/page01",Pages) app.Get("/logout",Logout) app.Post("/login",Login_post) // Server listen app.Start(session.LoadAndSave(app)) //app.Start(nil) } |
テンプレートの用意
templates/header.tpl
1 2 3 |
{{define "header"}} <p>This is header</p> {{end}} |
1 2 3 |
{{define "footer"}} <p>This is footer</p> {{end}} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{{define "index"}} <!DOCTYPE html> <html> <meta charset="UTF-8"> <head> <title>Example</title> </head> <body> {{template "header"}} <div> Site content <br> </div> {{template "footer"}} </body> </html> {{end}} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{{define "signup"}} <!DOCTYPE html> <html> <head> <title>User SignUp</title> </head> <body> {{template "header"}} <div> Site content </div> {{template "footer"}} </body> </html> {{end}} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
{{define "login"}} <!DOCTYPE html> <html> <head> <title>Login Example</title> </head> <body> {{template "header"}} <div id="login_form"> <form name="f1" method="post" action="/login" id="f1"> <table> <tr> <td class="f1_label">User Name :</td><td><input type="text" name="username" value="" /> </td> </tr> <tr> <td class="f1_label">Password :</td><td><input type="password" name="password" value="" /> </td> </tr> <tr> <td> <input type="submit" name="login" value="Log In" style="font-size:18px; " /> </td> </tr> </table> </form> </div> {{template "footer"}} </body> </html> {{end}} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{{define "logined"}} <!DOCTYPE html> <html> <meta charset="UTF-8"> <head> <title>Now User Logged In!!</title> </head> <body> {{template "header"}} <div> Username: {{.username}} / Password: {{.password}} <br> Authenticated Result: {{.ret_code}} <br> </div> {{template "footer"}} </body> </html> {{end}} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{{define "page"}} <!DOCTYPE html> <html> <meta charset="UTF-8"> <head> <title>Page Example</title> </head> <body> {{template "header"}} <div> Site content <br> Message: {{.}} <br> </div> {{template "footer"}} </body> </html> {{end}} |
ユーザ認証処理
user/userauth.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package user import ( "fmt" "database/sql" _ "github.com/go-sql-driver/mysql" ) type User struct { ID int Name string Pass string } func User_chk(udata map[string]string) bool { db,err := sql.Open("mysql","stuff:%23fv323Ln@tcp(127.0.0.1)/users") if err != nil { panic(err.Error()) } defer db.Close() var user User err = db.QueryRow("SELECT * FROM user WHERE name =? AND pass =?",udata["username"],udata["password"]).Scan(&user.ID,&user.Name,&user.Pass) switch { case err == sql.ErrNoRows: fmt.Println("レコードが存在しません") return false case err != nil: panic(err.Error()) return false default: fmt.Println(user.ID, user.Name,user.Pass) return true } } |
CSS
asset/css/style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#login_form { position: absolute; top: 20%; left: 30%; right: 30%; bottom: 20%; font-size: 18px; } #f1 { background-color: #ccc; border-style: solid; border-width: 1px; } .f1_label { white-space: nowrap; } |
テスト用のデータベース
今回はデバッグしやすいようにパスワードを暗号化していません。次のその2のシリーズで暗号化する予定です。
1 2 3 4 5 6 7 8 9 10 |
CREATE DATABASE users; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` text NOT NULL, `pass` text NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `user` VALUES (1,'john','test001'),(2,'michael','test002'),(3,'rebecca','test003'); |