2015-11-18

(Go) 웹서비스에 POST 요청으로 값 넘겨서 처리하기



import (
    "net/http"
    "net/url"
)
 
func reqPost() {
    postUrl := "http://example.com/test"
    values := make(url.Values)
    values.Set("name", "HongGilDong")
    values.Set("age", 31)
    req, _ := http.PostForm(postUrl, values)
    defer req.Body.Close()
}


(beego) JSON Response 작성하기



  • 샘플 코드
    func (c *HappeningController) Get() {
     data := map[string]interface{}{
      "test":  "test",
      "num":   23,
      "array": []string{"one", "two"},
     }
     c.Data["json"] = &data
     c.ServeJson()
    }
  • 결과
    {
        "test":"test",
        "number": 23,
        "array":["one", "two"]
    }

2015-11-13

(beego) router 작성시 주의할 점



  • router 작성시에 하나의 컨트롤러에 여러 URL 을 매핑하는 경우, urlfor 가 예측할 수 없을 정도로 비정상적으로 작동한다. 예를 들어, 다음과 같이 router 를 작성했다고 하자.
        ...
    
        beego.Router("/mgmt/user", &c_mgmt.UserController{}, "get:Index")
        beego.Router("/mgmt/user/:page", &c_mgmt.UserController{}, "get,post:Index")
        beego.Router("/mgmt/user/:page/:s_item/:val", &c_mgmt.UserController{}, "get:Index")
    
        ...
  • View 의 템플릿에서
    {{urlfor ""mgmt.UserController.Index" "1"}}
    를 작성한 경우, /mgmt/user/1 이 호출되어야 겠지만, 실제로는 전혀 다른 값이 호출된다는 것이다. 반면, URL을 호출한 경우에는 매핑된 컨트롤러가 제대로 실행된다. 그러므로, 정상적인 동작을 위해서는 URL 과 컨트롤러는 1:1 로 매핑해야 한다.

2015-11-03

(Go) Get local IP addresses


  • Sample Code
    myip.go
    package main
     
    import (
     "fmt"
     "net"
    )
     
    func main() {
     addrs, _ := net.InterfaceAddrs()
     
     for _, addr := range addrs {
      if addr.Network() == "ip+net" {
       if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
        if ipnet.IP.To4() != nil {
         fmt.Println(ipnet.IP.String())
        }
       }
      } else if addr.Network() == "ip" {
       if addr.String() != "0.0.0.0" {
        fmt.Println(addr.String())
       }
      }
     }
    }

2015-10-22

beego Tutorial

beego 를 공부해보다가, 삽질을 조금 줄여보고자 튜토리얼을 급하게 만들어보았습니다. 개인 위키에 있던 것을 그대로 복사해 놓아서 설명도 적고, 내용도 부족하지만, 그래도 초짜에게는 도움이 될 것 같아요. *^^*




beego Tutorial 11 : 로그인/로그아웃 처리하기


  • 명색이 관리자 화면인데, 아무나 들어와서 조작하면 안되겠지요? 이제는 마지막으로 로그인/로그아웃 처리를 해보겠습니다. 로그인/로그아웃은 세션을 이용해서 구현합니다.
  • 로그인 화면을 구성합니다. xyz/views/index.html 을 작성합니다.
    <!DOCTYPE html>
    <html lang="ko">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="_xsrf" content="{{.xsrf_token}}" />
    <title>Login</title>
    <link rel="stylesheet" href="/static/b/css/bootstrap.min.css">
    </head>
    <body>
     
    <div class="container" style="margin-top: 20px">
      <form action="/login" method="post" class="form-horizontal" style="margin: 0 auto; max-width: 360px;">
        {{ .xsrfdata }}
        <div class="form-group">
          <label for="userid" class="col-sm-3 control-label">아이디</label>
          <div class="col-sm-9">
            <input type="text" id="userid" name="userid" class="form-control" placeholder="당신의 ID를 입력하세요..." required autofocus>
          </div>
        </div>
        <div class="form-group">
          <label for="passwd" class="col-sm-3 control-label">비밀번호</label>
          <div class="col-sm-9">
            <input type="password" id="passwd" name="passwd" class="form-control" placeholder="비밀번호를 입력하세요..." required>
          </div>
        </div>
        <input type="submit" class="btn btn-primary btn-block" value="로그인" />
      </form>
    </div>
     
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="/static/b/js/bootstrap.min.js"></script>
    </body>
    </html>
  • xyz/controllers/main.go 를 다음처럼 수정합니다.
    package controllers
     
    import (
        "github.com/astaxie/beego"
        "html/template"
    )
     
    type MainController struct {
        beego.Controller
    }
     
    // 메인 로그인 화면
    func (c *MainController) Index() {
        c.Data["xsrfdata"] = template.HTML(c.XsrfFormHtml())
        c.TplNames = "index.html"
    }
  • xyz/conf/app.conf 에 다음을 추가합니다.
    ...
    
    sessionon = true
    
    ...
  • xyz/models/admin.go 에 다음을 추가합니다.
    ...
     
    func (this *Admin) IsMember(userid string, passwd string) bool {
        sha := sha512.New()
        sha.Write([]byte(passwd))
        hashed_passwd := hex.EncodeToString(sha.Sum(nil))
     
        o := orm.NewOrm()
        o.Using("default")
        cnt, _ := o.QueryTable(this).Filter("userid", userid).Filter("password", hashed_passwd).Count()
     
        if cnt == 0 {
            return false
        }
     
        return true
    }
     
    ...
  • xyz/controllers/main.go 에 xyz/models 를 import 하고, 다음을 추가한다.
    ...
     
    // 로그인
    func (c *MainController) Login() {
        userid := c.GetString("userid")
        passwd := c.GetString("passwd")
     
        admin := new(models.Admin)
        if is_member := admin.IsMember(userid, passwd); is_member == true {
            c.SetSession("login", true) // 로그인세션 등록
            c.Redirect(c.UrlFor("AdminController.Index"), 302)
            return
        }
     
        c.Redirect(c.UrlFor(".Index"), 302)
    }
     
    ...
  • xyz/routers/router.go 에 다음을 추가한다.
    ...
     
    beego.Router("/login", &c.MainController{}, "post:Login")
     
    ...
  • xyz/main.go 에 github.com/astaxie/beego/context 를 import 하고 다음을 Init() 안의 마지막에 추가한다.
    ...
     
        // Authetication
        var FilterUser = func(ctx *context.Context) {
            _, login := ctx.Input.Session("login").(bool)
            if login != true {
                ctx.Redirect(302, "/")
            }
        }
        beego.InsertFilter("/admin", beego.BeforeRouter, FilterUser)
        beego.InsertFilter("/admin/*", beego.BeforeRouter, FilterUser)
     
    ...
    여기까지 작성하면, 로그인해야만 http://localhost:8080/admin 을 접근할 수 있게 된다.
  • xyz/views/admin/index.html 에 다음을 적당한 곳에 추가한다.
    ...
     
    <a href="/logout">[로그아웃]</a>
     
    ...
  • xyz/controllers/main.go 에 다음을 추가한다.
    ...
     
    // 로그아웃
    func (c *MainController) Logout() {
        c.DestroySession()
        c.Redirect(c.UrlFor(".Index"), 302)
    }
     
    ...
  • xyz/routers/router.go 에 다음을 추가한다.
    ...
     
    beego.Router("/logout", &c.MainController{}, "get:Logout")
     
    ...
    이제, 로그인도 해보고, 로그아웃도 해보세요. ^^


beego Tutorial 10 : 개인정보 양방향암호화(AES256) 하기



  • 이 예제에는 개인정보 데이터가 없긴 하지만, 별명(Nick)을 개인정보라 간주하고 암호화를 진행해보겠습니다.
  • 데이터를 암호화하고 다시 풀려면, Key 가 있어야 합니다. 이 Key 를 xyz/conf/app.conf 에 기록해 놓습니다.
    ...
    
    enckey = oei0oiejhdsa7AS6dkjSk3l5dkwHG659
    
    ...
  • xyz/models/common.go 를 수정합니다.
    package models
     
    import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/rand"
        "encoding/base64"
        "fmt"
        "github.com/astaxie/beego"
        "github.com/astaxie/beego/orm"
        "io"
    )
     
    var key []byte
     
    func init() {
        key = []byte(beego.AppConfig.String("enckey"))
        orm.RegisterModelWithPrefix("tb_", new(Admin))
    }
     
    func encodeBase64(b []byte) []byte {
        return []byte(base64.StdEncoding.EncodeToString(b))
    }
     
    func decodeBase64(b []byte) []byte {
        data, err := base64.StdEncoding.DecodeString(string(b))
        if err != nil {
            fmt.Printf("Error: Bad Key!\n")
            return nil
        }
        return data
    }
     
    func encrypt(text []byte) []byte {
        // The key argument should be the AES key,
        // either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
        block, err := aes.NewCipher(key)
        if err != nil {
            panic(err)
        }
        b := encodeBase64(text)
        ciphertext := make([]byte, aes.BlockSize+len(b))
        iv := ciphertext[:aes.BlockSize]
        if _, err := io.ReadFull(rand.Reader, iv); err != nil {
            panic(err)
        }
        cfb := cipher.NewCFBEncrypter(block, iv)
        cfb.XORKeyStream(ciphertext[aes.BlockSize:], b)
        return ciphertext
    }
     
    func decrypt(text []byte) []byte {
        block, err := aes.NewCipher(key)
        if err != nil {
            panic(err)
        }
        if len(text) < aes.BlockSize {
            fmt.Printf("Error!\n")
            return nil
        }
        iv := text[:aes.BlockSize]
        text = text[aes.BlockSize:]
        cfb := cipher.NewCFBDecrypter(block, iv)
        cfb.XORKeyStream(text, text)
        return decodeBase64(text)
    }
  • xyz/models/admin.go 에 다음을 추가한다.
    func (this *Admin) SetNick(nick string) {
        ciphertext := encrypt([]byte(nick))
        this.Nick = hex.EncodeToString(ciphertext)
    }
     
    func (this *Admin) GetNick() string {
        ciphertext, _ := hex.DecodeString(this.Nick)
        plaintext := decrypt(ciphertext)
        return string(plaintext)
    }
    Insert()Update() 에서 this.Nick = nick 이라고 되어 있는 부분을 this.SetNick(nick) 으로 변경한다.
  • xyz/views/admin 밑에 있는 index.html 과 update_form.html 파일에서 Nick 이라고 되어 있는 부분을 모두 GetNick 으로 변경한다.
  • DB의 내용을 모두 삭제한 후에, 관리자를 추가해봅니다. 그리고, DB에서 Nick 이 암호화되어 있는지도 봅시다.
    $ sqlite3 xyz.db
    SQLite version 3.7.9 2011-11-01 00:52:41
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> select nick from tb_admin;
    2e4cccdfe968a2b127bff7eac978df2354c0bb7c25e8534d
    sqlite> .quit
    제대로 암호화되어서 무슨 내용인지 알아볼 수 없네요. ^^;


beego Tutorial 9 : 비밀번호 단방향암호화(SHA512) 하기



  • 정보통신망법, 개인정보보호법 등에 의하면 비밀번호는 단방향암호화(SHA256 등), 개인정보는 양방향암호화(AES256 등)를 하도록 되어 있습니다. 지금까지 작성한 예제는 이런 법에 맞지 않아서 서비스할 수 없겠네요. 여기에서는 비밀번호의 단방향 암호화를 구현해보도록 하겠습니다.
  • 먼저, 기존 DB에 들어있던 사용자 정보를 모두 삭제합시다.
    $ cd $GOPATH/src/xyz
    $ sqlite xyz.db
    SQLite version 3.7.9 2011-11-01 00:52:41
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> select * from tb_admin;
    1|testid1|passwd1|nick1
    2|testid2|passwd2|nick2
    3|testid3|passwd3|nick3
    4|testid4|passwd4|nick4
    6|admin|dpeltms3|ADMIN
    sqlite> delete from tb_admin;
    sqlite> select * from tb_admin;
    sqlite> .quit
  • xyz/models/admin.go 에 다음을 추가합니다. 이 부분이 실제로 SHA512 로 단방향암호화를 하는 부분입니다.
    ...
     
    import (
        "github.com/astaxie/beego/orm"
        "crypto/sha512"
        "encoding/hex"
    )
     
    ...
     
    func (this *Admin) SetPassword(passwd string) {
        sha := sha512.New()
        sha.Write([]byte(passwd))
        this.Password = hex.EncodeToString(sha.Sum(nil))
    }
     
    ...
    그리고, this.password = passwd 라고 되어 있는 부분도 this.SetPassword(passwd) 로 변경해줍니다. Insert() 와 Chg_password() 에 있습니다.
  • 이제, 관리자를 추가해보고, DB에서 비밀번호가 어떻게 들어가 있는지 확인해봅니다.
    $ sqlite3 xyz.db
    SQLite version 3.7.9 2011-11-01 00:52:41
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> select * from tb_admin;
    8|myid|d49434a7e6f3220b6ec15bff84429934d519d0443d20ff46ff3a36e1fc50fc2d43c939d259042987162202dcbbc8788752ea79c02379064ab64363b069278c32|마이이이디
    sqlite> .quit
    비밀번호가 사람이 알아볼 수 없는 형태로 되어 있지요? 그러면 잘 된 것입니다. *^^*


beego Tutorial 8 : 관리자 삭제하기



  • xyz/views/admin/index.html 에 다음을 추가한다.
    ...
     
    <button onclick="delete_admin('/admin/delete/{{$admins.Id}}')" class="btn btn-default btn-xs">삭제</button>
     
    ...
     
    <script>
    function delete_admin(url) {
        var result = confirm("관리자를 정말로 삭제하시겠습니까?");
        if( result == false ) return;
        location.href = url;
    }
    </script>
     
    ...
  • xyz/models/admin.go 에 다음을 추가한다.
    ...
     
    func (this *Admin) Delete(id int) {
        o := orm.NewOrm()
        o.Using("default")
        this.Id = id
        o.Delete(this)
    }
     
    ...
  • xyz/controllers/admin.go 에 다음을 추가한다.
    // 관리자 삭제
    func (c *AdminController) Delete() {
        id, _ := strconv.Atoi(c.Ctx.Input.Param(":id"))
        admin := new(models.Admin)
        admin.Delete(id)
     
        c.Redirect(c.UrlFor(".Index"), 302)
    }
  • xyz/routers/router.go 에 다음을 추가하고, 관리자 삭제를 해봅니다.
    beego.Router("/admin/delete/:id", &c.AdminController{}, "get:Delete")
  • 여기까지 하면, 기본적인 CRUD 기능을 모두 작성할 수 있게 됩니다. *^^*