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  }