From 5a9ae561ee0dbd9b82285594b6fb8b9fe3eea0bd Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Mon, 17 Mar 2025 16:45:22 -0500 Subject: [PATCH] gin for protobufs Day1 --- Makefile | 18 +++++ gin.go | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ stack.go | 91 ++++++++++++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 Makefile create mode 100644 gin.go create mode 100644 stack.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d80c78 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +# You must use the current google protoc-gen-go +# +# cd ~/go/src/google.golang.org/protobuf/cmd/protoc-gen-go +# go install + +all: goimports vet + +vet: + @GO111MODULE=off go vet + @echo this go library package builds okay + +# autofixes your import headers in your golang files +goimports: + goimports -w *.go + +clean: + rm -f *.pb.go + -rm -f go.* diff --git a/gin.go b/gin.go new file mode 100644 index 0000000..3429817 --- /dev/null +++ b/gin.go @@ -0,0 +1,207 @@ +// Copyright 2017-2025 WIT.COM Inc. All rights reserved. +// Use of this source code is governed by the GPL 3.0 + +package pinpb + +// this is similar to 'gin' but specifically only for +// sending and working with protocol buffers +// +// also, it is as close to possible a golang 'primitive' +// package (there is no go.sum file) + +import ( + "net/http" + "net/url" + "sync" + "text/template" +) + +// Param is a single URL parameter, consisting of a key and a value. +type Param struct { + Key string + Value string +} + +// Params is a Param-slice, as returned by the router. +// The slice is ordered, the first URL parameter is also the first slice value. +// It is therefore safe to read values by the index. +type Params []Param + +func (engine *Engine) allocateContext(maxParams uint16) *Context { + v := make(Params, 0, maxParams) + return &Context{engine: engine, params: &v} +} + +func New() *Engine { + engine := &Engine{ + RouterGroup: RouterGroup{ + Handlers: nil, + basePath: "/", + root: true, + }, + FuncMap: template.FuncMap{}, + RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, + trustedProxies: []string{"0.0.0.0/0", "::/0"}, + } + engine.RouterGroup.engine = engine + engine.pool.New = func() any { + return engine.allocateContext(engine.maxParams) + // return nil + } + // return engine.With(opts...) + return engine +} + +// With returns a Engine with the configuration set in the OptionFunc. +func (engine *Engine) With(opts ...OptionFunc) *Engine { + for _, opt := range opts { + opt(engine) + } + + return engine +} + +func Default(opts ...OptionFunc) *Engine { + engine := New() + // engine.Use(Logger(), Recovery()) + return engine.With(opts...) +} + +// Context is the most important part of gin. It allows us to pass variables between middleware, +// manage the flow, validate the JSON of a request and render a JSON response for example. +type Context struct { + engine *Engine + params *Params + Request *http.Request + handlers HandlersChain + + // queryCache caches the query result from c.Request.URL.Query(). + queryCache url.Values + + // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH, + // or PUT body parameters. + formCache url.Values + + // SameSite allows a server to define a cookie attribute making it impossible for + // the browser to send this cookie along with cross-site requests. + sameSite http.SameSite +} + +// HandlerFunc defines the handler used by gin middleware as return value. +type HandlerFunc func(*Context) + +// OptionFunc defines the function to change the default configuration +type OptionFunc func(*Engine) + +// HandlersChain defines a HandlerFunc slice. +type HandlersChain []HandlerFunc + +// Last returns the last handler in the chain. i.e. the last handler is the main one. +func (c HandlersChain) Last() HandlerFunc { + if length := len(c); length > 0 { + return c[length-1] + } + return nil +} + +// RouterGroup is used internally to configure router, a RouterGroup is associated with +// a prefix and an array of handlers (middleware). +type RouterGroup struct { + Handlers HandlersChain + basePath string + engine *Engine + root bool +} + +// Engine is the framework's instance, it contains the muxer, middleware and configuration settings. +// Create an instance of Engine, by using New() or Default() +type Engine struct { + RouterGroup + + // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a + // handler for the path with (without) the trailing slash exists. + // For example if /foo/ is requested but a route only exists for /foo, the + // client is redirected to /foo with http status code 301 for GET requests + // and 307 for all other request methods. + RedirectTrailingSlash bool + + // RedirectFixedPath if enabled, the router tries to fix the current request path, if no + // handle is registered for it. + // First superfluous path elements like ../ or // are removed. + // Afterwards the router does a case-insensitive lookup of the cleaned path. + // If a handle can be found for this route, the router makes a redirection + // to the corrected path with status code 301 for GET requests and 307 for + // all other request methods. + // For example /FOO and /..//Foo could be redirected to /foo. + // RedirectTrailingSlash is independent of this option. + RedirectFixedPath bool + + // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the + // current route, if the current request can not be routed. + // If this is the case, the request is answered with 'Method Not Allowed' + // and HTTP status code 405. + // If no other Method is allowed, the request is delegated to the NotFound + // handler. + HandleMethodNotAllowed bool + + // ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that + // match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was + // fetched, it falls back to the IP obtained from + // `(*gin.Context).Request.RemoteAddr`. + ForwardedByClientIP bool + + // AppEngine was deprecated. + // Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD + // #726 #755 If enabled, it will trust some headers starting with + // 'X-AppEngine...' for better integration with that PaaS. + AppEngine bool + + // UseRawPath if enabled, the url.RawPath will be used to find parameters. + UseRawPath bool + + // UnescapePathValues if true, the path value will be unescaped. + // If UseRawPath is false (by default), the UnescapePathValues effectively is true, + // as url.Path gonna be used, which is already unescaped. + UnescapePathValues bool + + // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. + // See the PR #1817 and issue #1644 + RemoveExtraSlash bool + + // RemoteIPHeaders list of headers used to obtain the client IP when + // `(*gin.Engine).ForwardedByClientIP` is `true` and + // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the + // network origins of list defined by `(*gin.Engine).SetTrustedProxies()`. + RemoteIPHeaders []string + + // TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by + // that platform, for example to determine the client IP + TrustedPlatform string + + // MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm + // method call. + MaxMultipartMemory int64 + + // UseH2C enable h2c support. + UseH2C bool + + // ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil. + ContextWithFallback bool + + FuncMap template.FuncMap + trustedProxies []string + pool sync.Pool + maxParams uint16 + /* + delims render.Delims + secureJSONPrefix string + HTMLRender render.HTMLRender + allNoRoute HandlersChain + allNoMethod HandlersChain + noRoute HandlersChain + noMethod HandlersChain + trees methodTrees + maxSections uint16 + trustedCIDRs []*net.IPNet + */ +} diff --git a/stack.go b/stack.go new file mode 100644 index 0000000..d4fed60 --- /dev/null +++ b/stack.go @@ -0,0 +1,91 @@ +// Copyright 2017-2025 WIT.COM Inc. All rights reserved. +// Use of this source code is governed by the GPL 3.0 + +package pinpb + +// this is similar to 'gin' but specifically only for +// sending and working with protocol buffers +// +// also, it is as close to possible a golang 'primitive' +// package (there is no go.sum file) + +import ( + "bytes" + "fmt" + "os" + "runtime" + "time" +) + +var ( + dunno = []byte("???") + centerDot = []byte("·") + dot = []byte(".") + slash = []byte("/") +) + +// stack returns a nicely formatted stack frame, skipping skip frames. +func Stack(skip int) []byte { + buf := new(bytes.Buffer) // the returned data + // As we loop, we open files and read them. These variables record the currently + // loaded file. + var lines [][]byte + var lastFile string + for i := skip; ; i++ { // Skip the expected number of frames + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + // Print this much at least. If we can't find the source, it won't show. + fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) + if file != lastFile { + data, err := os.ReadFile(file) + if err != nil { + continue + } + lines = bytes.Split(data, []byte{'\n'}) + lastFile = file + } + fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) + } + return buf.Bytes() +} + +// source returns a space-trimmed slice of the n'th line. +func source(lines [][]byte, n int) []byte { + n-- // in stack trace, lines are 1-indexed but our array is 0-indexed + if n < 0 || n >= len(lines) { + return dunno + } + return bytes.TrimSpace(lines[n]) +} + +// function returns, if possible, the name of the function containing the PC. +func function(pc uintptr) []byte { + fn := runtime.FuncForPC(pc) + if fn == nil { + return dunno + } + name := []byte(fn.Name()) + // The name includes the path name to the package, which is unnecessary + // since the file name is already included. Plus, it has center dots. + // That is, we see + // runtime/debug.*T·ptrmethod + // and want + // *T.ptrmethod + // Also the package path might contain dot (e.g. code.google.com/...), + // so first eliminate the path prefix + if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { + name = name[lastSlash+1:] + } + if period := bytes.Index(name, dot); period >= 0 { + name = name[period+1:] + } + name = bytes.ReplaceAll(name, centerDot, dot) + return name +} + +// timeFormat returns a customized time string for logger. +func timeFormat(t time.Time) string { + return t.Format("2006/01/02 - 15:04:05") +}