├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── doc.go └── web ├── base.go ├── config.go ├── document.go ├── page.go └── site.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | # For MacOS: 25 | .DS_Store 26 | 27 | # For vim: 28 | *.swp 29 | 30 | #For ctags 31 | tag 32 | tags 33 | TAG 34 | TAGS 35 | 36 | ======= 37 | # For golang: 38 | pkg 39 | bin -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # code to the Golanger Web Framework repository. 3 | 4 | Li Wei 5 | Jiang Bian 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Golanger.com. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | doc.go -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2013 Golanger.com. All rights reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | 18 | Golanger Web Framework 19 | ======================================================================= 20 | 21 | Golanger is a lightweight framework for writing web applications in Go. 22 | 23 | 24 | ## Features 25 | * Configuration (Support runtime modify with immediate effect) 26 | * Routing 27 | * Controllers 28 | * Templates 29 | * Session 30 | * Plugins 31 | * UrlManager 32 | * DataValidation 33 | * Log 34 | * Debug 35 | * RunTime Environment (Develop、Pre-release、Produce) 36 | 37 | ## Wishlist 38 | * Further optimize performance 39 | * Further enhance the stability 40 | * Architectural refactoring achieve interface package 41 | 42 | ## Quick Start 43 | * 首先要确保你已经安装了Go,如果没有请参考Go Installation 44 | * 设置GOPATH... 45 | * 安装Golanger 46 | 47 | ``` 48 | go get -u golanger.com/framework 49 | ``` 50 | 51 | * 如果想了解Golanger可以先从Samples开始 52 | 53 | ## Golanger 框架约定: 54 | * 框架本身不实现任何模式 55 | * 框架可组合实现多种模式,如mvc,restful等 56 | 57 | ## Golanger 运行时环境说明 58 | 59 | 运行程序时通过 -env=\ 命令行参数指定 60 | 61 | * 开发环境 - develop 62 | 63 | ``` 64 | !一般用于本地开发时设定。 65 | 内部会有另一个进程在监听 某对象,比如你改了啥html文件,你改了啥go文件,都可以实现实时(其实是伪实时,因为重新编译了程序,然后做逐步替换处理)生效 66 | ``` 67 | 68 | * 预发布环境 - prerelease 69 | 70 | ``` 71 | !一般用于线上进入beta状态时使用 72 | 只监听模板内文件,不监听go文件,就是说,不会重新编译,但是会在请求时重新加载模板文件 73 | ``` 74 | 75 | * 生产环境 - produce 76 | 77 | ``` 78 | !一般用于几乎不变动的稳定生产环境使用 79 | 不监听任何,包括模板和go文件。 80 | ``` 81 | 82 | ## Golanger 应用目录结构说明(配置为MVC模式) 83 | 84 | ``` 85 | 构建新应用时可以将samples/applicationTemplate/web/website.zip解压,作为程序应用模板来扩展功能 86 | ``` 87 | 88 | Golanger 框架支持配置为MVC模式的实现(三层架构模式)(Model-View-Controller), 它是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller) 89 | 90 | Golanger 应用目录的命名规则(可根据自己需求自行变化): 91 | 92 | * 控制器(Controller): 存放在controllers目录中, 负责转发请求,对请求进行处理. 93 | * 模型(Model): 存放在models目录中, 程序员编写程序应有的功能(实现算法等等)、数据管理和数据库设计(可以实现具体的功能). 94 | * 视图(View): 存放在views目录中, 界面设计人员进行图形界面设计. 95 | * 资源文件放在assets目录中 96 | * 配置文件放在config目录中 97 | * 自定义助手类放在helper目录中 98 | * 自定义模板函数放在templateFunc目录中 99 | * add-on存放独立此应用的第三方库,默认死把GOPATH设置到此目录 100 | 101 | 本文以 samples/applicationTemplate/web/website.zip 项目为例,说说Golanger的MVC模式的目录结构: 102 | 103 | ``` 104 | ~/website $ tree 105 | . 106 | └── src // 项目源码目录 107 | ├── main.go // 主程序文件,配置监听端口,配置文件,可以通过此实现MVC、RestFul等模式 108 | ├── config // 项目配置信息目录,支持程序运行时修改并实时生效,可以通过配置实现MVC、RestFul等模式,采用增强型json的格式(支持注释) 109 | │   ├── site // 应用的相关站点配置 110 | │   ├── assets // 资源型文件相关配置 111 | │   ├── custom // 自定义变量等相关设置 112 | │   ├── database // 数据库等相关配置 113 | │   ├── environment // 应用程序内部环境变量 114 | │   ├── html // 静态html缓存等相关配置 115 | │   ├── i18n // i18n多国语言的相关配置 116 | │   ├── log // 日志相关的配置,支持log分级的组合形式,log的输出形式(控制台,文件) 117 | │   ├── session // Session相关的配置,如MemorySession、FileSession、CookieSession等 118 | │   ├── template // 模板相关的配置 119 | │   ├── urlmanage // Url的管理,支持url的重写,兼容apache的重写标记 120 | │   └── locale // i18n的语言文件目录 121 | │      └── zh-cn // 中文语种文件 122 | ├── controllers // 控制器(Controller)模块, 负责所有业务的请求,转发等业务逻辑 123 | │   ├── 404.go // 404逻辑处理页面 124 | │   ├── app.go // 控制器(Controller)模块的初始化, 每一个逻辑处理页面都要注册app.go 125 | │   └── index.go // 126 | │ // 1. 习惯性的将文件名与要处理的url路径名相同(通过App.RegisterController注册路径的Handle),来清晰的划分功能模块, 127 | │ // 注册响应要处理的url目录路径,然后处理子页面的逻辑。 128 | │ // 2. 文件名和App.RegisterController注册的路径的Handle可以不同,主要依据App.RegisterController注册的路径的Handle。 129 | │ // 3. 每个模块用一个文件名,用来功能性分离, 让结构更清晰而已。 130 | ├── data // 存放App自有数据的目录 131 | ├── doc.go // Go的文档文件 132 | ├── helper // 助手模块, add-on里面的第三方库的包含, helper可以对它做二次封装。比如: website-admin 就对mgo进行了二次封装,方便以后的扩展。 133 | │ //可以关闭 App.HandleFavicon() 和 App.HandleStatic()。支持无需停止程序,动态修改生效(除特殊说明的配置项) 134 | ├── models // 模型(Model),编写程序应有的功能(实现算法等等)、数据管理和数据库设计(可以实现具体的功能) 135 | │   └── table_name.go // 示例文件,基于mongodb的模型的。可自行根据自己使用的数据库系统来构建 136 | ├── readme // 项目说明文件 137 | ├── templateFunc // 模板函数扩展目录 138 | │   └── operator.go // 一个简单的模板运算的库 139 | ├── tmp // 临时文件目录 140 | ├── views // 视图(View), 支持动态修改模板内容 141 | │   └── theme // 主题目录 142 | │   └── default // 网站的默认主题,可以在config/site中进行配置 143 | │   ├── _global // golanger会自动判断此目录是否存在, 如果存在会自动加载相关的模板文件 144 | │   │   ├── footer.html // footer 模板 145 | │   │   └── header.html // header 模板 146 | │   ├── index // App.RegisterController注册路径的Handle, golanger会自动判断此目录是否存在 147 | │   │   └── index.html // 如果请求相关的注册路径,会自动加载相关的模板信息 148 | │   └── _notfound 149 | │   └── 404.html 150 | ├── assets // 资源文件目录 151 | │   ├── static // 静态文件目录, 如果需要nginx之类的程序处理静态文件,可以关闭config中asset最静态目录的支持 152 | │ │   ├── add-on // 静态文件的第三方库 153 | │   │   ├── theme // 主题目录 154 | │   │   │   └── default // 网站的默认主题,可以在config/site中进行配置 155 | │   │   │   ├── css // 样式文件目录 156 | │   │   │   │   ├── global // golanger会自动判断此目录是否存在 157 | │   │   │   │   │   └── global.css // 如果目录存在,golanger会自动判断global.css是否存在,如果存在,会自动相应的模板变量,并在全局包含 158 | │   │   │   │   └── index // App.RegisterController注册路径的Handle, golanger会自动判断此目录是否存在 159 | │   │   │   │   ├── global.css // 如果注册路径存在,golanger会自动判断global.css是否存在,如果存在,会自动相应的模板变量,并在全模块下包含 160 | │   │   │   │   └── index.css // 如果注册路径存在,golanger会自动判断“注册路径”.css(index.css)是否存在,如果存在,会自动相应的模板变量,并在当前同名请求的页面包含 161 | │   │   │   ├── img 162 | │   │   │   │   ├── global // golanger会自动判断此目录是否存在 163 | │   │   │   │   └── index // App.RegisterController注册路径的Handle, golanger会自动判断此目录是否存在 164 | │   │   │   └── js 165 | │   │   │   ├── global // golanger会自动判断此目录是否存在 166 | │   │   │   │   └── global.js // 如果目录存在,golanger会自动判断global.js是否存在,如果存在,会自动相应的模板变量,并在全局包含 167 | │   │   │   └── index // App.RegisterController注册路径的Handle, golanger会自动判断此目录是否存在 168 | │   │   │   ├── global.js // 如果注册路径存在,golanger会自动判断global.js是否存在,如果存在,会自动相应的模板变量,并在全模块下包含 169 | │   │   │   └── index.js // 如果注册路径存在,golanger会自动判断“注册路径”.css(index.css)是否存在,如果存在,会自动相应的模板变量,并在当前同名请求的页面包含 170 | │   │   └── upload // 上传文件路径 171 | │   └── html // 生成静态html文件时存放的目录 172 | ├── add-on // 第三方扩展目录, 默认GOPATH目录 173 | ├── build.bat // windows 平台编译脚本 174 | ├── build.sh // Linux/Mac 平台编译脚本 175 | └── website // 生成的执行文件 176 | ``` 177 | 178 | ## Samples Online 179 | * WebServer服务 180 | * 聊天室(chatroom) - 在线demo 181 | * 记事本(guestbook) - 在线demo 182 | * Helloworld - 在线demo 183 | * 图片分享(pinterest) - 在线demo 184 | * Golang Play - 在线demo 185 | * Todo List - 在线demo 186 | * 权限管理(website-admin) - 在线demo 187 | * User: testgolanger 188 | * Password: testgolanger 189 | 190 | ## 性能测试 191 | * [Golanger Using Apache Bench for Simple Load Testing](https://github.com/golangers/framework/wiki/Golanger-Using-Apache-Bench-for-Simple-Load-Testing) 192 | 193 | ## 开发团队成员清单 194 | * Li Wei 网名:海意/LeeTaiFook/李大福, QQ:20660991,新浪微博:weibo.com/leetaifook 195 | * Jiang Bian 196 | 197 | ## 联系方式 198 | ### WebSite 199 | * Golanger Web Framework 200 | * Golanger 201 | * 新浪微博 202 | * QQ群: 29994666 203 | 204 | ### 邮件列表 205 | * Golanger邮件列表 206 | * 207 | 208 | */ 209 | 210 | package documentation 211 | -------------------------------------------------------------------------------- /web/base.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "mime" 5 | "net/http" 6 | "net/url" 7 | "sync" 8 | ) 9 | 10 | type base struct { 11 | rmutex sync.RWMutex 12 | mutex sync.Mutex 13 | header map[string][]string 14 | } 15 | 16 | func (b *base) Init(w http.ResponseWriter, r *http.Request) *base { 17 | 18 | return b 19 | } 20 | 21 | func (b *base) AddHeader(k, v string) { 22 | b.header[k] = append(b.header[k], v) 23 | } 24 | 25 | func (b *base) DelHeader(k string) { 26 | delete(b.header, k) 27 | } 28 | 29 | func (b *base) getHttpGet(r *http.Request) map[string]string { 30 | g := map[string]string{} 31 | q := r.URL.Query() 32 | for key, _ := range q { 33 | g[key] = q.Get(key) 34 | } 35 | 36 | return g 37 | } 38 | 39 | func (b *base) getHttpPost(r *http.Request, MAX_FORM_SIZE int64) map[string]string { 40 | ct := r.Header.Get("Content-Type") 41 | ct, _, _ = mime.ParseMediaType(ct) 42 | if ct == "multipart/form-data" { 43 | r.ParseMultipartForm(MAX_FORM_SIZE) 44 | } else { 45 | r.ParseForm() 46 | } 47 | 48 | p := map[string]string{} 49 | for key, _ := range r.Form { 50 | p[key] = r.FormValue(key) 51 | } 52 | 53 | return p 54 | } 55 | 56 | func (b *base) getHttpCookie(r *http.Request) map[string]string { 57 | c := map[string]string{} 58 | for _, ck := range r.Cookies() { 59 | c[ck.Name], _ = url.QueryUnescape(ck.Value) 60 | } 61 | 62 | return c 63 | } 64 | -------------------------------------------------------------------------------- /web/config.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "golanger.com/config" 5 | "golanger.com/log" 6 | "os" 7 | ) 8 | 9 | type Config struct { 10 | SupportLog bool `json:"SupportLog"` 11 | LogWriteTo string `json:"LogWriteTo"` 12 | LogLevel string `json:"LogLevel"` 13 | SupportTemplate bool `json:"SupportTemplate"` 14 | SupportSession bool `json:"SupportSession"` 15 | SupportCookieSession bool `json:"SupportCookieSession"` 16 | SupportI18n bool `json:"SupportI18n"` 17 | SupportStatic bool `json:"SupportStatic"` 18 | SupportUrlManage bool `json:"SupportUrlManage"` 19 | SupportUrlManageWithCache bool `json:"SupportUrlManageWithCache"` 20 | SessionType string `json:"SessionType"` 21 | RootStaticFiles string `json:"RootStaticFiles"` 22 | DefaultLocalePath string `json:"DefaultLocalePath"` 23 | DefaultLanguage string `json:"DefaultLanguage"` 24 | AutoGenerateHtml bool `json:"AutoGenerateHtml"` 25 | AutoGenerateHtmlCycleTime int64 `json:"AutoGenerateHtmlCycleTime"` 26 | AutoLoadStaticHtml bool `json:"AutoLoadStaticHtml"` 27 | LoadStaticHtmlWithLogic bool `json:"LoadStaticHtmlWithLogic"` 28 | ChangeSiteRoot bool `json:"ChangeSiteRoot"` 29 | AccessHtml bool `json:"AccessHtml"` 30 | AutoJumpToHtml bool `json:"AutoJumpToHtml"` 31 | AssetsDirectory string `json:"AssetsDirectory"` 32 | StaticDirectory string `json:"StaticDirectory"` 33 | ThemeDirectory string `json:"ThemeDirectory"` 34 | Theme string `json:"Theme"` 35 | StaticCssDirectory string `json:"StaticCssDirectory"` 36 | StaticJsDirectory string `json:"StaticJsDirectory"` 37 | StaticImgDirectory string `json:"StaticImgDirectory"` 38 | HtmlDirectory string `json:"HtmlDirectory"` 39 | TemplateDirectory string `json:"TemplateDirectory"` 40 | TemplateGlobalDirectory string `json:"TemplateGlobalDirectory"` 41 | TemplateGlobalFile string `json:"TemplateGlobalFile"` 42 | TemporaryDirectory string `json:"TemporaryDirectory"` 43 | UploadDirectory string `json:"UploadDirectory"` 44 | IndexDirectory string `json:"IndexDirectory"` 45 | IndexPage string `json:"IndexPage"` 46 | SiteRoot string `json:"SiteRoot"` 47 | Environment map[string]string `json:"Environment"` 48 | Database map[string]string `json:"Database"` 49 | UrlManageRule []string `json:"UrlManageRule"` 50 | M map[string]interface{} `json:"Custom"` 51 | configDir string 52 | configLastModTime int64 53 | } 54 | 55 | func NewConfig() Config { 56 | return Config{ 57 | SupportUrlManageWithCache: true, 58 | LogWriteTo: "console", 59 | SessionType: "memory", 60 | RootStaticFiles: "favicon.ico", 61 | TemplateDirectory: "./view/", 62 | TemporaryDirectory: "./tmp/", 63 | AssetsDirectory: "./assets/", 64 | StaticDirectory: "static/", 65 | DefaultLocalePath: "./config/locale/", 66 | DefaultLanguage: "zh-cn", 67 | ThemeDirectory: "theme/", 68 | Theme: "default", 69 | StaticCssDirectory: "css/", 70 | StaticJsDirectory: "js/", 71 | StaticImgDirectory: "img/", 72 | HtmlDirectory: "html/", 73 | UploadDirectory: "upload/", 74 | TemplateGlobalDirectory: "_global/", 75 | TemplateGlobalFile: "*", 76 | IndexDirectory: "index/", 77 | IndexPage: "index.html", 78 | SiteRoot: "/", 79 | Environment: map[string]string{}, 80 | Database: map[string]string{}, 81 | UrlManageRule: []string{}, 82 | } 83 | } 84 | 85 | func (c Config) Init() Config { 86 | c.preSet() 87 | return c 88 | } 89 | 90 | func (c *Config) preSet() { 91 | c.UploadDirectory = c.AssetsDirectory + c.StaticDirectory + c.UploadDirectory 92 | c.ThemeDirectory = c.ThemeDirectory + c.Theme + "/" 93 | c.StaticCssDirectory = c.AssetsDirectory + c.StaticDirectory + c.ThemeDirectory + c.StaticCssDirectory 94 | c.StaticJsDirectory = c.AssetsDirectory + c.StaticDirectory + c.ThemeDirectory + c.StaticJsDirectory 95 | c.StaticImgDirectory = c.AssetsDirectory + c.StaticDirectory + c.ThemeDirectory + c.StaticImgDirectory 96 | } 97 | 98 | func (c *Config) set(configDir string, configLastModTime int64) { 99 | c.configDir = configDir 100 | c.configLastModTime = configLastModTime 101 | } 102 | 103 | func (c *Config) LoadData(data string) { 104 | config.Data(data).Load(c) 105 | c.preSet() 106 | } 107 | 108 | func (c *Config) Load(configDir string) { 109 | conf := config.Dir(configDir).Load(c) 110 | configDir = conf.Target() 111 | if dataFi, err := os.Stat(configDir); err == nil { 112 | c.set(configDir, dataFi.ModTime().Unix()) 113 | c.preSet() 114 | } else { 115 | log.Fatal(" error:", err) 116 | } 117 | } 118 | 119 | func (c *Config) Reload() bool { 120 | var b bool 121 | configDir := c.configDir 122 | if configDir == "" { 123 | return false 124 | } 125 | 126 | if dataFi, err := os.Stat(configDir); err == nil { 127 | if dataFi.ModTime().Unix() > c.configLastModTime { 128 | cm := NewConfig() 129 | cm.Load(configDir) 130 | *c = cm 131 | b = true 132 | } 133 | } 134 | 135 | return b 136 | } 137 | -------------------------------------------------------------------------------- /web/document.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "text/template" 5 | ) 6 | 7 | type Document struct { 8 | Close bool 9 | GenerateHtml bool 10 | Static string 11 | Theme string 12 | Attr map[string]string 13 | Css map[string]string 14 | Js map[string]string 15 | Img map[string]string 16 | GlobalCssFile string 17 | GlobalJsFile string 18 | GlobalIndexCssFile string 19 | GlobalIndexJsFile string 20 | IndexCssFile string 21 | IndexJsFile string 22 | Hide bool 23 | Func template.FuncMap 24 | Title string 25 | Body interface{} 26 | } 27 | -------------------------------------------------------------------------------- /web/page.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "golanger.com/i18n" 7 | "golanger.com/log" 8 | "golanger.com/session/cookiesession" 9 | "golanger.com/session/filesession" 10 | "golanger.com/session/memorysession" 11 | "golanger.com/urlmanage" 12 | "golanger.com/validate" 13 | "io/ioutil" 14 | "net/http" 15 | "net/url" 16 | "os" 17 | "path/filepath" 18 | "reflect" 19 | "strconv" 20 | "strings" 21 | "text/template" 22 | "time" 23 | ) 24 | 25 | type Page struct { 26 | *site 27 | Config 28 | Document 29 | Controller map[string]interface{} 30 | DefaultController interface{} 31 | NotFoundtController interface{} 32 | CurrentController string 33 | CurrentAction string 34 | Template string 35 | MAX_FORM_SIZE int64 36 | GET map[string]string 37 | POST map[string]string 38 | COOKIE map[string]string 39 | SESSION map[string]interface{} 40 | ONCE_SESSION interface{} 41 | COOKIE_SESSION map[string]interface{} 42 | LANG map[string]string 43 | TARGET_LANG string 44 | MemorySession *memorysession.SessionManager 45 | FileSession *filesession.SessionManager 46 | CookieSession *cookiesession.SessionManager 47 | I18n *i18n.I18nManager 48 | Validation validate.Validation 49 | UrlManage *urlmanage.UrlManage 50 | currentPath string 51 | currentFileName string 52 | } 53 | 54 | type PageParam struct { 55 | MaxFormSize int64 56 | SessionDir string 57 | CookieName string 58 | CookieSessionName string 59 | CookieSessionKey string 60 | I18nName string 61 | Expires int 62 | TimerDuration string 63 | } 64 | 65 | func NewPage(param PageParam) Page { 66 | if param.MaxFormSize <= 0 { 67 | param.MaxFormSize = 2 << 20 // 2MB => 2的20次方 乘以 2 =》 2 * 1024 * 1024 68 | } 69 | 70 | return Page{ 71 | site: &site{ 72 | base: &base{ 73 | header: map[string][]string{}, 74 | }, 75 | templateFunc: template.FuncMap{}, 76 | templateCache: map[string]templateCache{}, 77 | Version: strconv.Itoa(time.Now().Year()), 78 | }, 79 | Controller: map[string]interface{}{}, 80 | Config: NewConfig().Init(), 81 | Document: Document{ 82 | Attr: map[string]string{}, 83 | Css: map[string]string{}, 84 | Js: map[string]string{}, 85 | Img: map[string]string{}, 86 | Func: template.FuncMap{}, 87 | }, 88 | MAX_FORM_SIZE: param.MaxFormSize, 89 | MemorySession: memorysession.New(param.CookieName, param.Expires, param.TimerDuration), 90 | FileSession: filesession.New(param.CookieName, param.Expires, param.SessionDir, param.TimerDuration), 91 | CookieSession: cookiesession.New(param.CookieSessionName, param.CookieSessionKey), 92 | I18n: i18n.New(param.I18nName), 93 | UrlManage: urlmanage.New(), 94 | } 95 | } 96 | 97 | func (p *Page) Init(w http.ResponseWriter, r *http.Request) { 98 | p.site.base.mutex.Lock() 99 | p.site.Init(w, r) 100 | p.site.base.mutex.Unlock() 101 | 102 | p.site.base.rmutex.RLock() 103 | defer p.site.base.rmutex.RUnlock() 104 | p.GET = p.site.base.getHttpGet(r) 105 | p.POST = p.site.base.getHttpPost(r, p.MAX_FORM_SIZE) 106 | p.COOKIE = p.site.base.getHttpCookie(r) 107 | } 108 | 109 | func (p *Page) initModule(w http.ResponseWriter, r *http.Request) { 110 | if p.site.supportSession { 111 | switch p.Config.SessionType { 112 | case "file": 113 | p.SESSION = p.FileSession.Get(w, r) 114 | case "memory": 115 | p.SESSION = p.MemorySession.Get(w, r) 116 | default: 117 | p.SESSION = p.MemorySession.Get(w, r) 118 | } 119 | 120 | var ok bool 121 | p.ONCE_SESSION, ok = p.SESSION["__ONCE"] 122 | if ok { 123 | log.Debug(" ", `p.SESSION["__ONCE"] to set:`, p.ONCE_SESSION) 124 | delete(p.SESSION, "__ONCE") 125 | log.Debug(" ", `p.SESSION["__ONCE"] to delete:`, p.SESSION["__ONCE"]) 126 | } 127 | } 128 | 129 | if p.site.supportCookieSession { 130 | p.COOKIE_SESSION = p.CookieSession.Get(r) 131 | } 132 | 133 | if p.site.supportI18n { 134 | p.LANG = func() map[string]string { 135 | log.Debug(" ", `p.TARGET_LANG: `, p.TARGET_LANG) 136 | 137 | if p.TARGET_LANG == "" { 138 | p.TARGET_LANG = strings.TrimSpace(r.Header.Get("Accept-Language")) 139 | if i := strings.Index(p.TARGET_LANG, ","); i != -1 { 140 | p.TARGET_LANG = p.TARGET_LANG[:i] 141 | } 142 | } 143 | 144 | return p.I18n.Lang(p.TARGET_LANG) 145 | }() 146 | } 147 | 148 | if p.site.base.header != nil || len(p.site.base.header) > 0 { 149 | log.Debug(" ", "p.site.base.header:", p.site.base.header) 150 | for t, s := range p.site.base.header { 151 | for _, v := range s { 152 | w.Header().Add(t, v) 153 | } 154 | } 155 | } 156 | } 157 | 158 | func (p *Page) SetDefaultController(i interface{}) *Page { 159 | p.DefaultController = i 160 | 161 | return p 162 | } 163 | 164 | func (p *Page) SetNotFoundController(i interface{}) *Page { 165 | p.NotFoundtController = i 166 | 167 | return p 168 | } 169 | 170 | func (p *Page) RegisterController(relUrlPath string, i interface{}) *Page { 171 | if _, ok := p.Controller[relUrlPath]; !ok { 172 | p.Controller[relUrlPath] = i 173 | } 174 | 175 | return p 176 | } 177 | 178 | func (p *Page) UpdateController(oldUrlPath, relUrlPath string, i interface{}) *Page { 179 | delete(p.Controller, oldUrlPath) 180 | p.Controller[relUrlPath] = i 181 | 182 | return p 183 | } 184 | 185 | func (p *Page) GetController(urlPath string) interface{} { 186 | var relUrlPath string 187 | if strings.HasPrefix(urlPath, p.site.Root) { 188 | relUrlPath = urlPath[len(p.site.Root):] 189 | } else { 190 | relUrlPath = urlPath 191 | } 192 | 193 | i, ok := p.Controller[relUrlPath] 194 | if !ok { 195 | i = p.NotFoundtController 196 | } 197 | 198 | return i 199 | } 200 | 201 | func (p *Page) LoadData(data string) { 202 | p.Config.LoadData(data) 203 | p.reset(false) 204 | } 205 | 206 | func (p *Page) Load(configPath string) { 207 | p.Config.Load(configPath) 208 | p.reset(false) 209 | } 210 | 211 | func (p *Page) setGlobalTpl(globalTplModTime int64, reset bool) { 212 | if globalTpls, err := filepath.Glob(p.Config.TemplateDirectory + p.Config.ThemeDirectory + p.Config.TemplateGlobalDirectory + p.Config.TemplateGlobalFile); err == nil && globalTpls != nil && len(globalTpls) > 0 { 213 | var buf bytes.Buffer 214 | for _, tpl := range globalTpls { 215 | if b, err := ioutil.ReadFile(tpl); err == nil { 216 | buf.Write(b) 217 | } else { 218 | log.Error(" ", err) 219 | } 220 | } 221 | 222 | sBuf := strings.TrimSpace(buf.String()) 223 | if sBuf != "" { 224 | p.site.SetTemplateCacheObject("globalTpl", buf.String(), globalTplModTime) 225 | if reset { 226 | newT := template.New("globalTpl").Funcs(p.site.templateFunc) 227 | if t, err := newT.Parse(sBuf); t != nil { 228 | p.site.globalTemplate = t 229 | } else { 230 | log.Error(" ", err) 231 | } 232 | } else { 233 | if t, err := p.site.globalTemplate.Parse(sBuf); t != nil { 234 | p.site.globalTemplate = t 235 | } else { 236 | log.Error(" ", err) 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | func (p *Page) reset(update bool) { 244 | p.setLog(p.Config.SupportLog, p.Config.LogWriteTo, p.Config.LogLevel) 245 | p.setUrlManage(p.Config.SupportUrlManage, p.Config.SupportUrlManageWithCache, p.Config.UrlManageRule) 246 | 247 | if update { 248 | if p.site.supportSession != p.Config.SupportSession { 249 | p.site.supportSession = p.Config.SupportSession 250 | } 251 | 252 | if p.site.supportCookieSession != p.Config.SupportCookieSession { 253 | p.site.supportCookieSession = p.Config.SupportCookieSession 254 | } 255 | 256 | if p.site.supportI18n != p.Config.SupportI18n { 257 | p.site.supportI18n = p.Config.SupportI18n 258 | } 259 | 260 | if p.Document.Theme != p.Config.Theme { 261 | p.Document.Theme = p.Config.Theme 262 | } 263 | 264 | if p.Document.Static != p.Config.SiteRoot+p.Config.StaticDirectory { 265 | p.Document.Static = p.Config.SiteRoot + p.Config.StaticDirectory 266 | } 267 | 268 | if p.site.Root == p.Config.SiteRoot { 269 | return 270 | } else { 271 | log.Info(" ", "p.Config.SiteRoot be changed:", p.Config.SiteRoot) 272 | p.SetDefaultController(p.GetController(p.Config.IndexDirectory)) 273 | p.UpdateController(p.site.Root, p.Config.SiteRoot, p.DefaultController) 274 | p.site.Root = p.Config.SiteRoot 275 | } 276 | } else { 277 | p.site.supportSession = p.Config.SupportSession 278 | p.site.supportCookieSession = p.Config.SupportCookieSession 279 | p.site.supportI18n = p.Config.SupportI18n 280 | p.Document.Theme = p.Config.Theme 281 | p.site.Root = p.Config.SiteRoot 282 | p.Document.Static = p.site.Root + p.Config.StaticDirectory 283 | p.SetDefaultController(p.GetController(p.Config.IndexDirectory)) 284 | p.RegisterController(p.site.Root, p.DefaultController) 285 | p.site.globalTemplate = template.New("globalTpl").Funcs(p.site.templateFunc) 286 | } 287 | 288 | if globalCssFi, err := os.Stat(p.Config.StaticCssDirectory + "/global/"); err == nil && globalCssFi.IsDir() { 289 | DcssPath := p.Config.StaticCssDirectory + "global/" 290 | p.Document.Css["global"] = p.site.Root + DcssPath[len(p.Config.AssetsDirectory):] 291 | log.Debug(" ", `p.Document.Css["global"]:`, p.Document.Css["global"]) 292 | 293 | if _, err := os.Stat(DcssPath + "global.css"); err == nil { 294 | p.Document.GlobalCssFile = p.Document.Css["global"] + "global.css" 295 | } 296 | } 297 | 298 | if globalJsFi, err := os.Stat(p.Config.StaticJsDirectory + "/global/"); err == nil && globalJsFi.IsDir() { 299 | DjsPath := p.Config.StaticJsDirectory + "global/" 300 | p.Document.Js["global"] = p.site.Root + DjsPath[len(p.Config.AssetsDirectory):] 301 | log.Debug(" ", `p.Document.Js["global"]:`, p.Document.GlobalCssFile) 302 | if _, err := os.Stat(DjsPath + "global.js"); err == nil { 303 | p.Document.GlobalJsFile = p.Document.Js["global"] + "global.js" 304 | } 305 | } 306 | 307 | if globalImgFi, err := os.Stat(p.Config.StaticImgDirectory + "/global/"); err == nil && globalImgFi.IsDir() { 308 | DimgPath := p.Config.StaticImgDirectory + "global/" 309 | p.Document.Img["global"] = p.site.Root + DimgPath[len(p.Config.AssetsDirectory):] 310 | log.Debug(" ", `p.Document.Img["global"]:`, p.Document.Img["global"]) 311 | } 312 | 313 | if globalTplFi, err := os.Stat(p.Config.TemplateDirectory + p.Config.ThemeDirectory + p.Config.TemplateGlobalDirectory); err != nil { 314 | log.Error(" ", err) 315 | } else { 316 | p.setGlobalTpl(globalTplFi.ModTime().Unix(), false) 317 | } 318 | } 319 | 320 | func (p *Page) setUrlManage(manage, cache bool, rules []string) { 321 | if !manage { 322 | p.UrlManage.Stop() 323 | } else { 324 | if len(rules) == 0 { 325 | p.UrlManage.Stop() 326 | } else { 327 | p.UrlManage.Start() 328 | if cache == false { 329 | p.UrlManage.SetCache(cache) 330 | } 331 | 332 | ruleContent := strings.Join(rules, "\n") 333 | p.UrlManage.LoadRule(ruleContent, true) 334 | } 335 | } 336 | } 337 | 338 | func (p *Page) setLog(hasLog bool, writeTo, level string) { 339 | if !hasLog { 340 | log.SetLevel(log.LEVEL_DISABLE) 341 | } else { 342 | if level == "" { 343 | log.SetLevel(log.LEVEL_DEFAULT) 344 | } else { 345 | var lv int 346 | lvs := strings.Split(level, ",") 347 | for _, v := range lvs { 348 | if l, lok := log.LevelText[strings.ToLower(strings.TrimSpace(v))]; lok { 349 | lv |= l 350 | } 351 | } 352 | 353 | log.SetLevel(lv) 354 | } 355 | 356 | switch writeTo { 357 | default: 358 | //包括console 359 | } 360 | } 361 | } 362 | 363 | func (p *Page) setCurrentInfo(path string) { 364 | urlPath, fileName := filepath.Split(path) 365 | if urlPath == p.site.Root { 366 | urlPath = p.site.Root + p.Config.IndexDirectory 367 | } 368 | 369 | if fileName == "" { 370 | fileName = p.Config.IndexPage 371 | } 372 | 373 | p.currentPath = urlPath 374 | log.Debug(" ", "p.currentPath:", p.currentPath) 375 | p.currentFileName = fileName 376 | log.Debug(" ", "p.currentFileName:", p.currentFileName) 377 | p.CurrentController = urlPath[len(p.site.Root):] 378 | log.Debug(" ", "p.CurrentController:", p.CurrentController) 379 | p.CurrentAction = strings.Replace(strings.Title(strings.Replace(p.currentFileName[:len(p.currentFileName)-len(filepath.Ext(p.currentFileName))], "_", " ", -1)), " ", "", -1) 380 | log.Debug(" ", "p.CurrentAction:", p.CurrentAction) 381 | } 382 | 383 | func (p *Page) callMethod(tpc reflect.Type, vpc reflect.Value, action string, rvr reflect.Value, rvw reflect.Value) []reflect.Value { 384 | arv := []reflect.Value{} 385 | if rm, ok := tpc.MethodByName(action); ok { 386 | mt := rm.Type 387 | log.Debug(" ", mt.String()+".NumIn():", mt.NumIn()) 388 | switch mt.NumIn() { 389 | case 2: 390 | if mt.In(1) == rvr.Type() { 391 | arv = vpc.MethodByName(action).Call([]reflect.Value{rvr}) 392 | } else { 393 | arv = vpc.MethodByName(action).Call([]reflect.Value{rvw}) 394 | } 395 | case 3: 396 | arv = vpc.MethodByName(action).Call([]reflect.Value{rvw, rvr}) 397 | default: 398 | arv = vpc.MethodByName(action).Call([]reflect.Value{}) 399 | } 400 | } 401 | 402 | return arv 403 | } 404 | 405 | func (p *Page) filterMethod(methodName string) bool { 406 | b := false 407 | b = !strings.HasPrefix(methodName, "Before_") && !strings.HasPrefix(methodName, "After_") && !strings.HasPrefix(methodName, "Filter_") 408 | return b 409 | } 410 | 411 | func (p *Page) filterDoMethod(tpc reflect.Type, vpc reflect.Value, action string, rvr reflect.Value, rvw reflect.Value) { 412 | log.Debug(" ", "action:", action) 413 | if aarv := p.callMethod(tpc, vpc, action, rvr, rvw); len(aarv) == 1 { 414 | if ma, tok := aarv[0].Interface().([]map[string]string); tok { 415 | log.Debug(" ", "FilterMap:", ma) 416 | for _, filter := range ma { 417 | doFilter := false 418 | doMethod := false 419 | doParam := false 420 | method, mok := filter["_FILTER"] 421 | 422 | if mok { 423 | filterType, found := filter[p.CurrentAction] 424 | if !found && filter["_ALL"] == "allow" { 425 | doFilter = true 426 | } else if found && filterType == "allow" { 427 | doFilter = true 428 | } 429 | } 430 | 431 | if doFilter { 432 | if rm, ok := filter["_METHOD"]; !ok { 433 | doMethod = true 434 | } else { 435 | reqMethods := strings.Split(rm, ",") 436 | for _, v := range reqMethods { 437 | r := rvr.Interface().(*http.Request) 438 | if r.Method == strings.ToUpper(v) { 439 | doMethod = true 440 | break 441 | } 442 | } 443 | } 444 | 445 | if rmp, ok := filter["_PARAM"]; !ok { 446 | doParam = true 447 | } else { 448 | reqParams := strings.Split(rmp, ",") 449 | for _, v := range reqParams { 450 | _, vgok := p.GET[v] 451 | _, vpok := p.POST[v] 452 | if vgok || vpok { 453 | doParam = true 454 | break 455 | } 456 | } 457 | } 458 | 459 | if doMethod && doParam { 460 | p.callMethod(tpc, vpc, "Filter_"+method, rvr, rvw) 461 | } 462 | } 463 | } 464 | } 465 | } 466 | } 467 | 468 | func (p *Page) loadGeneratedHtml(w http.ResponseWriter, r *http.Request) (b bool) { 469 | log.Debug(" ", "p.Config.SupportTemplate:", p.Config.SupportTemplate) 470 | log.Debug(" ", "p.Config.LoadStaticHtmlWithLogic:", p.Config.LoadStaticHtmlWithLogic) 471 | log.Debug(" ", "p.Config.AutoGenerateHtml:", p.Config.AutoGenerateHtml) 472 | 473 | if p.Config.SupportTemplate && !p.Config.LoadStaticHtmlWithLogic && p.Config.AutoGenerateHtml { 474 | tplFi, tplErr := os.Stat(p.Config.TemplateDirectory + p.Config.ThemeDirectory + p.Template) 475 | if tplErr != nil { 476 | log.Error(" ", tplErr) 477 | } else { 478 | p.site.base.rmutex.RLock() 479 | tmplCache := p.site.GetTemplateCache(p.Template) 480 | p.site.base.rmutex.RUnlock() 481 | if tplFi.ModTime().Unix() > tmplCache.ModTime { 482 | return false 483 | } 484 | } 485 | 486 | htmlFile := "" 487 | assetsHtmlDir := p.Config.AssetsDirectory + p.Config.HtmlDirectory 488 | if strings.HasPrefix(p.Template, p.Config.IndexDirectory) { 489 | htmlFile = assetsHtmlDir + strings.Replace(p.Template, p.Config.IndexDirectory, "", 1) 490 | } else { 491 | htmlFile = assetsHtmlDir + p.Template 492 | } 493 | 494 | if r.URL.RawQuery != "" { 495 | htmlFile += "?" + r.URL.RawQuery 496 | } 497 | 498 | if htmlFi, htmlErr := os.Stat(htmlFile); htmlErr == nil { 499 | if p.checkHtmlDoWrite(tplFi, htmlFi, htmlErr) { 500 | } 501 | 502 | htmlContent, err := ioutil.ReadFile(htmlFile) 503 | if err == nil { 504 | w.Header().Add("Content-Length", strconv.FormatInt(htmlFi.Size(), 10)) 505 | w.Header().Add("Last-Modified", htmlFi.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")) 506 | w.Write(htmlContent) 507 | return true 508 | } 509 | } 510 | } 511 | 512 | return 513 | } 514 | 515 | func (p *Page) routeController(i interface{}, w http.ResponseWriter, r *http.Request) { 516 | pageOriController := p.GetController(p.currentPath) 517 | rv := reflect.ValueOf(pageOriController) 518 | rvw, rvr := reflect.ValueOf(w), reflect.ValueOf(r) 519 | rt := rv.Type() 520 | vpc := reflect.New(rt) 521 | 522 | iv := reflect.ValueOf(i).Elem() 523 | vapc := vpc.Elem().FieldByName("Application") 524 | vapc.Set(iv) 525 | 526 | tapc := vapc.Type() 527 | if _, found := tapc.FieldByName("RW"); found { 528 | vapc.FieldByName("RW").Set(rvw) 529 | } 530 | 531 | if _, found := tapc.FieldByName("R"); found { 532 | vapc.FieldByName("R").Set(rvr) 533 | } 534 | 535 | vppc := vapc.FieldByName("Page") 536 | tpc := vpc.Type() 537 | ppc := vppc.Addr().Interface().(*Page) 538 | 539 | if ppc.Config.AutoGenerateHtml { 540 | ppc.Document.GenerateHtml = true 541 | } 542 | 543 | if ppc.CurrentAction != "Init" { 544 | ppc.callMethod(tpc, vpc, "Init", rvr, rvw) 545 | } 546 | 547 | ppc.initModule(w, r) 548 | 549 | if _, ok := tpc.MethodByName(ppc.CurrentAction); ok && ppc.filterMethod(ppc.CurrentAction) { 550 | ppc.filterDoMethod(tpc, vpc, "Before_", rvr, rvw) 551 | ppc.callMethod(tpc, vpc, "Before_"+ppc.CurrentAction, rvr, rvw) 552 | 553 | if ppc.Document.Close == false { 554 | ppc.callMethod(tpc, vpc, ppc.CurrentAction, rvr, rvw) 555 | } 556 | 557 | ppc.callMethod(tpc, vpc, "After_"+ppc.CurrentAction, rvr, rvw) 558 | ppc.filterDoMethod(tpc, vpc, "After_", rvr, rvw) 559 | } else { 560 | if !strings.Contains(tpc.String(), "Page404") { 561 | notFountRV := reflect.ValueOf(ppc.NotFoundtController) 562 | notFountRT := notFountRV.Type() 563 | vnpc := reflect.New(notFountRT) 564 | vanpc := vnpc.Elem().FieldByName("Application") 565 | vanpc.Set(vapc) 566 | vpnpc := vanpc.FieldByName("Page") 567 | vpnpc.Set(vppc) 568 | 569 | ppc = vpnpc.Addr().Interface().(*Page) 570 | tnpc := vnpc.Type() 571 | ppc.callMethod(tnpc, vnpc, "Init", rvr, rvw) 572 | } 573 | } 574 | 575 | if ppc.site.supportSession { 576 | switch ppc.Config.SessionType { 577 | case "file": 578 | ppc.FileSession.Set(ppc.SESSION, w, r) 579 | case "memory": 580 | ppc.MemorySession.Set(w, r) 581 | default: 582 | ppc.MemorySession.Set(w, r) 583 | } 584 | } 585 | 586 | if ppc.site.supportCookieSession { 587 | ppc.CookieSession.Set(ppc.COOKIE_SESSION, w, r) 588 | } 589 | 590 | if !(p.Document.Close || p.Document.Hide) && ppc.Config.SupportTemplate { 591 | ppc.setStaticDocument() 592 | ppc.routeTemplate(w, r) 593 | } 594 | } 595 | 596 | func (p *Page) setStaticDocument() { 597 | fileNameNoExt := p.currentFileName[:len(p.currentFileName)-len(filepath.Ext(p.currentFileName))] 598 | 599 | cssFi, cssErr := os.Stat(p.Config.StaticCssDirectory + p.currentPath) 600 | jsFi, jsErr := os.Stat(p.Config.StaticJsDirectory + p.currentPath) 601 | imgFi, imgErr := os.Stat(p.Config.StaticImgDirectory + p.currentPath) 602 | 603 | if cssErr == nil && cssFi.IsDir() { 604 | cssPath := strings.Trim(p.currentPath, "/") 605 | DcssPath := p.Config.StaticCssDirectory + cssPath + "/" 606 | p.Document.Css[cssPath] = p.site.Root + DcssPath[len(p.Config.AssetsDirectory):] 607 | log.Debug(" ", "p.Document.Css["+cssPath+"]:", p.Document.Css[cssPath]) 608 | 609 | _, errgcss := os.Stat(DcssPath + "global.css") 610 | _, errcss := os.Stat(DcssPath + fileNameNoExt + ".css") 611 | 612 | if errgcss == nil { 613 | p.Document.GlobalIndexCssFile = p.Document.Css[cssPath] + "global.css" 614 | log.Debug(" ", "p.Document.GlobalIndexCssFile:", p.Document.GlobalIndexCssFile) 615 | } 616 | 617 | if errcss == nil { 618 | p.Document.IndexCssFile = p.Document.Css[cssPath] + fileNameNoExt + ".css" 619 | log.Debug(" ", "p.Document.IndexCssFile:", p.Document.IndexCssFile) 620 | } 621 | } 622 | 623 | if jsErr == nil && jsFi.IsDir() { 624 | jsPath := strings.Trim(p.currentPath, "/") 625 | DjsPath := p.Config.StaticJsDirectory + jsPath + "/" 626 | p.Document.Js[jsPath] = p.site.Root + DjsPath[len(p.Config.AssetsDirectory):] 627 | log.Debug(" ", "p.Document.Js["+jsPath+"]:", p.Document.Js[jsPath]) 628 | 629 | _, errgjs := os.Stat(DjsPath + "global.js") 630 | _, errjs := os.Stat(DjsPath + fileNameNoExt + ".js") 631 | 632 | if errgjs == nil { 633 | p.Document.GlobalIndexJsFile = p.Document.Js[jsPath] + "global.js" 634 | log.Debug(" ", "p.Document.GlobalIndexJsFile:", p.Document.GlobalIndexJsFile) 635 | } 636 | 637 | if errjs == nil { 638 | p.Document.IndexJsFile = p.Document.Js[jsPath] + fileNameNoExt + ".js" 639 | log.Debug(" ", "p.Document.IndexJsFile:", p.Document.IndexJsFile) 640 | } 641 | } 642 | 643 | if imgErr == nil && imgFi.IsDir() { 644 | imgPath := strings.Trim(p.currentPath, "/") 645 | DimgPath := p.Config.StaticImgDirectory + imgPath + "/" 646 | p.Document.Img[imgPath] = p.site.Root + DimgPath[len(p.Config.AssetsDirectory):] 647 | log.Debug(" ", "p.Document.Img["+imgPath+"]:", p.Document.Img[imgPath]) 648 | } 649 | } 650 | 651 | func (p *Page) routeTemplate(w http.ResponseWriter, r *http.Request) { 652 | if globalTplFi, err := os.Stat(p.Config.TemplateDirectory + p.Config.ThemeDirectory + p.Config.TemplateGlobalDirectory); err != nil { 653 | log.Error(" ", err) 654 | } else { 655 | if globalTplCache := p.site.GetTemplateCache("globalTpl"); globalTplCache.ModTime > 0 { 656 | if globalTplFi.ModTime().Unix() > globalTplCache.ModTime { 657 | p.setGlobalTpl(globalTplFi.ModTime().Unix(), true) 658 | } 659 | } 660 | } 661 | 662 | if tplFi, err := os.Stat(p.Config.TemplateDirectory + p.Config.ThemeDirectory + p.Template); err != nil { 663 | p.site.base.rmutex.RLock() 664 | tmplCache := p.site.GetTemplateCache(p.Template) 665 | if tmplCache.ModTime > 0 { 666 | p.site.base.mutex.Lock() 667 | p.DelTemplateCache(p.Template) 668 | p.site.base.mutex.Unlock() 669 | log.Info(" ", "Delete Template Cache:", p.Template) 670 | } 671 | p.site.base.rmutex.RUnlock() 672 | } else { 673 | p.site.base.rmutex.RLock() 674 | tmplCache := p.site.GetTemplateCache(p.Template) 675 | 676 | if tplFi.ModTime().Unix() > tmplCache.ModTime { 677 | p.site.base.mutex.Lock() 678 | p.SetTemplateCache(p.Template, p.Config.TemplateDirectory+p.Config.ThemeDirectory+p.Template) 679 | p.site.base.mutex.Unlock() 680 | 681 | tmplCache = p.site.GetTemplateCache(p.Template) 682 | } 683 | 684 | globalTemplate, _ := p.site.globalTemplate.Clone() 685 | p.site.base.rmutex.RUnlock() 686 | pageTemplate, err := globalTemplate.New(filepath.Base(p.Template)).Parse(tmplCache.Content) 687 | 688 | if err != nil { 689 | log.Error(" ", err) 690 | w.Write([]byte(fmt.Sprint(err))) 691 | } else { 692 | templateVar := map[string]interface{}{ 693 | "G": p.GET, 694 | "P": p.POST, 695 | "S": p.SESSION, 696 | "O_S": p.ONCE_SESSION, 697 | "C": p.COOKIE, 698 | "CS": p.COOKIE_SESSION, 699 | "D": p.Document, 700 | "L": p.LANG, 701 | "Config": p.Config.M, 702 | "Template": p.Template, 703 | } 704 | 705 | p.site.base.rmutex.RLock() 706 | templateVar["Siteroot"] = p.site.Root 707 | templateVar["Version"] = p.site.Version 708 | p.site.base.rmutex.RUnlock() 709 | 710 | if !p.Document.GenerateHtml { 711 | err := pageTemplate.Execute(w, templateVar) 712 | if err != nil { 713 | log.Error(" ", err) 714 | w.Write([]byte(fmt.Sprint(err))) 715 | } 716 | } else { 717 | htmlFile := "" 718 | assetsHtmlDir := p.Config.AssetsDirectory + p.Config.HtmlDirectory 719 | if strings.HasPrefix(p.Template, p.Config.IndexDirectory) { 720 | htmlFile = assetsHtmlDir + strings.Replace(p.Template, p.Config.IndexDirectory, "", 1) 721 | } else { 722 | htmlFile = assetsHtmlDir + p.Template 723 | } 724 | 725 | if r.URL.RawQuery != "" { 726 | htmlFile += "?" + r.URL.RawQuery 727 | } 728 | 729 | htmlDir := filepath.Dir(htmlFile) 730 | if htmlDirFi, err := os.Stat(htmlDir); err != nil || !htmlDirFi.IsDir() { 731 | os.MkdirAll(htmlDir, 0777) 732 | log.Info(" ", "MkdirAll:", htmlDir) 733 | } 734 | 735 | htmlFi, htmlErr := os.Stat(htmlFile) 736 | if p.checkHtmlDoWrite(tplFi, htmlFi, htmlErr) { 737 | if file, err := os.OpenFile(htmlFile, os.O_CREATE|os.O_WRONLY, 0777); err != nil { 738 | log.Error(" ", err) 739 | } else { 740 | if p.Config.AutoJumpToHtml || p.Config.ChangeSiteRoot { 741 | p.site.base.rmutex.RLock() 742 | templateVar["Siteroot"] = p.site.Root + p.Config.HtmlDirectory 743 | p.site.base.rmutex.RUnlock() 744 | } 745 | 746 | pageTemplate.Execute(file, templateVar) 747 | } 748 | } 749 | 750 | if p.Config.AutoJumpToHtml { 751 | p.site.base.rmutex.RLock() 752 | siteRoot := p.site.Root 753 | p.site.base.rmutex.RUnlock() 754 | http.Redirect(w, r, siteRoot+htmlFile[len(p.Config.AssetsDirectory):], http.StatusFound) 755 | } else if p.Config.AutoLoadStaticHtml && htmlFi != nil && htmlErr == nil { 756 | htmlContent, err := ioutil.ReadFile(htmlFile) 757 | if err == nil { 758 | w.Write(htmlContent) 759 | } else { 760 | log.Error(" ", err) 761 | } 762 | } else { 763 | err := pageTemplate.Execute(w, templateVar) 764 | if err != nil { 765 | log.Error(" ", err) 766 | } 767 | } 768 | } 769 | } 770 | } 771 | } 772 | 773 | func (p *Page) checkHtmlDoWrite(tplFi, htmlFi os.FileInfo, htmlErr error) bool { 774 | var doWrite bool 775 | log.Debug(" ", "p.Config.AutoGenerateHtmlCycleTime:", p.Config.AutoGenerateHtmlCycleTime) 776 | if p.Config.AutoGenerateHtmlCycleTime <= 0 { 777 | doWrite = true 778 | } else { 779 | if htmlErr != nil { 780 | doWrite = true 781 | } else { 782 | log.Debug(" ", "tplFi.ModTime().Unix():", tplFi.ModTime().Unix()) 783 | log.Debug(" ", "htmlFi.ModTime().Unix():", htmlFi.ModTime().Unix()) 784 | 785 | switch { 786 | case tplFi.ModTime().Unix() >= htmlFi.ModTime().Unix(): 787 | doWrite = true 788 | case time.Now().Unix()-htmlFi.ModTime().Unix() >= p.Config.AutoGenerateHtmlCycleTime: 789 | doWrite = true 790 | default: 791 | globalTplCache := p.site.GetTemplateCache("globalTpl") 792 | log.Debug(" ", `globalTplCache.ModTime:`, globalTplCache.ModTime) 793 | if globalTplCache.ModTime > 0 && globalTplCache.ModTime >= htmlFi.ModTime().Unix() { 794 | doWrite = true 795 | } 796 | } 797 | } 798 | } 799 | 800 | return doWrite 801 | } 802 | 803 | func (p *Page) handleRootStatic(files string) { 804 | aFile := strings.Split(files, ",") 805 | for _, file := range aFile { 806 | http.HandleFunc(p.site.Root+file, func(w http.ResponseWriter, r *http.Request) { 807 | staticPath := p.Config.AssetsDirectory + file 808 | log.Debug(" ", "staticPath:", staticPath) 809 | http.ServeFile(w, r, staticPath) 810 | }) 811 | } 812 | } 813 | 814 | func (p *Page) handleStatic() { 815 | http.HandleFunc(p.Document.Static, func(w http.ResponseWriter, r *http.Request) { 816 | if p.UrlManage.Manage() { 817 | newUrl := p.UrlManage.ReWrite(w, r) 818 | if newUrl == "redirect" { 819 | p.site.base.mutex.Unlock() 820 | return 821 | } else { 822 | r.URL, _ = url.Parse(newUrl) 823 | } 824 | } 825 | 826 | staticPath := p.Config.AssetsDirectory + p.Config.StaticDirectory + r.URL.Path[len(p.Document.Static):] 827 | log.Debug(" ", "staticPath:", staticPath) 828 | http.ServeFile(w, r, staticPath) 829 | }) 830 | } 831 | 832 | func (p *Page) handleStaticHtml() { 833 | StaticHtmlDir := p.Config.SiteRoot + p.Config.HtmlDirectory 834 | http.HandleFunc(StaticHtmlDir, func(w http.ResponseWriter, r *http.Request) { 835 | if p.UrlManage.Manage() { 836 | newUrl := p.UrlManage.ReWrite(w, r) 837 | if newUrl == "redirect" { 838 | p.site.base.mutex.Unlock() 839 | return 840 | } else { 841 | r.URL, _ = url.Parse(newUrl) 842 | } 843 | } 844 | 845 | staticPath := p.Config.AssetsDirectory + p.Config.HtmlDirectory + r.URL.Path[len(StaticHtmlDir):] 846 | if r.URL.RawQuery != "" { 847 | staticPath += "?" + r.URL.RawQuery 848 | } 849 | 850 | log.Debug(" ", "staticPath:", staticPath) 851 | http.ServeFile(w, r, staticPath) 852 | }) 853 | } 854 | 855 | func (p *Page) handleRoute(i interface{}) { 856 | http.HandleFunc(p.site.Root, func(w http.ResponseWriter, r *http.Request) { 857 | p.site.base.mutex.Lock() 858 | if p.Config.Reload() { 859 | p.reset(true) 860 | } 861 | 862 | if p.UrlManage.Manage() { 863 | newUrl := p.UrlManage.ReWrite(w, r) 864 | if newUrl == "redirect" { 865 | p.site.base.mutex.Unlock() 866 | return 867 | } else { 868 | r.URL, _ = url.Parse(newUrl) 869 | } 870 | } 871 | 872 | if p.site.supportI18n { 873 | if err := p.I18n.Setup(p.Config.DefaultLocalePath, p.Config.DefaultLanguage); err != nil { 874 | log.Panic(" ", "I18n(Setup):", err) 875 | } 876 | } 877 | 878 | p.setCurrentInfo(r.URL.Path) 879 | p.Template = p.CurrentController + p.currentFileName 880 | p.site.base.mutex.Unlock() 881 | log.Debug(" ", "p.Template:", p.Template) 882 | 883 | if !p.loadGeneratedHtml(w, r) { 884 | p.routeController(i, w, r) 885 | } 886 | }) 887 | } 888 | 889 | func (p *Page) ListenAndServe(addr string, i interface{}) { 890 | if p.Config.SupportStatic { 891 | p.handleRootStatic(p.Config.RootStaticFiles) 892 | p.handleStatic() 893 | } 894 | 895 | if p.Config.AccessHtml { 896 | p.handleStaticHtml() 897 | } 898 | 899 | p.handleRoute(i) 900 | 901 | err := http.ListenAndServe(addr, nil) 902 | if err != nil { 903 | log.Fatal(" ", err) 904 | } 905 | } 906 | -------------------------------------------------------------------------------- /web/site.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "golanger.com/log" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "text/template" 9 | ) 10 | 11 | type templateCache struct { 12 | ModTime int64 13 | Content string 14 | } 15 | 16 | type site struct { 17 | *base 18 | supportSession bool 19 | supportCookieSession bool 20 | supportI18n bool 21 | templateFunc template.FuncMap 22 | templateCache map[string]templateCache 23 | globalTemplate *template.Template 24 | Root string 25 | Version string 26 | } 27 | 28 | func (s *site) Init(w http.ResponseWriter, r *http.Request) *site { 29 | s.base.Init(w, r) 30 | 31 | return s 32 | } 33 | 34 | func (s *site) AddTemplateFunc(name string, i interface{}) { 35 | _, ok := s.templateFunc[name] 36 | if !ok { 37 | s.templateFunc[name] = i 38 | } else { 39 | log.Warn(" ", "func:"+name+" be added,do not repeat to add") 40 | } 41 | } 42 | 43 | func (s *site) DelTemplateFunc(name string) { 44 | if _, ok := s.templateFunc[name]; ok { 45 | delete(s.templateFunc, name) 46 | } 47 | } 48 | 49 | func (s *site) SetTemplateCacheObject(tmplKey, content string, modTime int64) { 50 | s.templateCache[tmplKey] = templateCache{ 51 | ModTime: modTime, 52 | Content: content, 53 | } 54 | } 55 | 56 | func (s *site) SetTemplateCache(tmplKey, tmplPath string) { 57 | if tmplFi, err := os.Stat(tmplPath); err == nil { 58 | if b, err := ioutil.ReadFile(tmplPath); err == nil { 59 | s.SetTemplateCacheObject(tmplKey, string(b), tmplFi.ModTime().Unix()) 60 | } 61 | } 62 | 63 | } 64 | 65 | func (s *site) GetTemplateCache(tmplKey string) templateCache { 66 | if tmpl, ok := s.templateCache[tmplKey]; ok { 67 | return tmpl 68 | } 69 | 70 | return templateCache{} 71 | } 72 | 73 | func (s *site) DelTemplateCache(tmplKey string) { 74 | delete(s.templateCache, tmplKey) 75 | } 76 | --------------------------------------------------------------------------------