SC-hw4-cloudgo-io

[Toc]

web小应用之cloudgo-io


1、概述

1.1 功能

这是一个简单的web小应用,有以下几个功能:

  • 支持静态文件服务
  • 支持简单的js访问
  • 提交表单,并输出一个表格
  • 对/unknow给出开发中的提示,返回码501

1.2 运行

  • 转到main.go所在目录,执行以下命令
go run main.go # 不指定端口默认8080

or

go run main.go -p 9090 # 指定监听9090端口

or

go install && cloudgo-io [-p ...] # 先安装之后可以直接使用cloudgo-io或cloudgo-io -p ...命令启动
  • 在浏览器地址栏输入
# 登录界面
http://localhost:yourport/login

# 静态资源文件服务
http://localhost:yourport/public

# 未开发
http://localhost:yourport/unknown

1.3 效果

  • 主界面(背景来自我的github page)
  • 支持静态文件服务

  • 支持简单的js访问(可跳转到静态资源界面)

  • 提交表单,并输出一个表格

  • 对/unknow给出开发中的提示,返回码501

2、挑选框架

综合比较了最常见的六种框架后,我选择了iris,毕竟年份最新、功能最全、速度最快。

这是2017年的统计数据。虽然star数暂时没有beego那么多,但是iris比beego慢了四年诞生,发展得很快。

当然我选择iris最大的原因,还是因为下图,功能这么全,早进坑早享受。不过现在iris网上的教程比较稀缺,多数需要先跟着官方example一步一步摸索。

以上两幅图片引用自:
https://blog.csdn.net/dev_csdn/article/details/78740990

关于速度,见iris最新官方数据。

3、文件结构

没有硬性规定,但是最好把负责不同模块或功能的文件分到不同的文件夹,便于维护。我的文件结构如下:

cloudgo-io/
    configs/
        main.tml
    static/
        css/
        js/
        img/
    services/
        GetPages.go
        ServicesManager.go
    templates/
        login.html
        info.html
  • configs中的main.tml文件可交由服务器管理员配置服务器后台。虽然也可以放在代码中配置,但是这样比较方便,不用每次都重新编译代码。
  • static存放静态文件。
  • services存放服务器提供服务的一系列代码,最后可以为这一系列服务安排一个manger统一管理,也方便统一调用。
  • templates存放网页的模板,在模板中需要留一些待注入的标记,有了这些预设的标记便可以在代码中找到对应的标记进行填充,比直接找element方便许多。
<table border="1">
    <tr>
        <th>username</th>
        <td>{{.username}}</td>
    </tr>
    <tr>
        <th>password</th>
        <td>{{.password}}</td>
    </tr>
</table>

以下介绍一些我的一些关于iris的探索成果和使用心得


4、iris的简单使用

4.1 获取iris

go get -u github.com/kataras/iris

4.2 使用的套路

  • 最简单版(官方例子)
package main

import "github.com/kataras/iris"

func main() {
    app := iris.Default()
    app.Get("/ping", func(ctx iris.Context) {
        ctx.JSON(iris.Map{
            "message": "pong",
        })
    })
    // listen and serve on http://0.0.0.0:8080.
    app.Run(iris.Addr(":8080"))
}
  • 一般步骤:
    • 1.先获取到iris的app。可以通过iris.New方式获取后自己配置一些参数,也可以通过iris.Default直接获取它的一个默认的app,app的类型是iris.Application。
    • 2.可选步骤(如下面我的main.go)
      • 2.1 允许用户自己设置监听的端口——如使用pflag实现用户指定端口。
      • 2.2 设置日志等级。默认为info等级,debug等级会输出更多的信息,方便调试。
      • 2.3 启动你提供的服务。
      • 2.4 其他
    • 3.app.Run运行app(顺便进行服务器配置,推荐)
package main

import (
    "os"

    "github.com/gitgiter/ServiceComputing/cloudgo-io/services"

    "github.com/kataras/iris"
    "github.com/spf13/pflag"
)

const (
    // PORT 8080 (default)
    PORT string = "8080"
)

func main() {

    // get and set server listening port

    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = PORT
    }

    pPort := pflag.StringP("port", "p", PORT, "http listening port")
    pflag.Parse()
    if len(*pPort) != 0 {
        port = *pPort
    }

    // get iris http server app
    app := iris.Default()

    // set logger level
    app.Logger().SetLevel("debug")

    services.StartServices(app)

    // listen and serve on http://localhost:port

    // configuring by file is more convenient
    app.Run(iris.Addr(":"+port), iris.WithConfiguration(iris.TOML("./configs/main.tml")))
}

4.3 配置

服务器配置有三种方式:

  • 第一种是直接在app.Run的时候显式配置
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.Configuration{
    DisableInterruptHandler:           false,
    DisablePathCorrection:             false,
    EnablePathEscape:                  false,
    FireMethodNotAllowed:              false,
    DisableBodyConsumptionOnUnmarshal: false,
    DisableAutoFireStatusCode:         false,
    TimeFormat:                        "Mon, 02 Jan 2006 15:04:05 GMT",
    Charset:                           "UTF-8",
}))
  • 第二种比较推荐,便是通过配置文件的形式,这样便可以在修改配置的时候不需要重新编译。如configs目录下的main.tml是我当前的配置,读取配置的方法就是4.2中的例子。
DisablePathCorrection = false
EnablePathEscape = false
FireMethodNotAllowed = true
DisableBodyConsumptionOnUnmarshal = false
TimeFormat = "Mon, 01 Jan 2006 15:04:05 GMT"
Charset = "UTF-8"
MyServerName = "gitgiter's iris"
  • 第三种则是直接在app.Run前调用app.Configure方法进行设置,这种比较不推荐,效果和第一种差不多而且可读性还没有第一种好。

5、使用iris实现上述四种服务

5.1 静态文件服务

第一个参数是一个虚拟路径,第二个参数才是真正的系统路径。

// load static files
// first parameter is the href request url, second is the real system path
app.StaticWeb("/public", "./static")

虚拟路径是給用户访问用的,如此时如果用户要访问我的静态文件目录,只需要在浏览器输入类似 ip:port/public/css/main.css 即可访问我的静态文件。可以避免系统路径改变导致用户的访问路径也要改变的麻烦。

为了进一步避免这种麻烦,可以在 /public 下所有的跳转链接,避免用户需要记住具体的静态文件路径。Context的HTML方法可以直接向页面写入html,这种适合写少量HTML的时候使用,要写大量HTML的最好的做法还是通过文件,否则会影响代码可读性且增大耦合性。

app.Get("/public", func(ctx iris.Context) {
    ctx.HTML(`<a href='/public/css/main.css'>/public/css/main.css</a><br/><br/>
        <a href='/public/img/bg.jpg'>/public/img/bg.jpg</a><br/><br/>
        <a href='/public/img/favicon.ico'>/public/img/favicon.ico</a><br/><br/>
        <a href='/public/js/showStatic.js'>/public/js/showStatic.js</a>`)
})

5.2 js请求

这里只写了一个很简单的js请求作为示例,比如点击登录界面中的Response按钮可以触发js的页面跳转事件。

js文件:

function myfunction()
{
    window.open("/public")
}

设置按钮点击事件:

js test: <input type="button" onclick="myfunction()" value="response">

处理请求,就是5.1写的那个。app.Get可以用来处理Get方式的请求,第一个参数为请求的url,第二个参数可以用一个函数指定请求的处理方式。

app.Get("/public", func(ctx iris.Context) {
    ...
})

5.3 表单提交,填充模板

  • 表单——form。表单提交一般采用POST模式,尤其是带有敏感信息的表单,这样表单的参数不会显示在url中。action用来指定表单提交后的动作,这里是跳转到/info页面。
<form method="POST" action="/info">
    username: <input type="text" name="Username"><br/><br/>
    password: <input type="password" name="Password"><br/><br/>
    <input type="submit" value="login"><br/><br/>
    js test: <input type="button" onclick="myfunction()" value="response">
</form>
  • 通过文件设置HTML视图时需要提前注册视图才可以加载视图。第一个参数指定你要加载的视图文件所在的文件夹,第二个参数指定要加载的文件后缀是html。Reload设置为true可以避免每次改动模板时需要重启app,对开发者比较友好,对用户来说没太大区别。
// register views from ./templates folder
app.RegisterView(iris.HTML("./templates", ".html").Reload(true))
  • ReadForm读取POST过来的表单,并绑定模板对视图进行填充。注意读取表单时要先定义一个结构体,这个结构体的每个字段名称需要和表单一致,且首字母必须大写,表示exported。ViewData填充数据使用键值对即可,key就是info.html里面定义的模板,value就是要替换的值。View加载指定文件的视图。
type User struct {
    Username string
    Password string
}

// GetInfoPage load html and static web
func GetInfoPage(app *iris.Application) {

    app.Post("/info", func(ctx iris.Context) {
        // get the form data
        form := User{}
        err := ctx.ReadForm(&form)
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.WriteString(err.Error())
        }

        // bind data by passing key-value pair
        username := form.Username
        password := form.Password
        ctx.ViewData("username", username)
        ctx.ViewData("password", password)
        ctx.View("info.html")
    })
}

5.4 /unknown报错

这个和前面的网页请求同理,不过需要通过app.StatusCode设置一个返回码以让用户知道是什么原因导致的页面无效,默认是返回200即正常访问。

// NotImplement returns 501 error to client
func NotImplement(app *iris.Application) {

    app.Get("/unknown", func(ctx iris.Context) {
        ctx.StatusCode(501)
        ctx.JSON(iris.Map{
            "error": "501 not implement error",
        })
    })
}

6、其他