zoukankan      html  css  js  c++  java
  • 使用Golang 搭建http web服务器

    Golang在搭建web服务器方面的能力是毋庸置疑的。官方已经有提供net/http包为搭建http服务器做准备。使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置。至于这个包是否好用,这个就见仁见智了。你可以从net包开始封装一个web框架,当然也可以基于http包封装一个web框架。但是不论你是打算怎么样做,了解基本的net/http包一定是你借鉴的基础。

    需求

    我们要做两个简单的后台web系统。这个系统简单到只有两个页面:登陆和首页。

    1 登陆页面

    clip_image001[4]

    登陆页面需要提交用户名和密码,将用户名和密码和mysql数据库中的用户名密码比对达到验证的效果。mysql数据库的go语言驱动推荐使用mymysql(https://github.com/ziutek/mymysql)。

    当用户名和密码正确的时候,需要在cookie中种下用户名和加密后的密钥来进行cookie认证。我们不对cookie设置ExpireTime,这样这个cookie的有效期就是浏览器打开到浏览器关闭的session期间。

    另外,这个页面还需要加载一个js。提交用户名和密码的是由js进行ajax post命令进行查询的。

    这个页面需要加载css,进行页面排版

    2 首页

    clip_image002[4]

    首页是非常简单,但它是一个动态页面。

    首先右上角的”欢迎登陆, 管理员:yejianfeng“。这里的用户名yejianfeng是根据不同的用户会进行变化的。这里需要用到模版,我们又会用到了一个模版包html/template。这个包的作用就是加载模版。

    其次这个页面也需要的是css,js(退出系统的删除cookie操作由js控制)

    路由

    分析下访问路径,会有几个文件:

    /admin/index -- 首页

    /login/index --登陆页显示

    /ajax/login -- 登陆的ajax动作

    /css/main.css -- 获取main的css文件

    /js/login.js -- 获取登陆的js文件

    在net/http包中,动态文件的路由和静态文件的路由是分开的,动态文件使用http.HandleFunc进行设置静态文件就需要使用到http.FileServer

    package main
    
    import (
        "net/http"
    )
    
    func main() {
        http.Handle("/css/", http.FileServer(http.Dir("template")))
        http.Handle("/js/", http.FileServer(http.Dir("template")))
        
        http.HandleFunc("/admin/", adminHandler)
        http.HandleFunc("/login/",loginHandler)
        http.HandleFunc("/ajax/",ajaxHandler)
        http.HandleFunc("/",NotFoundHandler)
        http.ListenAndServe(":8888", nil)
    
    }

    这里的http.FileServer(http.Dir("template"))的路径是怎么算的要注意下了。相对路径都是从当前执行路径路径下开始算的,这里设置的路径树是这样:

    clip_image003[4]

    关于http.HandleFunc如果不理解请参考我的上一篇文章

    http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html

    处理器

    这里需要定制4个handler对应相应的一级路径。我们将这些个handler都放入到route.go文件中

    页面404处理器

    main中的逻辑是当/admin/ /login/ /ajax/都不符合路径的时候就进入404页面处理器NotFoundHandler

    func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/" {
            http.Redirect(w, r, "/login/index", http.StatusFound)
        }
        
        t, err := template.ParseFiles("template/html/404.html")
        if (err != nil) {
            log.Println(err)
        }
        t.Execute(w, nil)
    
    }

    这段逻辑是很清晰的:如果路径是"/" 即访问路径是http://192.168.100.166:8888/ 的时候,访问会跳转到登陆页面,否则当访问路径不满足制定的路由,读取404模版,显示404。

    强烈推荐使用template.ParseFile模版文件解析

    template包中也提供了Parse这类直接在代码中写上模版的函数。使用起来,你会发现这类函数远没有ParseFile好用,原因很明显,如果你把模版写在代码中,一旦模版需要进行小细节的修改,也需要重新编译。并且我们使用模版的目的就是将显示逻辑和业务逻辑分离,Parse会导致整个代码是不可维护!

    当然有人会考虑到效率问题,一个是读取文件,一个是直接读取内存。但是我觉得这点效率差别用户根本不会察觉到,牺牲极小的性能保证工程性是很值得的。

    ParseFile中的路径问题也是很容易犯的问题。这里的填写相对路径,则文件是从当前执行的路径开始算。

    clip_image004[4]

    比如这个路径,执行bin/webdemo,template/html/404.html就对应/go/gopath/template/html/404.html

    这一步后续动作是在template/html文件夹中创建404.html页面

    登陆页面处理器

    func loginHandler(w http.ResponseWriter, r *http.Request) {
        pathInfo := strings.Trim(r.URL.Path, "/")
        parts := strings.Split(pathInfo, "/")
        var action = ""
        if len(parts) > 1 {
            action = strings.Title(parts[1]) + "Action"
        }
    
        login := &loginController{}
        controller := reflect.ValueOf(login)
        method := controller.MethodByName(action)
        if !method.IsValid() {
            method = controller.MethodByName(strings.Title("index") + "Action")
        }
        requestValue := reflect.ValueOf(r)
        responseValue := reflect.ValueOf(w)
        method.Call([]reflect.Value{responseValue, requestValue})
    }

    根据MVC思想,对具体的逻辑内容使用不同的Controller,这里定义了一个loginController, 使用reflect将pathInfo映射成为controller中的方法。

    这样做的好处显而易见:

    1 清晰的文件逻辑。不同的一级目录分配到不同的控制器,不同的控制器中有不同的方法处理。这些控制器放在一个独立的文件中,目录结构清晰干净。

    2 路由和业务逻辑分开。 main中的http.HandleFunc处理一级路由,route处理二级路由,controller处理业务逻辑,每个单元分开处理。

    好了,下面需要定义loginContrller,我们另外起一个文件loginController.go

    package main
    
    import (
        "net/http"
        "html/template"
    )
    
    type loginController struct {
    }
    
    func (this *loginController)IndexAction(w http.ResponseWriter, r *http.Request) {
        t, err := template.ParseFiles("template/html/login/index.html")
        if (err != nil) {
            log.Println(err)
        }
        t.Execute(w, nil)
    }

    下面需要创建template/html/login/index.html

    这个文件中包含mian.css, jquery.js, base.js。 创建这些css和js。具体的css和js内容请看github源码

    js中的逻辑是这样写的:当login表单提交的时候,会调用/ajax/login进行验证操作,下面就开始写ajax的处理器

    ajax处理器

    route中的ajaxHandler和loginHandler是大同小异,这里就忽略不说了,具体说一下ajaxController

    package main
    
    import (
        "net/http"
        "github.com/ziutek/mymysql/autorc"
        _ "github.com/ziutek/mymysql/thrsafe"
        "encoding/json"
    )
    
    type Result struct{
        Ret int
        Reason string
        Data interface{}
    }
    
    type ajaxController struct {
    }
    
    func (this *ajacController)LoginAction(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("content-type", "application/json")
        err := r.ParseForm()
        if err != nil {
            OutputJson(w, 0, "参数错误", nil)
            return
        }
        
        admin_name := r.FormValue("admin_name")
        admin_password := r.FormValue("admin_password")
        
        if admin_name == "" || admin_password == ""{
            OutputJson(w, 0, "参数错误", nil)
            return
        }
        
        db := mysql.New("tcp", "", "192.168.199.128", "root", "test", "webdemo")
        if err := db.Connect(); err != nil {
            OutputJson(w, 0, "数据库操作失败", nil)
            return
        }
        defer db.Close()
        
        rows, res, err := db.Query("select * from webdemo_admin where admin_name = '%s'", admin_name)
        if err != nil {
            OutputJson(w, 0, "数据库操作失败", nil)
            return
        }
        
        name := res.Map("admin_password")
        admin_password_db := rows[0].Str(name)
        
        if admin_password_db != admin_password {
            OutputJson(w, 0, "密码输入错误", nil)
            return
        }
        
        // 存入cookie,使用cookie存储
        expiration := time.Unix(1, 0)
        cookie := http.Cookie{Name: "admin_name", Value: rows[0].Str(res.Map("admin_name")), Path: "/"}
        http.SetCookie(w, &cookie)
        
        OutputJson(w, 1, "操作成功", nil)
        return
    }
    
    func OutputJson(w http.ResponseWriter, ret int, reason string, i interface{}) {
        out := &Result{ret, reason, i}
        b, err := json.Marshal(out)
        if err != nil {
            return
        }
        w.Write(b)
    }

    这段代码有几个地方可以看看:

    如何设置header:

    w.Header().Set("content-type", "application/json")

    如何解析参数:

    err := r.ParseForm()

    admin_name := r.FormValue("admin_name")

    如何连接数据库

    db := mysql.New("tcp", "", "192.168.199.128", "root", "test", "webdemo")

    当然这里得需要有个数据库和数据表,建表和建表的sql文件在github上能看到

    create table webdemo_admin

    (

    admin_id int not null auto_increment,

    admin_name varchar(32) not null,

    admin_password varchar(32) not null,

    primary key(admin_id)

    );

    如何设置cookie

    cookie := http.Cookie{Name: "admin_name", Value: rows[0].Str(res.Map("admin_name")), Path: "/"}

    http.SetCookie(w, &cookie)

    主页处理器

    adminHandler的逻辑比其他Handler多的一个是需要 获取cookie

    cookie, err := r.Cookie("admin_name")

    并且在传递给controller的时候需要将admin_name传递进去

    func adminHandler(w http.ResponseWriter, r *http.Request) {
        // 获取cookie
        cookie, err := r.Cookie("admin_name")
        if err != nil || cookie.Value == ""{
            http.Redirect(w, r, "/login/index", http.StatusFound)
        }
        
        pathInfo := strings.Trim(r.URL.Path, "/")
        parts := strings.Split(pathInfo, "/")
        
        admin := &adminController{}
        controller := reflect.ValueOf(admin)
        method := controller.MethodByName(action)
        if !method.IsValid() {
            method = controller.MethodByName(strings.Title("index") + "Action")
        }
        requestValue := reflect.ValueOf(r)
        responseValue := reflect.ValueOf(w)
        userValue := reflect.ValueOf(cookie.Value)
        method.Call([]reflect.Value{responseValue, requestValue, Uservalue})
    }

    其他的部分都是一样的了。

    它对应的Controller的Action是

    type User struct {
        UserName string
    }
    
    type adminController struct {
    }
    
    func (this *adminController)IndexAction(w http.ResponseWriter, r *http.Request, user string) {
        t, err := template.ParseFiles("template/html/admin/index.html")
        if (err != nil) {
            log.Println(err)
        }
        t.Execute(w, &User{user})
    }

    这里就将user传递出去给admin/index模版

    模版内部使用{{.UserName}}进行参数显示

    后记

    至此,这个基本的webdemo就完成了。启动服务之后,就会在8888端口开启了web服务。

    当然,这个web服务还有许多东西可以优化,个人信息验证,公共模板的提炼使用,数据库的密码使用密文等。但是这个例子中已经用到了搭建web服务器最基本的几个技能了。

    源代码

    本文所涉及的源代码放在github上

    https://github.com/jianfengye/webdemo

    实时了解作者更多技术文章,技术心得,请关注微信公众号“轩脉刃的刀光剑影”

    本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名叶剑峰(包含链接http://www.cnblogs.com/yjf512/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系

  • 相关阅读:
    五彩珠游戏
    repeater 的编辑功能
    客户端禁止cokice后,对session的影响.
    IIS无法运行ASP程序?
    winXP 密码 破解 重置
    winXP 密码 破解 重置
    IIS无法运行ASP程序?
    winXP 密码 破解 重置
    winXP 密码 破解 重置
    1.大批量数据操作
  • 原文地址:https://www.cnblogs.com/yjf512/p/2668384.html
Copyright © 2011-2022 走看看