github.com/goplus/yap@v0.8.1/yap.go (about) 1 /* 2 * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package yap 18 19 import ( 20 "html/template" 21 "io/fs" 22 "log" 23 "net/http" 24 "os" 25 "strings" 26 27 "github.com/goplus/yap/internal/htmltempl" 28 "github.com/goplus/yap/noredirect" 29 ) 30 31 type H map[string]interface{} 32 33 type Engine struct { 34 router 35 Mux *http.ServeMux 36 37 delimLeft, delimRight string 38 39 tpl htmltempl.Template 40 fs fs.FS 41 las func(addr string, handler http.Handler) error 42 } 43 44 // New creates a YAP engine. 45 func New(fs ...fs.FS) *Engine { 46 e := new(Engine) 47 e.InitYap(fs...) 48 return e 49 } 50 51 // InitYap initialize a YAP application. 52 func (p *Engine) InitYap(fs ...fs.FS) { 53 if p.Mux == nil { 54 p.Mux = http.NewServeMux() 55 p.las = http.ListenAndServe 56 p.router.init() 57 } 58 if fs != nil { 59 p.initYapFS(fs[0]) 60 } 61 } 62 63 func (p *Engine) initYapFS(fsys fs.FS) { 64 const name = "yap" 65 if f, e := fsys.Open(name); e == nil { 66 f.Close() 67 if sub, e := fs.Sub(fsys, name); e == nil { 68 fsys = sub 69 } 70 } 71 p.fs = fsys 72 } 73 74 func (p *Engine) yapFS() fs.FS { 75 if p.fs == nil { 76 p.initYapFS(os.DirFS(".")) 77 } 78 return p.fs 79 } 80 81 func (p *Engine) NewContext(w http.ResponseWriter, r *http.Request) *Context { 82 ctx := &Context{ResponseWriter: w, Request: r, engine: p} 83 return ctx 84 } 85 86 // ServeHTTP makes the router implement the http.Handler interface. 87 func (p *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 88 p.router.serveHTTP(w, req, p) 89 } 90 91 // FS returns a $YapFS sub filesystem by specified a dir. 92 func (p *Engine) FS(dir string) (ret fs.FS) { 93 return SubFS(p.yapFS(), dir) 94 } 95 96 // Static serves static files from a dir (default is "$YapFS/static"). 97 func (p *Engine) Static(pattern string, dir ...fs.FS) { 98 var fsys fs.FS 99 if dir != nil { 100 fsys = dir[0] 101 } else { 102 fsys = p.FS("static") 103 } 104 p.StaticHttp(pattern, http.FS(fsys)) 105 } 106 107 // StaticHttp serves static files from fsys (http.FileSystem). 108 func (p *Engine) StaticHttp(pattern string, fsys http.FileSystem, allowRedirect ...bool) { 109 if !strings.HasSuffix(pattern, "/") { 110 pattern += "/" 111 } 112 allow := true 113 if allowRedirect != nil { 114 allow = allowRedirect[0] 115 } 116 var server http.Handler 117 if allow { 118 server = http.FileServer(fsys) 119 } else { 120 server = noredirect.FileServer(fsys) 121 } 122 p.Mux.Handle(pattern, http.StripPrefix(pattern, server)) 123 } 124 125 // Handle registers the handler function for the given pattern. 126 func (p *Engine) Handle(pattern string, f func(ctx *Context)) { 127 p.Mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { 128 f(p.NewContext(w, r)) 129 }) 130 } 131 132 // Handler returns the main entry that responds to HTTP requests. 133 func (p *Engine) Handler(mws ...func(h http.Handler) http.Handler) http.Handler { 134 h := http.Handler(p) 135 for _, mw := range mws { 136 h = mw(h) 137 } 138 return h 139 } 140 141 // Run listens on the TCP network address addr and then calls 142 // Serve with handler to handle requests on incoming connections. 143 // Accepted connections are configured to enable TCP keep-alives. 144 func (p *Engine) Run(addr string, mws ...func(h http.Handler) http.Handler) error { 145 h := p.Handler(mws...) 146 log.Println("Listen", addr) 147 err := p.las(addr, h) 148 if err != nil { 149 log.Fatalln(err) 150 } 151 return err 152 } 153 154 // SetLAS sets listenAndServe func to listens on the TCP network address addr 155 // and to handle requests on incoming connections. 156 func (p *Engine) SetLAS(listenAndServe func(addr string, handler http.Handler) error) { 157 p.las = listenAndServe 158 } 159 160 // SetDelims sets the action delimiters to the specified strings. Nested template definitions 161 // will inherit the settings. An empty delimiter stands for the corresponding default: {{ or }}. 162 func (p *Engine) SetDelims(left, right string) { 163 p.delimLeft, p.delimRight = left, right 164 } 165 166 func (p *Engine) templ(path string) *template.Template { 167 if p.tpl.Template == nil { 168 p.tpl.InitTemplates(p.yapFS(), p.delimLeft, p.delimRight, "_yap.html") 169 } 170 return p.tpl.Lookup(path) 171 } 172 173 // SubFS returns a sub filesystem by specified a dir. 174 func SubFS(fsys fs.FS, dir string) (ret fs.FS) { 175 f, err := fsys.Open(dir) 176 if err == nil { 177 f.Close() 178 ret, err = fs.Sub(fsys, dir) 179 } 180 if err != nil { 181 log.Panicln("Get $YapFS sub filesystem failed:", err) 182 } 183 return ret 184 }