WEBAPP开发教程Golang构建简单web框架
凌雪 2018-10-11 来源 :网络 阅读 1445 评论 0

摘要:本文将带你了解WEBAPP开发教程Golang构建简单web框架,希望本文对大家学WEBAPP有所帮助。

本文将带你了解WEBAPP开发教程Golang构建简单web框架,希望本文对大家学WEBAPP有所帮助。


使用Golang构建web服务还是比较简单的,使用net/http和gorilla/mux就能快速的构建一个简易的web   server
   
    package main
   
    import {
        "net/http"
    "github.com/gorilla/mux"
    }
   
    func main() {
        router =   mux.NewRouter().StrictSlash(true)
    router.Handle("/",   http.FileServer(http.Dir("/static")))
      http.ListenAndServe(":8080", nil)
    }
   
   
    这样一个简易的静态服务器就构建成功了。
    当然我们不可能就这么满足了,我们当然希望这个服务器是可以处理一些业务逻辑的。比如登录:
   
    router.HandleFunc("/login", handlers.LoginHandler)
   
   
    handler怎么写呢:
   
    func LoginHandler(w http.ResponseWriter, r *http.Request) {
          controllers.LoginIndexAction(w,r);
    }
   
   
    controller(使用mymysql连接数据库):
   
    func LoginAction(w http.ResponseWriter, r *http.Request) {
          w.Header().Set("content-type",   "application/json")
    err := r.ParseForm()
   
        if err != nil {
        Response(w, "Param   error.", "PARAM_ERROR",403)
        return
   
        }
    admin_name      := r.FormValue("admin_name")
    admin_password  :=   r.FormValue("admin_password")
    if admin_name == "" ||   admin_password == ""{
        Response(w, "Param   error.", "PARAM_ERROR",403)
        return
    }
   
        db := mysql.New("tcp",   "", "127.0.0.1:3306", "user", "pass",   "database")
    if err := db.Connect(); err !=   nil {
        log.Println(err)
        Response(w, "Param   error.", "PARAM_ERROR",403)
        return
    }
    defer db.Close()
   
        rows, res, err :=   db.Query("select * from webdemo_admin where admin_name = '%s'",   admin_name)
   
        if err != nil {
        log.Println(err)
        Response(w, "Database   error.", "DATABASE_ERROR",503)
        return
    }
   
        name := res.Map("admin_password")
    admin_password_db :=   rows[0].Str(name)
   
        if admin_password_db !=   admin_password {
        Response(w, "Password   error.", "PASSWORD_ERROR",403)
        return
    }
   
        cookie := http.Cookie{Name:   "admin_name", Value: rows[0].Str(res.Map("admin_name")),   Path: "/"}
   
        http.SetCookie(w,   &cookie)
    Response(w, "Login   success.", "SUCCESS",200)
    return
   
    }
   
    type response struct{
        Status int   `json:"status"`
    Description string   `json:"description"`
    Code string   `json:"code"`
    }
   
    func Response(w http.ResponseWriter, description string,code string, status   int) {
        out := &response{status,   description, code}
    b, err := json.Marshal(out)
    if err != nil {
        return
    }
    w.WriteHeader(status)
    w.Write(b)
    }
   
   
    将用户名放到cookie里就当登录成功了。
    如果有多个路由需要处理呢,情形就会变成这样:
   
    router.HandleFunc("/url1", handlers.Handler1)
    router.HandleFunc("/url2", handlers.Handler1)
    router.HandleFunc("/url3", handlers.Handler1)
    router.HandleFunc("/url4", handlers.Handler1)
    router.HandleFunc("/url5", handlers.Handler1)
    router.HandleFunc("/url6", handlers.Handler1)
    router.HandleFunc("/url7", handlers.Handler1)
    ...
   
   
      好像也无伤大雅,但是如果有更一步的需求,每个URL需要做权限验证,记录日志,这种方式显然就不太合理了,我们需要对router做统一的管理,这里我们跳过了handler层,直接由controller来处理,我觉得更简洁一点。
   
    //先定义Route的结构体
    type Route struct {
     Name        string
     Method      string
     Pattern     string
     Auth bool
     HandlerFunc http.HandlerFunc
    }
   
    type Routes []Route
   
    var routes = Routes{
     Route{
     "url1",
     "GET",
     "/url1",
     true,
     controllers.Url1,
     },
     Route{
     "url2",
     "POST",
     "/url2",
     false,
     controllers.Url2,
     },
    }
   
    var router *mux.Router
   
    func NewRouter() *mux.Router {
            if router == nil {
                router =   mux.NewRouter().StrictSlash(true)
        }
     for _, route := range routes {
     router.
     Methods(route.Method).
     Path(route.Pattern).
     Name(route.Name).
     Handler(route.HandlerFunc)
     }
   
     return router
    }
   
   
    这时候如果添加权限验证,只有通过登录验证的用户才有权限调用,这就需要中间件(我个人比较喜欢称它装饰器)出场了:
   
    func Auth(inner http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)   {
         cookie, err :=   r.Cookie("admin_name")
    if err != nil || cookie.Value ==   ""{
        Response(w, "token not   found.", "AUTH_FAILED",403)
        return;
    }
   
         rows, res, err :=   db.Query("select * from user where user_name= '%d'",   cookie.Value)
   
         if err != nil {
        Response(w, "can not   connect database.", "DB_ERROR",500)
        return
    }
   
         if len(rows) == 0 {
     Response(w, "user not   found.", "NOT_FOUND",404)
     return
    }
   
         row := rows[0]
   
         user := controllers.User{
       User_id:row.Int(res.Map("user_id")),
       User_name:row.Str(res.Map("user_name")),
       User_type:row.Str(res.Map("user_type")),
       Add_time:row.Str(res.Map("add_time"))}
    session.CurrentUser = user
      log.Printf("user_id:%v",controllers.CurrentUser.User_id)
    inner.ServeHTTP(w, r)
     })
    }
   
   
   
    func NewRouter() *mux.Router {
     if router == nil {
                    router =   mux.NewRouter().StrictSlash(true)
        }
     for _, route := range routes {
     if(route.Auth){
     handler = decorates.Auth(route.HandlerFunc)
   
     }
     router.
     Methods(route.Method).
     Path(route.Pattern).
     Name(route.Name).
     Handler(handler)
     }
   
     return router
   
    }
   
   
    显然这样管理session是比较粗糙的,怎么办,有现成的解决方案,jwt(JSON Web   Tokens),我们可以使用jwt-go来生成token,如果一个请求cookie或者header里面含有token,并且可以验证通过,我就认为这个用户是合法用户:
   
    //生成token
    func Generate(key string) (string, error) {
     token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
     "key": key,
     "exp": (time.Now().Add(time.Minute * 60 * 24 *   2)).Unix(),
     })
   
     tokenString, err := token.SignedString(settings.HmacSampleSecret)
     return tokenString, err
    }
    //验证token
    func Valid(tokenString string) (string, error) {
     token1, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{},   error) {
     if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
     return nil, fmt.Errorf("Unexpected signing method: %v",   token.Header["alg"])
     }
     return settings.HmacSampleSecret, nil
     })
   
     if claims, ok := token1.Claims.(jwt.MapClaims); ok && token1.Valid   {
     return fmt.Sprintf("%v", claims["key"]), nil
     } else {
     return "", err
     }
   
    }
   
   
    Auth中间件就可以变成下面的样子:
   
    func Auth(inner http.Handler) http.Handler {
   
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)   {
         cookie, err := r.Cookie("token")
    if err != nil || cookie.Value ==   ""{
        Response(w, "token not   found.", "AUTH_FAILED",403)
        return;
    }
   
         user_id, err :=   token.Valid(cookie.Value)
   
         if err != nil {
        Response(w, "bad   token.", "AUTH_FAILED",403)
        return;
    }
   
         rows, res, err :=   db.Query("select * from user where user_id= '%d'", user_id)
   
         if err != nil {
        Response(w, "can not   connect database.", "DB_ERROR",500)
        return
    }
   
         if len(rows) == 0 {
     Response(w, "user not   found.", "NOT_FOUND",404)
     return
    }
   
         row := rows[0]
   
         user := controllers.User{
     User_id:row.Int(res.Map("user_id")),  
       User_name:row.Str(res.Map("user_name")),
       User_type:row.Str(res.Map("user_type")),
       Add_time:row.Str(res.Map("add_time"))}
   
         session.CurrentUser = user
   
         log.Printf("user_id:%v",controllers.CurrentUser.User_id)
    inner.ServeHTTP(w, r)
     })
    }
   
   
    我们还可以对每个URL实现log记录:
   
    func Logger(inner http.Handler, name string) http.Handler {
   
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)   {
     start := time.Now()
     inner.ServeHTTP(w, r)
   
     log.Printf(
     "%s\t%s\t%s\t%s",
     r.Method,
     r.RequestURI,
     name,
     time.Since(start),
     )
     })
    }
   
    func NewRouter() *mux.Router {
     if router == nil {
                    router = mux.NewRouter().StrictSlash(true)
        }
     for _, route := range routes {
     var handler http.Handler = decorates.Logger(route.HandlerFunc,   route.Name)
     if(route.Auth){
     handler = decorates.Auth(handler)
     }
     router.
     Methods(route.Method).
     Path(route.Pattern).
     Name(route.Name).
     Handler(handler)
     }
     return router
    }
   
   
    有跨域的需求?好办:
   
    func CorsHeader(inner http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)   {
   
             w.Header().Set("Access-Control-Allow-Origin",   r.Header.Get("Origin"))
           w.Header().Set("Access-Control-Allow-Credentials",   "true")
      w.Header().Add("Access-Control-Allow-Method","POST,   OPTIONS, GET, HEAD, PUT, PATCH, DELETE")
   
           w.Header().Add("Access-Control-Allow-Headers","Origin,   X-Requested-With, X-HTTP-Method-Override,accept-charset,accept-encoding ,   Content-Type, Accept, Cookie")
   
             w.Header().Set("Content-Type","application/json")
     inner.ServeHTTP(w, r)
     })
   
    }
   
    func NewRouter() *mux.Router {
     if router == nil {
            router = mux.NewRouter().StrictSlash(true)
    }
     for _, route := range routes {
     var handler http.Handler = decorates.Logger(route.HandlerFunc,   route.Name)
     if(route.Auth){
     handler = decorates.Auth(handler)
     }
     handler = decorates.CorsHeader(handler)
     router.
     Methods(route.Method).
     Path(route.Pattern).
     Name(route.Name).
     Handler(handler)
     router.
     Methods("OPTIONS").
     Path(route.Pattern).
     Name("cors").
     Handler(decorates.CorsHeader(http.HandlerFunc(func(w   http.ResponseWriter, r *http.Request) {
     return
     })))
     }
   
     return router
    }
   
   
      session管理好像还有一些问题,每个request请求都会改变全局的CurrenUser,如果有并发的情况下,这就容易产生混乱了,可以需要用户信息的时候通过token去数据库来取,效率会有影响,但并发的问题可以解决了:
   
    func CurrentUser(r *http.Request) *models.User {
     cookie, err := r.Cookie("token")
     if err != nil || cookie.Value == "" {
     return &models.User{}
     }
     key, err := token.Valid(cookie.Value)
     if err != nil {
     return &models.User{}
     }
     if !strings.Contains(key, "|") {
     return &models.User{}
     }
     keys := strings.Split(key, "|")
     rows, res, err := db.QueryNonLogging("select * from user where   user_id = '%v' and user_pass = '%v'", keys[0], keys[1])
   
     if err != nil {
     return &models.User{}
     }
   
     if len(rows) == 0 {
     return &models.User{}
     }
     row := rows[0]
     user := models.User{
     User_id:     row.Int(res.Map("user_id")),
     User_name: row.Str(res.Map("user_name")),
     User_type: row.Str(res.Map("user_type")),
     Add_time:    row.Str(res.Map("add_time"))}
   
     return &user
   
    }
   
   
    日志的问题好像还没有解决,毕竟日志需要写到文件里面并且需要一些详细的信息,比如行号,文件,才能利于排查问题,或者做统计:
   
    func Printf(format string, params ...interface{}) {
     _, f, line, _ := runtime.Caller(1)
     log.Printf(format, params...)
     file, err := os.OpenFile(settings.LogFile,   os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
     if err != nil {
     log.Printf("%v", err)
     return
     }
     defer file.Close()
     _, err = file.Seek(0, os.SEEK_END)
     if err != nil {
     return
     }
     args := strings.Split(f, "/")
     f = args[len(args)-1]
     msg := fmt.Sprintf("%v:%v(%v)", line, format, f)
     logger := log.New(file, "", log.LstdFlags)
     logger.Printf(msg, params...)
    }
   
    func Println(v ...interface{}) {
     _, f, line, _ := runtime.Caller(1)
     log.Println(v...)
     file, err := os.OpenFile(settings.LogFile,   os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
     if err != nil {
     log.Printf("%v", err)
     return
     }
     defer file.Close()
     _, err = file.Seek(0, os.SEEK_END)
     if err != nil {
     return
     }
     args := strings.Split(f, "/")
     f = args[len(args)-1]
     msg := fmt.Sprintf("%v:%v(%v)", line, fmt.Sprintln(v...),   f)
     logger := log.New(file, "", log.LstdFlags)
     logger.Println(msg)
   
    }
   
   
      日志写到文件的问题解决了,又面临新的问题,日志文件太大,怎么办,需要归档(每隔12小时就查看一下日志文件多大了,如果太大了就压缩一下归档):
   
    var ticker = time.NewTicker(time.Minute * 60 * 12)
   
    func init() {
     go func() {
     for _ = range ticker.C {
     archive()
     }
     }()
   
    }
   
    func archive() error {
     info, _ := os.Stat(settings.LogFile)
     if info.Size() > 1024*1024*50 {
     target := fmt.Sprintf("%v.%v.tar.gz",
     shortFileName(settings.LogFile),
     time.Now().Format("2006-01-02-15-04"),
     )
     tmp := fmt.Sprintf("%v.%v.tmp",
     shortFileName(settings.LogFile),
     time.Now().Format("2006-01-02-15-04"),
     )
     in := bytes.NewBuffer(nil)
     cmd := exec.Command("sh")
     cmd.Stdin = in
     go func() {
     in.WriteString(fmt.Sprintf("cd %v\n",   shortFileDir(settings.LogFile)))
     in.WriteString(fmt.Sprintf("cp %v %v\n",   shortFileName(settings.LogFile), tmp))
     in.WriteString(fmt.Sprintf("echo '' > %v\n",   shortFileName(settings.LogFile)))
     in.WriteString(fmt.Sprintf("tar -czvf %v %v\n", target,   tmp))
     in.WriteString(fmt.Sprintf("rm %v\n", tmp))
     in.WriteString("exit\n")
     }()
     if err := cmd.Run(); err != nil {
     fmt.Println(err)
     return err
     }
     }
     return nil
    }
   
   
    基本的功能好像都能解决了,饱暖思淫欲,错误处理感觉用起来不怎么舒服,有更优雅的办法:
   
    type Handler func(http.ResponseWriter, *http.Request)   *models.APPError
   
    func (fn Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
     if e := fn(w, r); e != nil {
     utils.Response(w, e.Message, e.Code, e.Status)
     }
   
    }
   
   
    //装饰器就变成了这样
    func (inner Handler) Auth() Handler {
     return Handler(func(w http.ResponseWriter, r *http.Request)   *models.APPError {
     tokenString := ""
     cookie, _ := r.Cookie("token")
     if cookie != nil {
     tokenString = cookie.Value
     }
     if tokenString == "" {
     if r.Header != nil {
     if authorization := r.Header["Authorization"];   len(authorization) > 0 {
     tokenString = authorization[0]
     }
     }
     }
     key, err := token.Valid(tokenString)
     if err != nil {
     return &models.APPError{err, "bad token.",   "AUTH_FAILED", 403}
     }
     if !strings.Contains(key, "|") {
     return &models.APPError{err, "user not found.",   "NOT_FOUND", 404}
     }
     keys := strings.Split(key, "|")
     rows, _, err := db.QueryNonLogging("select * from user where user_id   = '%v' and user_pass = '%v'", keys[0], keys[1])
     if err != nil {
     return &models.APPError{err, "can not connect database.",   "DB_ERROR", 500}
     }
     if len(rows) == 0 {
     return &models.APPError{err, "user not found.",   "NOT_FOUND", 404}
     }
     go log.Printf("user_id:%v", keys[0])
     inner.ServeHTTP(w, r)
     return nil
     })
    }
    //router画风也变了
    type Route struct {
     Name        string
     Method      string
     Pattern     string
     HandlerFunc Handler
     ContentType string
    }
   
    type Routes []Route
    var BRoutes = Routes{
     Route{
     "nothing",
     "GET",
     "/",
     Config,
     contenttype.JSON,
     },
     Route{
     "authDemo",
     "GET",
     "/demo1",
     Handler(Config).
     Auth(),
     contenttype.JSON,
     },
     Route{
     "verifyDemo",
     "GET",
     "/demo2",
     Handler(Config).
     Verify(),
     contenttype.JSON,
     },
     Route{
     "verifyAndAuthDemo",
     "GET",
     "/demo3",
     Handler(Config).
     Auth().
     Verify(),
     contenttype.JSON,
     },
    }
   
   
    这样基本的web框架就完成了,想添加一些命令行工具,比如测试,自动生成app,推荐用kingpin来实现:
   
    var (
     app      =   kingpin.New("beauty", "A command-line tools of   beauty.")
     demo     =   app.Command("demo", "Demo of web server.")
     generate = app.Command("generate", "Generate a new   app.")
     name     =   generate.Arg("name", "AppName for   app.").Required().String()
    )
   
    func main() {
     switch kingpin.MustParse(app.Parse(os.Args[1:])) {
     case generate.FullCommand():
     GOPATH := os.Getenv("GOPATH")
     appPath := fmt.Sprintf("%v/src/%v", GOPATH, *name)
     origin :=   fmt.Sprintf("%v/src/github.com/yang-f/beauty/etc/demo.zip",   GOPATH)
     dst := fmt.Sprintf("%v.zip", appPath)
     _, err := utils.CopyFile(dst, origin)
     if err != nil {
     fmt.Println(err.Error())
     }
     utils.Unzip(dst, appPath)
     os.RemoveAll(dst)
     helper := utils.ReplaceHelper{
     Root:    appPath,
     OldText: "{appName}",
     NewText: *name,
     }
     helper.DoWrok()
     log.Printf("Generate %s success.", *name)
     case demo.FullCommand():
     log.Printf("Start server on port %s", settings.Listen)
     router := router.NewRouter()
     log.Fatal(http.ListenAndServe(settings.Listen, router))
     }
   
    }
   
   
    执行命令行是这样的:
   
    usage: beauty [<flags>] <command> [<args> ...]
   
    A command-line tools of beauty.
   
    Flags:
      --help  Show context-sensitive help (also try   --help-long and --help-man).
   
    Commands:
      help [<command>...]
    Show help.
   
      demo
    Demo of web server.
   
      generate <name>
   
        Generate a new app.
   
   
    到此,这个框架还在不断的优化中,希望能有人提供宝贵的批评和建议。
    以下是代码地址:
    yang-f/beauty谢谢!    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之WebApp频道!

本文由 @凌雪 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved