go-drip: v1.0.0 - initial release
This commit is contained in:
commit
2ad390c1c1
22
.devcontainer/devcontainer.json
Normal file
22
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/go
|
||||||
|
{
|
||||||
|
"name": "Go",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm"
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "go version",
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
|
}
|
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for more information:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# https://containers.dev/guide/dependabot
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "devcontainers"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Timo Riegebauer
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
153
context.go
Normal file
153
context.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/schema"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
response http.ResponseWriter
|
||||||
|
request *http.Request
|
||||||
|
params httprouter.Params
|
||||||
|
ctx context.Context
|
||||||
|
websocketManager *WebsocketManager
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextKey string
|
||||||
|
|
||||||
|
func (c *Context) Request() *http.Request {
|
||||||
|
return c.request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Response() http.ResponseWriter {
|
||||||
|
return c.response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Param(key string) string {
|
||||||
|
return c.params.ByName(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) FormFile(field string) (multipart.File, error) {
|
||||||
|
file, _, err := c.request.FormFile(field)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Query(key string) string {
|
||||||
|
return c.request.URL.Query().Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Header(key string) string {
|
||||||
|
return c.request.Header.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Cookie(name string) (*http.Cookie, error) {
|
||||||
|
return c.request.Cookie(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Value(key ContextKey) any {
|
||||||
|
return c.ctx.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ClientIP() string {
|
||||||
|
ip := c.request.Header.Get("X-Forwarded-For")
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.request.RemoteAddr
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) UserAgent() string {
|
||||||
|
return c.request.UserAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetHeader(key, value string) {
|
||||||
|
c.response.Header().Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetCookie(cookie *http.Cookie) {
|
||||||
|
http.SetCookie(c.response, cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetValue(key ContextKey, value any) {
|
||||||
|
c.ctx = context.WithValue(c.ctx, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Status(statusCode int) {
|
||||||
|
c.response.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Text(statusCode int, message string) error {
|
||||||
|
c.response.Header().Set("Content-Type", "text/plain")
|
||||||
|
c.response.WriteHeader(statusCode)
|
||||||
|
_, err := c.response.Write([]byte(message))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) JSON(statusCode int, v any) error {
|
||||||
|
c.response.Header().Set("Content-Type", "application/json")
|
||||||
|
c.response.WriteHeader(statusCode)
|
||||||
|
return json.NewEncoder(c.response).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Bytes(statusCode int, data []byte, contentType string) error {
|
||||||
|
c.response.Header().Set("Content-Type", contentType)
|
||||||
|
c.response.WriteHeader(statusCode)
|
||||||
|
_, err := c.response.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) HTML(statusCode int, html string) error {
|
||||||
|
c.response.Header().Set("Content-Type", "text/html")
|
||||||
|
c.response.WriteHeader(statusCode)
|
||||||
|
_, err := c.response.Write([]byte(html))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) File(filePath string) error {
|
||||||
|
http.ServeFile(c.response, c.request, filePath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ParseJSON(v any) error {
|
||||||
|
return json.NewDecoder(c.request.Body).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ParseXML(v any) error {
|
||||||
|
return xml.NewDecoder(c.request.Body).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ParseForm(v any) error {
|
||||||
|
if err := c.request.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return schema.NewDecoder().Decode(v, c.request.Form)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ParseMultipartForm(v any, maxMemory int64) error {
|
||||||
|
if err := c.request.ParseMultipartForm(maxMemory); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return schema.NewDecoder().Decode(v, c.request.MultipartForm.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ParseQuery(v any) error {
|
||||||
|
return schema.NewDecoder().Decode(v, c.request.URL.Query())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Redirect(statusCode int, url string) error {
|
||||||
|
http.Redirect(c.response, c.request, url, statusCode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Broadcast(path string, msg []byte) error {
|
||||||
|
return c.websocketManager.Broadcast(path, msg)
|
||||||
|
}
|
144
drip.go
Normal file
144
drip.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Drip struct {
|
||||||
|
router *httprouter.Router
|
||||||
|
errorHandler ErrorHandler
|
||||||
|
middlewares []Middleware
|
||||||
|
logger *log.Logger
|
||||||
|
websocketManager *WebsocketManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Drip {
|
||||||
|
return &Drip{
|
||||||
|
router: httprouter.New(),
|
||||||
|
errorHandler: defaultErrorHandler,
|
||||||
|
middlewares: []Middleware{},
|
||||||
|
logger: log.New(os.Stdout, "[Drip] ", log.LstdFlags),
|
||||||
|
websocketManager: &WebsocketManager{
|
||||||
|
conns: make(map[string]map[*websocket.Conn]bool),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) Start(listenAddr string) error {
|
||||||
|
browsableUrl := listenAddr
|
||||||
|
if strings.HasPrefix(browsableUrl, ":") {
|
||||||
|
browsableUrl = fmt.Sprintf("http://localhost%s", browsableUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Printf("Starting app at %s\n", browsableUrl)
|
||||||
|
return http.ListenAndServe(listenAddr, d.router)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) StartTLS(listenAddr, certFile, keyFile string) error {
|
||||||
|
browsableUrl := listenAddr
|
||||||
|
if strings.HasPrefix(browsableUrl, ":") {
|
||||||
|
browsableUrl = fmt.Sprintf("http://localhost%s", browsableUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Printf("Starting app at %s\n", browsableUrl)
|
||||||
|
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, d.router)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) SetErrorHandler(errorHandler ErrorHandler) {
|
||||||
|
d.errorHandler = errorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) Group(prefix string) *Group {
|
||||||
|
return &Group{
|
||||||
|
prefix: prefix,
|
||||||
|
drip: d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) Websocket(path string, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodGet, path, d.websocketManager.createHandler(), middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) Static(prefix, root string, middlewares ...Middleware) {
|
||||||
|
fileServer := http.FileServer(http.Dir(root))
|
||||||
|
wrappedHandler := d.createHandlerChain(func(ctx *Context) error {
|
||||||
|
http.StripPrefix(prefix, fileServer).ServeHTTP(ctx.response, ctx.request)
|
||||||
|
return nil
|
||||||
|
}, middlewares)
|
||||||
|
|
||||||
|
d.router.Handle(http.MethodGet, prefix+"/*filepath", wrappedHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) Use(middlewares ...Middleware) {
|
||||||
|
d.middlewares = append(d.middlewares, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) GET(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodGet, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) POST(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodPost, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) PUT(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodPut, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) DELETE(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodDelete, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) PATCH(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodPatch, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) HEAD(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodHead, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) OPTIONS(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
d.register(http.MethodOptions, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) register(method string, path string, h Handler, middlewares ...Middleware) {
|
||||||
|
handlers := slices.Clone(middlewares)
|
||||||
|
|
||||||
|
finalHandler := d.createHandlerChain(h, handlers)
|
||||||
|
d.router.Handle(method, path, finalHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Drip) createHandlerChain(h Handler, middlewares []Middleware) httprouter.Handle {
|
||||||
|
return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
ctx := &Context{
|
||||||
|
response: rw,
|
||||||
|
request: r,
|
||||||
|
params: params,
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
finalHandler := h
|
||||||
|
|
||||||
|
for i := len(middlewares) - 1; i >= 0; i-- {
|
||||||
|
finalHandler = middlewares[i](finalHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(d.middlewares) - 1; i >= 0; i-- {
|
||||||
|
finalHandler = d.middlewares[i](finalHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := finalHandler(ctx); err != nil {
|
||||||
|
d.logger.Printf("Error in request %s %s: %v\n", r.Method, r.URL.Path, err)
|
||||||
|
d.errorHandler(err, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
error_handler.go
Normal file
9
error_handler.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type ErrorHandler func(err error, ctx *Context)
|
||||||
|
|
||||||
|
func defaultErrorHandler(err error, c *Context) {
|
||||||
|
c.JSON(http.StatusInternalServerError, Json{"status": "failed", "message": err.Error()})
|
||||||
|
}
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module git.trcreatives.com/triegebauer/go-drip
|
||||||
|
|
||||||
|
go 1.23.8
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/schema v1.4.1
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||||
|
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
101
group.go
Normal file
101
group.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
prefix string
|
||||||
|
middlewares []Middleware
|
||||||
|
parent *Group
|
||||||
|
drip *Drip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Group(prefix string) *Group {
|
||||||
|
return &Group{
|
||||||
|
prefix: prefix,
|
||||||
|
parent: g,
|
||||||
|
drip: g.drip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Websocket(path string, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodGet, path, g.drip.websocketManager.createHandler(), middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Static(prefix, root string, middlewares ...Middleware) {
|
||||||
|
fileServer := http.FileServer(http.Dir(root))
|
||||||
|
fullPath := joinPaths(g.getFullPrefix(), prefix)
|
||||||
|
g.register(http.MethodGet, prefix+"/*filepath", func(ctx *Context) error {
|
||||||
|
http.StripPrefix(fullPath, fileServer).ServeHTTP(ctx.response, ctx.request)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Use(middlewares ...Middleware) {
|
||||||
|
g.middlewares = append(g.middlewares, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) GET(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodGet, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) POST(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodPost, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) PUT(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodPut, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) DELETE(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodDelete, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) PATCH(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodPatch, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) HEAD(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodHead, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) OPTIONS(path string, h Handler, middlewares ...Middleware) {
|
||||||
|
g.register(http.MethodOptions, path, h, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) getFullPrefix() string {
|
||||||
|
if g.parent != nil {
|
||||||
|
return joinPaths(g.parent.getFullPrefix(), g.prefix)
|
||||||
|
}
|
||||||
|
return g.prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) getAllMiddlewares() []Middleware {
|
||||||
|
middlewares := make([]Middleware, 0)
|
||||||
|
current := g
|
||||||
|
|
||||||
|
for current != nil {
|
||||||
|
middlewares = append(current.middlewares, middlewares...)
|
||||||
|
current = current.parent
|
||||||
|
}
|
||||||
|
return middlewares
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) register(method string, path string, h Handler, middlewares ...Middleware) {
|
||||||
|
fullPath := joinPaths(g.getFullPrefix(), path)
|
||||||
|
allMiddlewares := append(g.getAllMiddlewares(), middlewares...)
|
||||||
|
g.drip.register(method, fullPath, h, allMiddlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinPaths(a, b string) string {
|
||||||
|
a = strings.TrimSuffix(a, "/")
|
||||||
|
b = strings.TrimPrefix(b, "/")
|
||||||
|
|
||||||
|
if b == "" {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
return a + "/" + b
|
||||||
|
}
|
3
handler.go
Normal file
3
handler.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
type Handler func(ctx *Context) error
|
3
middleware.go
Normal file
3
middleware.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
type Middleware func(next Handler) Handler
|
81
websocket_manager.go
Normal file
81
websocket_manager.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package drip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebsocketManager struct {
|
||||||
|
conns map[string]map[*websocket.Conn]bool
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *WebsocketManager) createHandler() Handler {
|
||||||
|
return func(ctx *Context) error {
|
||||||
|
upgrader := websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
|
}
|
||||||
|
conn, err := upgrader.Upgrade(ctx.response, ctx.request, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wm.register(ctx.request.URL.Path, conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, _, err := conn.NextReader(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wm.unregister(ctx.request.URL.Path, conn)
|
||||||
|
conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *WebsocketManager) register(path string, conn *websocket.Conn) {
|
||||||
|
wm.mutex.Lock()
|
||||||
|
defer wm.mutex.Unlock()
|
||||||
|
|
||||||
|
if _, exists := wm.conns[path]; !exists {
|
||||||
|
wm.conns[path] = make(map[*websocket.Conn]bool)
|
||||||
|
}
|
||||||
|
wm.conns[path][conn] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *WebsocketManager) unregister(path string, conn *websocket.Conn) {
|
||||||
|
wm.mutex.Lock()
|
||||||
|
defer wm.mutex.Unlock()
|
||||||
|
|
||||||
|
if conns, exists := wm.conns[path]; exists {
|
||||||
|
delete(conns, conn)
|
||||||
|
if len(conns) == 0 {
|
||||||
|
delete(wm.conns, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *WebsocketManager) Broadcast(path string, msg []byte) error {
|
||||||
|
wm.mutex.RLock()
|
||||||
|
conns, exists := wm.conns[path]
|
||||||
|
wm.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("there is no websocket on path '%s'", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for conn := range conns {
|
||||||
|
go func(c *websocket.Conn) {
|
||||||
|
if err := c.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||||
|
wm.unregister(path, conn)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user