Iris简介
Iris是一款Go语言中用来开发web应用的框架,该框架支持编写一次并在任何地方以最小的机器功率运行,如Android、ios、Linux和Windows等。该框架只需要一个可执行的服务就可以在平台上运行了。
Iris框架以简单而强大的api而被开发者所熟悉。iris除了为开发者提供非常简单的访问方式外,还同样支持MVC。另外,用iris构建微服务也很容易。
在iris框架的官方网站上,被称为速度最快的Go后端开发框架。在Iris的网站文档上,列出了该框架具备的一些特点和框架特性,列举如下:
Iris特性
- 专注于高性能
- 简单流畅的API
- 高扩展性
- 强大的路由和中间件生态系统
- 使用iris独特的表达主义路径解释器构建RESTful API
- 动态路径参数化或通配符路由与静态路由不冲突
- 使用重定向选项从URL中删除尾部斜杠
- 使用虚拟主机和子域名变得容易
- 分组API和静态或甚至动态子域名
- net / http和negroni-like处理程序通过iris.FromStd兼容
- 针对任意Http请求错误 定义处理函数
- 支持事务和回滚
- 支持响应缓存
- 使用简单的函数嵌入资源并与go-bindata 保持兼容
- mvc
- 上下文
- 高度可扩展的试图渲染(目前支持markdown,json,xml,jsonp等等)
- 正文绑定器和发送HTTP响应的便捷功能
- 限制请求正文
- 提供静态资源或嵌入式资产
- 本地化i18N
- 压缩(Gzip是内置的)
- 身份验证
- Basic Authentication
- OAuth, OAuth2 (支持27个以上的热门网站)
- JWT *服务器
- 通过TLS提供服务时,自动安装和提供来自https://letsencrypt.org的证书
- 默认为关闭状态
- 在关闭,错误或中断事件时注册
- 连接多个服务器,完全兼容 net/http#Server
- 视图系统.支持五种模板隐隐 完全兼容 html/template
- Websocket库,其API类似于http://socket.io [如果你愿意,你仍然可以使用你最喜欢的]
- 热重启
- Typescript集成 + Web IDE
官方源代码地址: https://github.com/kataras/iris
Iris web微服务框架示例
总体功能
- 集成aiwuTech.fileLogger日志按天切割,按级别输出到不同日志文件
- 集成redis
- 集成mysql
- 读取etcd配置
- 异常处理,全局异常处理
- 拦截器打印请求和响应参数
- 前端html集成
- 等等
下载iris依赖包
go get -u github.com/kataras/iris
main.go项目入口
init方法: 初始化相关配置,都有注释说明
main方法: 启动Iris服务
package main
import (
. "web-demo/config"
. "web-demo/log"
. "web-demo/redis"
. "web-demo/db"
_ "web-demo/handler"
"web-demo/web"
"flag"
"fmt"
"time"
)
var showVersion = flag.Bool("v", true, "print version")
func init(){
//调用 flag.Parse() 进行解析
flag.Parse()
//初始化配置文件
ConfigRead()
//初始化log日志
LogInit()
//初始化redis
RedisInit(Cfg.RedisAddr,0 , Cfg.RedisPassword, Cfg.RedisMaxConn)
//初始化mysql
SqlDBInit(&SqlDBParam{Cfg.MysqlHost, Cfg.MysqlPort, Cfg.MysqlUser, Cfg.MysqlPwd,
Cfg.MysqlDb})
}
func main() {
if *showVersion {
//这个日期就是写死的一个日期,不是这个日期就不认识,就不能正确的格式化
//据说是go诞生之日
version := fmt.Sprintf("%s %s@%s", "web-demo", "1.0", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(version)
}
Log.Info("start server...")
//监听端口
Log.Info("listen on :%s", Cfg.ListenPort)
web.RunIris(Cfg.ListenPort)
}
AccessLogMiddleware.go 中间件拦截器
在拦截器中添加请求头
把请求参数和响应参数打印到日志文件
package web
import (
. "web-demo/util/threadlocal"
. "web-demo/log"
"github.com/kataras/iris"
. "github.com/jtolds/gls"
"strconv"
"time"
)
func init() {
RegisterPreMiddleware(accessLogMiddleware{})
}
type accessLogMiddleware struct {
// your 'stateless' fields here
}
func (m accessLogMiddleware) Serve(ctx *iris.Context) {
//check header
//request id
requestId := ctx.RequestHeader("X-Web-Demo-RequestId")
if requestId == "" {
requestId = strconv.FormatInt(time.Now().UnixNano(), 10)
}
//access log
AccessLog.Info(requestId + "\t" + ctx.RequestIP() + "\t" + string(ctx.RequestURI()))
//response requestId
ctx.Response.Header.Add("X-Web-Demo-RequestId", requestId)
//do chian
Mgr.SetValues(Values{Rid: requestId}, func() {
ctx.Next()
})
//rrlog
RRLog.Info(requestId + "\t" + "-RequestIP:==" + ctx.RequestIP())
RRLog.Info(requestId + "\t" + "-RequestP:==" + string(ctx.RequestPath(true)))
RRLog.Info(requestId + "\t" + "-RequestD:==" + string(ctx.Request.Body()))
RRLog.Info(requestId + "\t" + "-Response:==" + string(ctx.Response.Body()))
}
logger.go 日志定义和配置
package log
import (
"flag"
"github.com/aiwuTech/fileLogger"
"os"
. "web-demo/util/threadlocal"
"web-demo/config"
)
var Log Logger
var ErrorLog Logger
var AccessLog Logger
var RRLog Logger
var InnerRRLog Logger
type Logger interface {
Trace(format string, params ...interface{})
Info(format string, params ...interface{})
Warn(format string, params ...interface{})
Error(format string, params ...interface{})
}
type CustomedLogger struct{
MyLogger Logger
}
func (cl CustomedLogger)Trace(format string, params ...interface{}){
cl.MyLogger.Trace(cl.resetFormat(format), params)
}
func (cl CustomedLogger)Info(format string, params ...interface{}){
cl.MyLogger.Info(cl.resetFormat(format), params)
}
func (cl CustomedLogger)Warn(format string, params ...interface{}){
cl.MyLogger.Warn(cl.resetFormat(format), params)
}
func (cl CustomedLogger)Error(format string, params ...interface{}){
cl.MyLogger.Error(cl.resetFormat(format), params)
}
func (cl CustomedLogger)resetFormat(format string) string{
logstr := format
if rid, ok := Mgr.GetValue(Rid); ok {
logstr = rid.(string) + " - " + logstr
}
return logstr
}
func LogInit() {
logPath := config.Cfg.LogPath
if (len(logPath) == 0) {
logPath = "/Users/liang/ideaWorkspace/go/src/web-demo/logs/web-demo"
}
flag.Parse()
if !isExist(logPath) {
os.Mkdir(logPath, 0755)
}
logger := fileLogger.NewDailyLogger(logPath, "root.log", "", fileLogger.DEFAULT_LOG_SCAN, fileLogger.DEFAULT_LOG_SEQ)
Log = CustomedLogger{MyLogger: logger}
errorLog := fileLogger.NewDailyLogger(logPath, "error.log", "", fileLogger.DEFAULT_LOG_SCAN, fileLogger.DEFAULT_LOG_SEQ)
ErrorLog = CustomedLogger{MyLogger: errorLog}
accessLog := fileLogger.NewDailyLogger(logPath, "access.log", "", fileLogger.DEFAULT_LOG_SCAN, fileLogger.DEFAULT_LOG_SEQ)
AccessLog = CustomedLogger{MyLogger: accessLog}
rRLog := fileLogger.NewDailyLogger(logPath, "rr.log", "", fileLogger.DEFAULT_LOG_SCAN, fileLogger.DEFAULT_LOG_SEQ)
RRLog = CustomedLogger{MyLogger: rRLog}
innerRRLog := fileLogger.NewDailyLogger(logPath, "inner_rr.log", "", fileLogger.DEFAULT_LOG_SCAN, fileLogger.DEFAULT_LOG_SEQ)
InnerRRLog = CustomedLogger{MyLogger: innerRRLog}
}
func isExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
mysql.go mysql初始化连接
package db
import (
. "web-demo/log"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
. "web-demo/config"
)
var Mysql *gorm.DB
type SqlDBParam struct {
Ip string
Port int
User string
Pw string
Database string
}
// mysql初始化文件
func SqlDBInit(param *SqlDBParam) {
Log.Info("init mysql...")
param_s := fmt.Sprintf(
"%v:%v@tcp(%v:%v)/%v?parseTime=True&loc=Local",
param.User,
param.Pw,
param.Ip,
param.Port,
param.Database,
)
Log.Info("mysql param: %s", param_s)
db, err := gorm.Open("mysql", param_s)
if err != nil {
Log.Error("open mysql error: %v", err)
panic(err)
}
db.DB().SetMaxIdleConns(Cfg.MysqlMaxConn)
db.SingularTable(true)
db.LogMode(true)
Mysql = db
Log.Info("init mysql end.")
}
redis.go redis初始化和常用操作方法
package redis
import (
. "web-demo/log"
"github.com/garyburd/redigo/redis"
"time"
)
var (
redisPool *redis.Pool
)
const (
maxIdle = 500
idleTimeout = 0 * time.Second
wait = true
)
//Init
//eg: RedisInit("127.0.0.1:6379", 0, "pwd", 8)
func RedisInit(server string, db int, password string, maxConn int) {
maxActive := maxConn
//Make a pool object
redisPool = &redis.Pool{
// Maximum number of idle connections in the pool.
MaxIdle: maxIdle,
// Maximum number of connections allocated by the pool at a given time.
// When zero, there is no limit on the number of connections in the pool.
MaxActive: maxActive,
// Close connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout.
IdleTimeout: idleTimeout,
// If Wait is true and the pool is at the MaxActive limit, then Get() waits
// for a connection to be returned to the pool before returning.
Wait: wait,
// Dial is an application supplied function for creating and configuring a
// connection.
//
// The connection returned from Dial must not be in a special state
// (subscribed to pubsub channel, transaction started, ...).
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", server)
if err != nil {
return nil, err
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
return nil, err
}
}
if _, err := c.Do("SELECT", db); err != nil {
c.Close()
return nil, err
}
return c, nil
},
// TestOnBorrow is an optional application supplied function for checking
// the health of an idle connection before the connection is used again by
// the application. Argument t is the time that the connection was returned
// to the pool. If the function returns an error, then the connection is
// closed.
TestOnBorrow: func(c redis.Conn, t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_, err := c.Do("PING")
return err
},
}
Log.Info("redis[%v %v] init ok", server, db)
}
......
handler 注意Iris会自动扫描handler包名
如果分了多个包,像demo里分了user和html,这时iris扫描不到.
需要在handler包中添加一个公共的文件把这2个包导入,如下所示
package handler
import (
_ "web-demo/handler/html"
_ "web-demo/handler/user"
)
其他功能就不详细介绍了,请看源代码
web-demo运行
#启动项目
go run main.go
#打印如下,表示成功启动8080端口
listen on :8080
浏览器访问:
http://127.0.0.1:8080/webdemo/html/v1/passenger/login.md
显示login.md文件内容
http://127.0.0.1:8080/webdemo/html/v1/index
显示index.html内容
Demo源代码地址:https://github.com/golang-example/web-demo