SEO対策とセキュリティで企業をバックアップします。

ITエンジニアの技術力UPをお約束します。

Go言語初心者向けWEB講座その1:neoフレームワーク (簡単な認証、サインアップ、ログインを準備)

Go言語初心者向けWEB講座その1:neoフレームワーク (簡単な認証、サインアップ、ログインを準備)


Go言語も様々なフレームワークがありますが、あえて情報ソースが少なく学習コストが低いものを選んでみました。このneoは単純で1日で覚えられます。フルスタックのフレームワークならRevelを使うと思いますが、どの言語もメジャーなものを選択するとちゃんと理解しないでコピペで終わってしまいそうなので私は常にマイナーなものを選びます。言語を覚えているのか、フレームワークを覚えているのかがわからなくなるのでGo言語に限らずどの言語もフレームワークやその言語を深く理解しようと思う場合、マイナーなものを選択します。そうすることで後々の改造や言語を詳しくしるきっかけになるからです。

neoフレームワークのインストール

$ go get github.com/ivpusic/neo

最初にneoフレムワークをダウンロードしプロジェクトフォルダーmyappを自動生成させます。

$ 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
#
# 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
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
{{define "header"}}
<p>This is header</p>
{{end}}
templates/footer.tpl
{{define "footer"}}
<p>This is footer</p>
{{end}}
templates/index.tpl
{{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}}
signup.tpl
{{define "signup"}}
<!DOCTYPE html>
<html>
    <head>
        <title>User SignUp</title>
    </head>

    <body>
        {{template "header"}}
        <div>
            Site content
        </div>
        {{template "footer"}}
    </body>
</html>
{{end}}
login.tpl
{{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}}
logined.tpl
{{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}}
pages.tpl
{{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
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
#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のシリーズで暗号化する予定です。

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');

タグ: , , ,