Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

yap: classfile v2 #94

Merged
merged 8 commits into from
Mar 8, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
yap: classfile v2
xushiwei committed Mar 8, 2024
commit ad162fdc626b444080fc2d9600a39bddf5b5ec11
4 changes: 4 additions & 0 deletions classfile.go
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ const (
GopPackage = true
)

// App is project class of YAP classfile (old version).
type App struct {
Engine
}
@@ -86,6 +87,9 @@ func (p *App) Static__2(pattern string, fs http.FileSystem, allowRedirect ...boo
type AppType interface {
InitYap(fs ...fs.FS)
SetLAS(listenAndServe func(addr string, handler http.Handler) error)
Route(method, path string, handle func(ctx *Context))
Handle(pattern string, f func(ctx *Context))
Run(addr string, mws ...func(h http.Handler) http.Handler) error
}

var (
63 changes: 63 additions & 0 deletions classfile_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package yap

import (
"reflect"
"strings"
)

// Handler is worker class of YAP classfile (v2).
type Handler struct {
Context
}

func (p *Handler) Main(ctx *Context) {
p.Context = *ctx
}

func parseHandlerName(name string) (method, path string) {
pos := strings.IndexByte(name, '_')
if pos < 0 {
return name, "/"
}
return name[:pos], strings.ReplaceAll(name[pos:], "_", "/")
}

// AppV2 is project class of YAP classfile (v2).
type AppV2 struct {
App
}

// Gopt_AppV2_Main is required by Go+ compiler as the entry of a YAP project.
func Gopt_AppV2_Main(app AppType, handlers ...interface{ Main(ctx *Context) }) {
app.InitYap()
for _, h := range handlers {
name := reflect.TypeOf(h).Elem().Name() // class name of handler
switch method, path := parseHandlerName(name); method {
case "handle":
app.Handle(path, h.Main)
default:
app.Route(method, path, h.Main)
}
}
if me, ok := app.(interface{ MainEntry() }); ok {
me.MainEntry()
} else {
app.Run(":8080")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[golangci-lint] Error return value of app.Run is not checked (errcheck)

If you have any questions about this comment, feel free to raise an issue here:

}
}
3 changes: 3 additions & 0 deletions demo/classfile2_hello/get_p_:id.yap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
json {
"id": param("id"),
}
32 changes: 32 additions & 0 deletions demo/classfile2_hello/gop_autogen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions demo/classfile2_hello/handle.yap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
html `<html><body>Hello, <a href="/p/123">Yap</a>!</body></html>`
4 changes: 4 additions & 0 deletions gop.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
gop 1.2

project .yap AppV2 github.com/goplus/yap

class .yap Handler

project _yap.gox App github.com/goplus/yap

project _yapt.gox App github.com/goplus/yap/ytest github.com/goplus/yap/test
94 changes: 47 additions & 47 deletions router.go
Original file line number Diff line number Diff line change
@@ -82,46 +82,46 @@ type router struct {
HandleOPTIONS bool
}

func (r *router) init() {
r.RedirectTrailingSlash = true
r.RedirectFixedPath = true
r.HandleMethodNotAllowed = true
r.HandleOPTIONS = true
func (p *router) init() {
p.RedirectTrailingSlash = true
p.RedirectFixedPath = true
p.HandleMethodNotAllowed = true
p.HandleOPTIONS = true
}

// GET is a shortcut for router.Route(http.MethodGet, path, handle)
func (r *router) GET(path string, handle func(ctx *Context)) {
r.Route(http.MethodGet, path, handle)
func (p *router) GET(path string, handle func(ctx *Context)) {
p.Route(http.MethodGet, path, handle)
}

// HEAD is a shortcut for router.Route(http.MethodHead, path, handle)
func (r *router) HEAD(path string, handle func(ctx *Context)) {
r.Route(http.MethodHead, path, handle)
func (p *router) HEAD(path string, handle func(ctx *Context)) {
p.Route(http.MethodHead, path, handle)
}

// OPTIONS is a shortcut for router.Route(http.MethodOptions, path, handle)
func (r *router) OPTIONS(path string, handle func(ctx *Context)) {
r.Route(http.MethodOptions, path, handle)
func (p *router) OPTIONS(path string, handle func(ctx *Context)) {
p.Route(http.MethodOptions, path, handle)
}

// POST is a shortcut for router.Route(http.MethodPost, path, handle)
func (r *router) POST(path string, handle func(ctx *Context)) {
r.Route(http.MethodPost, path, handle)
func (p *router) POST(path string, handle func(ctx *Context)) {
p.Route(http.MethodPost, path, handle)
}

// PUT is a shortcut for router.Route(http.MethodPut, path, handle)
func (r *router) PUT(path string, handle func(ctx *Context)) {
r.Route(http.MethodPut, path, handle)
func (p *router) PUT(path string, handle func(ctx *Context)) {
p.Route(http.MethodPut, path, handle)
}

// PATCH is a shortcut for router.Route(http.MethodPatch, path, handle)
func (r *router) PATCH(path string, handle func(ctx *Context)) {
r.Route(http.MethodPatch, path, handle)
func (p *router) PATCH(path string, handle func(ctx *Context)) {
p.Route(http.MethodPatch, path, handle)
}

// DELETE is a shortcut for router.Route(http.MethodDelete, path, handle)
func (r *router) DELETE(path string, handle func(ctx *Context)) {
r.Route(http.MethodDelete, path, handle)
func (p *router) DELETE(path string, handle func(ctx *Context)) {
p.Route(http.MethodDelete, path, handle)
}

// Route registers a new request handle with the given path and method.
@@ -132,7 +132,7 @@ func (r *router) DELETE(path string, handle func(ctx *Context)) {
// This function is intended for bulk loading and to allow the usage of less
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (r *router) Route(method, path string, handle func(ctx *Context)) {
func (p *router) Route(method, path string, handle func(ctx *Context)) {
if method == "" {
panic("method must not be empty")
}
@@ -143,51 +143,51 @@ func (r *router) Route(method, path string, handle func(ctx *Context)) {
panic("handle must not be nil")
}

if r.trees == nil {
r.trees = make(map[string]*node)
if p.trees == nil {
p.trees = make(map[string]*node)
}

root := r.trees[method]
root := p.trees[method]
if root == nil {
root = new(node)
r.trees[method] = root
p.trees[method] = root

r.globalAllowed = r.allowed("*", "")
p.globalAllowed = p.allowed("*", "")
}

root.addRoute(path, handle)
}

func (r *router) recv(w http.ResponseWriter, req *http.Request) {
func (p *router) recv(w http.ResponseWriter, req *http.Request) {
if rcv := recover(); rcv != nil {
r.PanicHandler(w, req, rcv)
p.PanicHandler(w, req, rcv)
}
}

func (r *router) allowed(path, reqMethod string) (allow string) {
func (p *router) allowed(path, reqMethod string) (allow string) {
allowed := make([]string, 0, 9)

if path == "*" { // server-wide
// empty method is used for internal calls to refresh the cache
if reqMethod == "" {
for method := range r.trees {
for method := range p.trees {
if method == http.MethodOptions {
continue
}
// Route request method to list of allowed methods
allowed = append(allowed, method)
}
} else {
return r.globalAllowed
return p.globalAllowed
}
} else { // specific path
for method := range r.trees {
for method := range p.trees {
// Skip the requested method - we already tried this one
if method == reqMethod || method == http.MethodOptions {
continue
}

handle, _ := r.trees[method].getValue(path, nil)
handle, _ := p.trees[method].getValue(path, nil)
if handle != nil {
// Route request method to list of allowed methods
allowed = append(allowed, method)
@@ -215,13 +215,13 @@ func (r *router) allowed(path, reqMethod string) (allow string) {
return allow
}

func (r *router) serveHTTP(w http.ResponseWriter, req *http.Request, e *Engine) {
if r.PanicHandler != nil {
defer r.recv(w, req)
func (p *router) serveHTTP(w http.ResponseWriter, req *http.Request, e *Engine) {
if p.PanicHandler != nil {
defer p.recv(w, req)
}

path := req.URL.Path
root := r.trees[req.Method]
root := p.trees[req.Method]
if root != nil {
ctx := e.NewContext(w, req)
if handle, tsr := root.getValue(path, ctx); handle != nil {
@@ -235,7 +235,7 @@ func (r *router) serveHTTP(w http.ResponseWriter, req *http.Request, e *Engine)
code = http.StatusPermanentRedirect
}

if tsr && r.RedirectTrailingSlash {
if tsr && p.RedirectTrailingSlash {
if len(path) > 1 && path[len(path)-1] == '/' {
req.URL.Path = path[:len(path)-1]
} else {
@@ -246,10 +246,10 @@ func (r *router) serveHTTP(w http.ResponseWriter, req *http.Request, e *Engine)
}

// Try to fix the request path
if r.RedirectFixedPath {
if p.RedirectFixedPath {
fixedPath, found := root.findCaseInsensitivePath(
url.CleanPath(path),
r.RedirectTrailingSlash,
p.RedirectTrailingSlash,
)
if found {
req.URL.Path = fixedPath
@@ -260,20 +260,20 @@ func (r *router) serveHTTP(w http.ResponseWriter, req *http.Request, e *Engine)
}
}

if req.Method == http.MethodOptions && r.HandleOPTIONS {
if req.Method == http.MethodOptions && p.HandleOPTIONS {
// Route OPTIONS requests
if allow := r.allowed(path, http.MethodOptions); allow != "" {
if allow := p.allowed(path, http.MethodOptions); allow != "" {
w.Header().Set("Allow", allow)
if r.GlobalOPTIONS != nil {
r.GlobalOPTIONS.ServeHTTP(w, req)
if p.GlobalOPTIONS != nil {
p.GlobalOPTIONS.ServeHTTP(w, req)
}
return
}
} else if r.HandleMethodNotAllowed { // Route 405
if allow := r.allowed(path, req.Method); allow != "" {
} else if p.HandleMethodNotAllowed { // Route 405
if allow := p.allowed(path, req.Method); allow != "" {
w.Header().Set("Allow", allow)
if r.MethodNotAllowed != nil {
r.MethodNotAllowed.ServeHTTP(w, req)
if p.MethodNotAllowed != nil {
p.MethodNotAllowed.ServeHTTP(w, req)
} else {
http.Error(w,
http.StatusText(http.StatusMethodNotAllowed),