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

feat: add net/http adapter #645

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
233 changes: 233 additions & 0 deletions adapter/nethttp/nethttp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package nethttp

import (
"bytes"
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"

"github.com/GoAdminGroup/go-admin/adapter"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/engine"
cfg "github.com/GoAdminGroup/go-admin/modules/config"
"github.com/GoAdminGroup/go-admin/modules/constant"
"github.com/GoAdminGroup/go-admin/plugins"
"github.com/GoAdminGroup/go-admin/plugins/admin/models"
"github.com/GoAdminGroup/go-admin/template/types"
)

type NetHTTP struct {
adapter.BaseAdapter
ctx Context
app *http.ServeMux
}

func init() {
engine.Register(new(NetHTTP))
}

// User implements the method Adapter.User.
func (nh *NetHTTP) User(ctx interface{}) (models.UserModel, bool) {
return nh.GetUser(ctx, nh)
}

// Use implements the method Adapter.Use.
func (nh *NetHTTP) Use(app interface{}, plugs []plugins.Plugin) error {
return nh.GetUse(app, plugs, nh)
}

// Content implements the method Adapter.Content.
func (nh *NetHTTP) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) {
nh.GetContent(ctx, getPanelFn, nh, btns, fn)
}

type HandlerFunc func(ctx Context) (types.Panel, error)

func Content(handler HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
ctx := Context{
Request: request,
Response: writer,
}
engine.Content(ctx, func(ctx interface{}) (types.Panel, error) {
return handler(ctx.(Context))
})
}
}

// SetApp implements the method Adapter.SetApp.
func (nh *NetHTTP) SetApp(app interface{}) error {
var (
eng *http.ServeMux
ok bool
)
if eng, ok = app.(*http.ServeMux); !ok {
return errors.New("net/http adapter SetApp: wrong parameter")
}
nh.app = eng
return nil
}

// AddHandler implements the method Adapter.AddHandler.
func (nh *NetHTTP) AddHandler(method, path string, handlers context.Handlers) {
url := path
reg1 := regexp.MustCompile(":(.*?)/")
reg2 := regexp.MustCompile(":(.*?)$")
url = reg1.ReplaceAllString(url, "{$1}/")
url = reg2.ReplaceAllString(url, "{$1}")

if len(url) > 1 && url[0] == '/' && url[1] == '/' {
url = url[1:]
}

pattern := fmt.Sprintf("%s %s", strings.ToUpper(method), url)

nh.app.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path[len(r.URL.Path)-1] == '/' {
r.URL.Path = r.URL.Path[:len(r.URL.Path)-1]
}

ctx := context.NewContext(r)

params := getPathParams(url, r.URL.Path)
for key, value := range params {
if r.URL.RawQuery == "" {
r.URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + value
} else {
r.URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + value
}
}

ctx.SetHandlers(handlers).Next()
for key, head := range ctx.Response.Header {
w.Header().Set(key, head[0])
}
if ctx.Response.Body != nil {
buf := new(bytes.Buffer)
_, _ = buf.ReadFrom(ctx.Response.Body)
w.WriteHeader(ctx.Response.StatusCode)
_, _ = w.Write(buf.Bytes())
} else {
w.WriteHeader(ctx.Response.StatusCode)
}
})
}

// HandleFun is type of route methods of chi.
type HandleFun func(pattern string, handlerFn http.HandlerFunc)

// Context wraps the Request and Response object of Chi.
type Context struct {
Request *http.Request
Response http.ResponseWriter
}

// SetContext implements the method Adapter.SetContext.
func (*NetHTTP) SetContext(contextInterface interface{}) adapter.WebFrameWork {
var (
ctx Context
ok bool
)
if ctx, ok = contextInterface.(Context); !ok {
panic("net/http adapter SetContext: wrong parameter")
}
return &NetHTTP{ctx: ctx}
}

// Name implements the method Adapter.Name.
func (*NetHTTP) Name() string {
return "net/http"
}

// Redirect implements the method Adapter.Redirect.
func (nh *NetHTTP) Redirect() {
http.Redirect(nh.ctx.Response, nh.ctx.Request, cfg.Url(cfg.GetLoginUrl()), http.StatusFound)
}

// SetContentType implements the method Adapter.SetContentType.
func (nh *NetHTTP) SetContentType() {
nh.ctx.Response.Header().Set("Content-Type", nh.HTMLContentType())
}

// Write implements the method Adapter.Write.
func (nh *NetHTTP) Write(body []byte) {
nh.ctx.Response.WriteHeader(http.StatusOK)
_, _ = nh.ctx.Response.Write(body)
}

// GetCookie implements the method Adapter.GetCookie.
func (nh *NetHTTP) GetCookie() (string, error) {
cookie, err := nh.ctx.Request.Cookie(nh.CookieKey())
if err != nil {
return "", err
}
return cookie.Value, err
}

// Lang implements the method Adapter.Lang.
func (nh *NetHTTP) Lang() string {
return nh.ctx.Request.URL.Query().Get("__ga_lang")
}

// Path implements the method Adapter.Path.
func (nh *NetHTTP) Path() string {
return nh.ctx.Request.URL.Path
}

// Method implements the method Adapter.Method.
func (nh *NetHTTP) Method() string {
return nh.ctx.Request.Method
}

// FormParam implements the method Adapter.FormParam.
func (nh *NetHTTP) FormParam() url.Values {
_ = nh.ctx.Request.ParseMultipartForm(32 << 20)
return nh.ctx.Request.PostForm
}

// IsPjax implements the method Adapter.IsPjax.
func (nh *NetHTTP) IsPjax() bool {
return nh.ctx.Request.Header.Get(constant.PjaxHeader) == "true"
}

// Query implements the method Adapter.Query.
func (nh *NetHTTP) Query() url.Values {
return nh.ctx.Request.URL.Query()
}

// Request implements the method Adapter.Request.
func (nh *NetHTTP) Request() *http.Request {
return nh.ctx.Request
}

// getPathParams extracts path parameters from a URL based on a given pattern.
func getPathParams(pattern, url string) map[string]string {
params := make(map[string]string)

// Convert pattern to regex
placeholderRegex := regexp.MustCompile(`\{(\w+)\}`)
regexPattern := "^" + placeholderRegex.ReplaceAllStringFunc(pattern, func(s string) string {
return `(?P<` + s[1:len(s)-1] + `>\w+)`
}) + `$`

// Compile regex
regex := regexp.MustCompile(regexPattern)

// Match the URL against the regex
match := regex.FindStringSubmatch(url)
if match == nil {
return nil
}

// Extract named groups
for i, name := range regex.SubexpNames() {
if i != 0 && name != "" { // Ignore the whole match at index 0
params[name] = match[i]
}
}

return params
}
126 changes: 126 additions & 0 deletions examples/nethttp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package main

import (
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"time"

_ "github.com/GoAdminGroup/go-admin/adapter/nethttp"
_ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql"

"github.com/GoAdminGroup/go-admin/engine"
"github.com/GoAdminGroup/go-admin/examples/datamodel"
"github.com/GoAdminGroup/go-admin/modules/config"
"github.com/GoAdminGroup/go-admin/modules/language"
"github.com/GoAdminGroup/go-admin/plugins/example"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/chartjs"
"github.com/GoAdminGroup/themes/adminlte"
)

func main() {
r := http.NewServeMux()

eng := engine.Default()

cfg := config.Config{
Env: config.EnvLocal,
Databases: config.DatabaseList{
"default": {
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pwd: "root",
Name: "godmin",
MaxIdleConns: 50,
MaxOpenConns: 150,
ConnMaxLifetime: time.Hour,
Driver: config.DriverMysql,
},
},
UrlPrefix: "admin",
Store: config.Store{
Path: "./uploads",
Prefix: "uploads",
},
Language: language.EN,
IndexUrl: "/",
Debug: true,
ColorScheme: adminlte.ColorschemeSkinBlack,
}

template.AddComp(chartjs.NewChart())

// customize a plugin

examplePlugin := example.NewExample()

// load from golang.Plugin
//
// examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so")

// customize the login page
// example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39
//
// template.AddComp("login", datamodel.LoginPage)

// load config from json file
//
// eng.AddConfigFromJSON("../datamodel/config.json")

if err := eng.AddConfig(&cfg).
AddGenerators(datamodel.Generators).
AddDisplayFilterXssJsFilter().
// add generator, first parameter is the url prefix of table when visit.
// example:
//
// "user" => http://localhost:9033/admin/info/user
//
AddGenerator("user", datamodel.GetUserTable).
AddPlugins(examplePlugin).
Use(r); err != nil {
panic(err)
}

workDir, _ := os.Getwd()
filesDir := filepath.Join(workDir, "uploads")
FileServer(r, "/uploads", http.Dir(filesDir))

// you can custom your pages like:

eng.HTML("GET", "/admin", datamodel.GetContent)

go func() {
_ = http.ListenAndServe(":3333", r)
}()

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
log.Print("closing database connection")
eng.MysqlConnection().Close()
}

// FileServer conveniently sets up a http.FileServer handler to serve
// static files from a http.FileSystem.
func FileServer(r *http.ServeMux, path string, root http.FileSystem) {
if strings.ContainsAny(path, "{}*") {
panic("FileServer does not permit URL parameters.")
}

fs := http.StripPrefix(path, http.FileServer(root))

if path != "/" && path[len(path)-1] != '/' {
r.HandleFunc("GET "+path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
path += "/"
}
path += "*"

r.HandleFunc("GET "+path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
}))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/GoAdminGroup/go-admin

go 1.21.5
go 1.22.10

require (
github.com/360EntSecGroup-Skylar/excelize v1.4.1
Expand Down
Loading