### httprouter Basic Usage Example (Go) Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md Demonstrates the fundamental setup and usage of the httprouter package to create a simple HTTP server with two routes: a root path and a dynamic path for greetings. It shows how to define handlers and start the server. ```go // Copyright 2013 Julien Schmidt. All rights reserved. // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. // Package httprouter is a trie based high performance HTTP request router. // // A trivial example is: // // package main // // import ( // "fmt" // "github.com/julienschmidt/httprouter" // "net/http" // "log" // ) // // func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // fmt.Fprint(w, "Welcome!\n") // } // // func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) // } // // func main() { // router := httprouter.New() // router.GET("/", Index) // router.GET("/hello/:name", Hello) // // log.Fatal(http.ListenAndServe(":8080", router)) // } // // The router matches incoming requests by the request method and the path. // If a handle is registered for this path and method, the router delegates the // request to that function. // For the methods GET, POST, PUT, PATCH, DELETE and OPTIONS shortcut functions exist to // register handles, for all other methods router.Handle can be used. // // The registered path, against which the router matches incoming requests, can // contain two types of parameters: // Syntax Type // :name named parameter // *name catch-all parameter // // Named parameters are dynamic path segments. They match anything until the // next '/' or the path end: // Path: /blog/:category/:post // // Requests: // /blog/go/request-routers match: category="go", post="request-routers" // /blog/go/request-routers/ no match, but the router would redirect // /blog/go/ no match // /blog/go/request-routers/comments no match // // Catch-all parameters match anything until the path end, including the // directory index (the '/' before the catch-all). Since they match anything // until the end, catch-all parameters must always be the final path element. // Path: /files/*filepath // // Requests: // /files/ match: filepath="/" // /files/LICENSE match: filepath="/LICENSE" // /files/templates/article.html match: filepath="/templates/article.html" // /files no match, but the router would redirect // // The value of parameters is saved as a slice of the Param struct, consisting // each of a key and a value. The slice is passed to the Handle func as a third // parameter. // There are two ways to retrieve the value of a parameter: // // by the name of the parameter // user := ps.ByName("user") // defined by :user or *user // // // by the index of the parameter. This way you can also get the name (key) // thirdKey := ps[2].Key // the name of the 3rd parameter // thirdValue := ps[2].Value // the value of the 3rd parameter package httprouter import ( "context" "net/http" "strings" "sync" ) // Handle is a function that can be registered to a route to handle HTTP // requests. Like http.HandlerFunc, but has a third parameter for the values of // wildcards (path variables). type Handle func(http.ResponseWriter, *http.Request, Params) // 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 // ByName returns the value of the first Param which key matches the given name. // If no matching Param is found, an empty string is returned. func (ps Params) ByName(name string) string { for _, p := range ps { if p.Key == name { return p.Value } } return "" } type paramsKey struct{} // ParamsKey is the request context key under which URL params are stored. var ParamsKey = paramsKey{} // ParamsFromContext pulls the URL parameters from a request context, // or returns nil if none are present. func ParamsFromContext(ctx context.Context) Params { p, _ := ctx.Value(ParamsKey).(Params) return p } // MatchedRoutePathParam is the Param name under which the path of the matched // route is stored, if Router.SaveMatchedRoutePath is set. var MatchedRoutePathParam = "$matchedRoutePath" // MatchedRoutePath retrieves the path of the matched route. // Router.SaveMatchedRoutePath must have been enabled when the respective // handler was added, otherwise this function always returns an empty string. func (ps Params) MatchedRoutePath() string { return ps.ByName(MatchedRoutePathParam) } // Router is a http.Handler which can be used to dispatch requests to different ``` -------------------------------- ### Complete Gemini Application Example - Go Source: https://context7.com/kulak/geminirouter/llms.txt Provides a comprehensive example of building a Gemini application using the geminirouter. It demonstrates registering routes for different paths, including those with parameters and catch-all segments. It also shows how to set up custom 404 handlers and panic recovery mechanisms. ```go package main import ( "fmt" "log" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func main() { router := geminirouter.New() router.SaveMatchedRoutePath = true // Home page router.GEMINI("/", func(w gemini.ResponseWriter, r *gemini.Request, _ geminirouter.Params) { w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprint(w, "# My Gemini Capsule\n\n") fmt.Fprint(w, "=> /about About\n") fmt.Fprint(w, "=> /blog Blog\n") fmt.Fprint(w, "=> /contact Contact\n") }) // About page router.GEMINI("/about", func(w gemini.ResponseWriter, r *gemini.Request, _ geminirouter.Params) { w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprint(w, "# About\n\nThis is my Gemini capsule.\n") }) // Blog with category and post parameters router.GEMINI("/blog/:category/:post", func(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { category := ps.ByName("category") post := ps.ByName("post") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# %s - %s\n\nBlog post content here...\n", category, post) }) // File browser with catch-all router.GEMINI("/files/*filepath", func(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { filepath := ps.ByName("filepath") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# File Browser\n\nPath: %s\n", filepath) }) // Titan upload endpoint router.TITAN("/upload/*filepath", func(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { filepath := ps.ByName("filepath") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Upload Complete\n\nFile: %s\n", filepath) }) // Custom 404 handler router.NotFound = gemini.HandlerFunc(func(w gemini.ResponseWriter, r *gemini.Request) { w.WriteHeader(gemini.StatusNotFound, "text/gemini") fmt.Fprintf(w, "# 404 Not Found\n\n%s was not found.\n", r.URL.Path) }) // Panic recovery router.PanicHandler = func(w gemini.ResponseWriter, r *gemini.Request, err interface{}) { log.Printf("Panic: %v", err) w.WriteHeader(gemini.StatusCGIError, "text/gemini") fmt.Fprint(w, "# Server Error\n\nAn internal error occurred.\n") } log.Println("Starting Gemini server on :1965") log.Fatal(gemini.ListenAndServe(":1965", router)) } ``` -------------------------------- ### Create and Configure GeminiRouter Source: https://context7.com/kulak/geminirouter/llms.txt Demonstrates how to create a new GeminiRouter instance, configure its behavior (like redirecting slashes and path matching), and set custom handlers for 404 Not Found and panic recovery. It integrates with `gemini.ListenAndServe` to start the server. ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" "log" ) func main() { // Create new router with default settings // RedirectTrailingSlash and RedirectFixedPath are enabled by default router := geminirouter.New() // Configure router options router.RedirectTrailingSlash = true // Auto-redirect /foo/ to /foo if handler exists router.RedirectFixedPath = true // Case-insensitive path matching with auto-redirect router.SaveMatchedRoutePath = true // Store matched route path in params // Custom 404 handler router.NotFound = gemini.HandlerFunc(func(w gemini.ResponseWriter, r *gemini.Request) { w.WriteHeader(gemini.StatusNotFound, "text/gemini") fmt.Fprintf(w, "# Not Found\n\nThe requested resource was not found.") }) // Panic recovery handler router.PanicHandler = func(w gemini.ResponseWriter, r *gemini.Request, err interface{}) { w.WriteHeader(gemini.StatusCGIError, "text/gemini") fmt.Fprintf(w, "# Server Error\n\nAn internal error occurred: %v", err) } log.Fatal(gemini.ListenAndServe(":1965", router)) } ``` -------------------------------- ### HTTP Method Shortcuts for Routing Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md Provides convenient methods for registering handlers for common HTTP methods. These are shortcuts for the more general `router.Handle` function, making route registration more readable. They map directly to specific HTTP methods like GET, POST, PUT, etc. ```go // GET is a shortcut for router.Handle(http.MethodGet, path, handle) func (r *Router) GET(path string, handle Handle) { r.Handle(http.MethodGet, path, handle) } // HEAD is a shortcut for router.Handle(http.MethodHead, path, handle) func (r *Router) HEAD(path string, handle Handle) { r.Handle(http.MethodHead, path, handle) } // OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle) func (r *Router) OPTIONS(path string, handle Handle) { r.Handle(http.MethodOptions, path, handle) } // POST is a shortcut for router.Handle(http.MethodPost, path, handle) func (r *Router) POST(path string, handle Handle) { r.Handle(http.MethodPost, path, handle) } // PUT is a shortcut for router.Handle(http.MethodPut, path, handle) func (r *Router) PUT(path string, handle Handle) { r.Handle(http.MethodPut, path, handle) } // PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle) func (r *Router) PATCH(path string, handle Handle) { // ... (implementation for PATCH) ... } ``` -------------------------------- ### Register Titan Protocol Handlers with Catch-all Source: https://context7.com/kulak/geminirouter/llms.txt Illustrates how to register handlers for the Titan protocol, specifically for file uploads. It uses a catch-all parameter (`*filepath`) to capture the entire path following the `/upload/` prefix. The example shows reading uploaded data from the request body and processing it. ```go package main import ( "fmt" "io" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func UploadFile(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { filepath := ps.ByName("filepath") // Read upload data from request body data, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(gemini.StatusBadRequest, "text/gemini") fmt.Fprintf(w, "# Upload Failed\n\nError reading data: %v\n", err) return } // Process the upload (save to disk, database, etc.) w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Upload Successful\n\nUploaded: %s\nSize: %d bytes\n", filepath, len(data)) } func main() { router := geminirouter.New() // Register Titan upload handler with catch-all parameter router.TITAN("/upload/*filepath", UploadFile) // Catch-all routes match everything after the prefix: // /upload/ -> filepath="/" // /upload/documents/file.txt -> filepath="/documents/file.txt" } ``` -------------------------------- ### HTTP Method Shortcuts - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md Provides shortcut methods for common HTTP request methods like DELETE. These methods simplify route registration by directly calling the more generic Handle method with the appropriate HTTP method constant. ```Go // DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle) func (r *Router) DELETE(path string, handle Handle) { r.Handle(http.MethodDelete, path, handle) } ``` -------------------------------- ### Initialize New Router Instance Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md Creates and returns a new initialized `Router`. Path auto-correction, including trailing slashes, is enabled by default. This function sets up default behaviors for trailing slash redirection, fixed path redirection, method not allowed handling, and OPTIONS request handling. ```go // New returns a new initialized Router. // Path auto-correction, including trailing slashes, is enabled by default. func New() *Router { return &Router{ RedirectTrailingSlash: true, RedirectFixedPath: true, HandleMethodNotAllowed: true, HandleOPTIONS: true, } } ``` -------------------------------- ### Serving Static Files - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The ServeFiles method allows serving static files from a specified file system root. It requires the path to end with `/*filepath` and uses `http.FileServer` internally. Note that `http.NotFound` is used instead of the router's custom NotFound handler. ```Go // ServeFiles serves files from the given file system root. // The path must end with "/*filepath", files are then served from the local // path /defined/root/dir/*filepath. // For example if root is "/etc" and *filepath is "passwd", the local file // "/etc/passwd" would be served. // Internally a http.FileServer is used, therefore http.NotFound is used instead // of the Router's NotFound handler. // To use the operating system's file system implementation, // use http.Dir: // router.ServeFiles("/src/*filepath", http.Dir("/var/www")) func (r *Router) ServeFiles(path string, root http.FileSystem) { if len(path) < 10 || path[len(path)-10:] != "/*filepath" { panic("path must end with /*filepath in path '" + path + "'") } fileServer := http.FileServer(root) r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) { req.URL.Path = ps.ByName("filepath") fileServer.ServeHTTP(w, req) }) } ``` -------------------------------- ### ServeHTTP Implementation for Request Routing Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The ServeHTTP method handles incoming HTTP requests, routing them to the appropriate handler based on the request method and path. It includes logic for finding handlers, managing path parameters, and implementing redirection for missing trailing slashes or case-insensitive paths. It also handles OPTIONS and Method Not Allowed scenarios before falling back to a 404 Not Found response. ```go func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { if r.PanicHandler != nil { defer r.recv(w, req) } path := req.URL.Path if root := r.trees[req.Method]; root != nil { if handle, ps, tsr := root.getValue(path, r.getParams); handle != nil { if ps != nil { handle(w, req, *ps) r.putParams(ps) } else { handle(w, req, nil) } return } else if req.Method != http.MethodConnect && path != "/" { // Moved Permanently, request with GET method code := http.StatusMovedPermanently if req.Method != http.MethodGet { // Permanent Redirect, request with same method code = http.StatusPermanentRedirect } if tsr && r.RedirectTrailingSlash { if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" } http.Redirect(w, req, req.URL.String(), code) return } // Try to fix the request path if r.RedirectFixedPath { fixedPath, found := root.findCaseInsensitivePath( CleanPath(path), r.RedirectTrailingSlash, ) if found { req.URL.Path = fixedPath http.Redirect(w, req, req.URL.String(), code) return } } } } if req.Method == http.MethodOptions && r.HandleOPTIONS { // Handle OPTIONS requests if allow := r.allowed(path, http.MethodOptions); allow != "" { w.Header().Set("Allow", allow) if r.GlobalOPTIONS != nil { r.GlobalOPTIONS.ServeHTTP(w, req) } return } } else if r.HandleMethodNotAllowed { // Handle 405 if allow := r.allowed(path, req.Method); allow != "" { w.Header().Set("Allow", allow) if r.MethodNotAllowed != nil { r.MethodNotAllowed.ServeHTTP(w, req) } else { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed, ) } return } } // Handle 404 if r.NotFound != nil { r.NotFound.ServeHTTP(w, req) } else { http.NotFound(w, req) } } ``` -------------------------------- ### Optimized String Sorting for Allowed Methods Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md This Go code snippet implements an optimized in-place sorting algorithm for a slice of strings representing allowed HTTP methods. It avoids potential performance bottlenecks associated with heap allocations and interface conversions that might occur with standard library sort functions on slices used in this context. ```go // sort.Strings(allowed) unfortunately causes unnecessary allocations // due to allowed being moved to the heap and interface conversion for i, l := 1, len(allowed); i < l; i++ { for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- { allowed[j], allowed[j-1] = allowed[j-1], allowed[j] } } // return as comma separated list return strings.Join(allowed, ", ") } return allow ``` -------------------------------- ### GeminiRouter: Router Creation and Configuration Source: https://context7.com/kulak/geminirouter/llms.txt Demonstrates how to create a new GeminiRouter instance and configure its behavior, including custom not found and panic handlers. ```APIDOC ## Router Creation and Configuration ### Description This section shows how to initialize a new `geminirouter.Router` and configure its global settings such as enabling/disabling trailing slash redirects, fixed path redirects, and setting custom handlers for 404 Not Found and panic recovery. ### Method `geminirouter.New()` ### Parameters None for `geminirouter.New()` Router configuration options: - `RedirectTrailingSlash` (bool): If true, the router automatically redirects requests ending in a slash to the non-slash equivalent if a handler exists. - `RedirectFixedPath` (bool): If true, the router performs case-insensitive path matching and redirects to the corrected path. - `SaveMatchedRoutePath` (bool): If true, the matched route path is stored in the request's parameters. - `NotFound` (gemini.HandlerFunc): A custom handler to be executed when no matching route is found. - `PanicHandler` (func(gemini.ResponseWriter, *gemini.Request, interface{})): A custom handler to recover from panics during request processing. ### Request Example ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" "log" ) func main() { // Create new router with default settings router := geminirouter.New() // Configure router options router.RedirectTrailingSlash = true router.RedirectFixedPath = true router.SaveMatchedRoutePath = true // Custom 404 handler router.NotFound = gemini.HandlerFunc(func(w gemini.ResponseWriter, r *gemini.Request) { w.WriteHeader(gemini.StatusNotFound, "text/gemini") fmt.Fprintf(w, "# Not Found\n\nThe requested resource was not found.") }) // Panic recovery handler router.PanicHandler = func(w gemini.ResponseWriter, r *gemini.Request, err interface{}) { w.WriteHeader(gemini.StatusCGIError, "text/gemini") fmt.Fprintf(w, "# Server Error\n\nAn internal error occurred: %v", err) } log.Fatal(gemini.ListenAndServe(":1965", router)) } ``` ### Response N/A (This function primarily sets up the router and starts the server.) ``` -------------------------------- ### GeminiRouter: Registering Titan Protocol Handlers Source: https://context7.com/kulak/geminirouter/llms.txt Demonstrates how to register handlers for Titan protocol uploads, including using catch-all parameters to capture file paths. ```APIDOC ## Registering Titan Protocol Handlers ### Description This section details how to register handlers for Titan protocol requests, specifically for file uploads, using the `router.TITAN()` method. It highlights the use of catch-all parameters (`*filepath`) to capture the full path of the uploaded file, allowing for flexible storage and processing. ### Method `router.TITAN(path string, handler gemini.HandlerFunc)` ### Endpoint `/upload/*filepath` (Path with a catch-all parameter for file path) ### Parameters - `path` (string): The URL path pattern to match for Titan uploads. The catch-all parameter `*filepath` captures the rest of the path. - `handler` (gemini.HandlerFunc): The function to execute when the path matches. ### Request Body For Titan uploads, the request body contains the data to be uploaded. The handler should read this data using `io.ReadAll(r.Body)`. ### Request Example ```go package main import ( "fmt" "io" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func UploadFile(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { filepath := ps.ByName("filepath") // Read upload data from request body data, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(gemini.StatusBadRequest, "text/gemini") fmt.Fprintf(w, "# Upload Failed\n\nError reading data: %v\n", err) return } // Process the upload (save to disk, database, etc.) w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Upload Successful\n\nUploaded: %s\nSize: %d bytes\n", filepath, len(data)) } func main() { router := geminirouter.New() // Register Titan upload handler with catch-all parameter router.TITAN("/upload/*filepath", UploadFile) } ``` ### Response #### Success Response (200 OK) - **Status**: `gemini.StatusSuccess` - **Content-Type**: `text/gemini` - **Body**: A Gemini response indicating successful upload, including the captured filepath and size of the uploaded data. #### Error Response (400 Bad Request) - **Status**: `gemini.StatusBadRequest` - **Content-Type**: `text/gemini` - **Body**: A Gemini response indicating an error during data reading. ### Catch-all Parameter Usage - For a request to `/upload/documents/file.txt`, `ps.ByName("filepath")` will return `"/documents/file.txt"`. - For a request to `/upload/`, `ps.ByName("filepath")` will return `/`. ``` -------------------------------- ### Manual Route Lookup - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The Lookup method allows for manual lookup of a method and path combination. It returns the registered Handle function, the path parameters, and a boolean indicating if a redirection for trailing slash is possible. This is useful for building frameworks on top of the router. ```Go // Lookup allows the manual lookup of a method + path combo. // This is e.g. useful to build a framework around this router. // If the path was found, it returns the handle function and the path parameter // values. Otherwise the third return value indicates whether a redirection to // the same path with an extra / without the trailing slash should be performed. func (r *Router) Lookup(method, path string) (Handle, Params, bool) { if root := r.trees[method]; root != nil { handle, ps, tsr := root.getValue(path, r.getParams) if handle == nil { r.putParams(ps) return nil, nil, tsr } if ps == nil { return handle, nil, tsr } return handle, *ps, tsr } return nil, nil, false } ``` -------------------------------- ### Register Gemini Protocol Handlers Source: https://context7.com/kulak/geminirouter/llms.txt Shows how to register different Gemini protocol handlers for various routes. It includes handlers for the root path, routes with named parameters like `/user/:username`, and routes with multiple named parameters like `/blog/:category/:post`. The `geminirouter.Params` type is used to access route parameters. ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func Index(w gemini.ResponseWriter, r *gemini.Request, _ geminirouter.Params) { w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprint(w, "# Welcome\n\nWelcome to my Gemini capsule!\n") } func UserProfile(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { username := ps.ByName("username") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# User Profile\n\nUsername: %s\n", username) } func BlogPost(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { category := ps.ByName("category") post := ps.ByName("post") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Blog Post\n\nCategory: %s\nPost: %s\n", category, post) } func main() { router := geminirouter.New() // Register Gemini protocol handlers router.GEMINI("/", Index) router.GEMINI("/user/:username", UserProfile) router.GEMINI("/blog/:category/:post", BlogPost) // Routes with named parameters: // /user/alice -> username="alice" // /blog/tech/golang -> category="tech", post="golang" } ``` -------------------------------- ### Integrate Standard Gemini Handlers with geminirouter Source: https://context7.com/kulak/geminirouter/llms.txt Illustrates how to integrate standard Gemini handlers (implementing ServeGemini) and HandlerFuncs with the geminirouter. Parameters for handlers are accessible via the request context using geminirouter.ParamsFromContext(). This allows using existing Gemini handlers with the router. ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) // Standard Gemini handler (no params) type LoggingHandler struct{} func (h *LoggingHandler) ServeGemini(w gemini.ResponseWriter, r *gemini.Request) { fmt.Printf("Request: %s %s\n", r.URL.Scheme, r.URL.Path) w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprint(w, "# Logged Request\n\nYour request has been logged.\n") } func main() { router := geminirouter.New() // Wrap standard Gemini handler // Params are available via context: geminirouter.ParamsFromContext(r.Context()) router.Handler(gemini.SchemaGemini, "/log/:id", &LoggingHandler{}) // Or use HandlerFunc for function handlers router.HandlerFunc(gemini.SchemaGemini, "/simple", func(w gemini.ResponseWriter, r *gemini.Request) { // Extract params from context ps := geminirouter.ParamsFromContext(r.Context()) w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Simple Handler\n\nParams: %v\n", ps) }) } ``` -------------------------------- ### Adapting http.HandlerFunc to Router Handle - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The HandlerFunc method is a convenience adapter that allows an http.HandlerFunc to be used directly as a request handle. It internally calls the Handler method. ```Go // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a // request handle. func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { r.Handler(method, path, handler) } ``` -------------------------------- ### Manage Router Parameters Pool Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md Handles the retrieval and return of parameters used by the router. It utilizes a `sync.Pool` to efficiently manage `Params` slices, reducing memory allocation overhead. The `getParams` function retrieves a `Params` slice and resets it, while `putParams` returns it to the pool. ```go func (r *Router) getParams() *Params { ps, _ := r.paramsPool.Get().(*Params) *ps = (*ps)[0:0] // reset slice return ps } func (r *Router) putParams(ps *Params) { if ps != nil { r.paramsPool.Put(ps) } } ``` -------------------------------- ### Registering HTTP Request Handles - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The Handle method registers a new request handler for a given HTTP method and path. It includes validation for method, path, and handler. Optional features like saving matched route paths and lazy allocation of parameter pools are also managed here. This method is intended for bulk loading and custom HTTP methods. ```Go func (r *Router) Handle(method, path string, handle Handle) { varsCount := uint16(0) if method == "" { panic("method must not be empty") } if len(path) < 1 || path[0] != '/' { panic("path must begin with '/' in path '" + path + "'") } if handle == nil { panic("handle must not be nil") } if r.SaveMatchedRoutePath { varsCount++ handle = r.saveMatchedRoutePath(path, handle) } if r.trees == nil { r.trees = make(map[string]*node) } root := r.trees[method] if root == nil { root = new(node) r.trees[method] = root r.globalAllowed = r.allowed("*", "") } root.addRoute(path, handle) // Update maxParams if paramsCount := countParams(path); paramsCount+varsCount > r.maxParams { r.maxParams = paramsCount + varsCount } // Lazy-init paramsPool alloc func if r.paramsPool.New == nil && r.maxParams > 0 { r.paramsPool.New = func() interface{} { ps := make(Params, 0, r.maxParams) return &ps } } } ``` -------------------------------- ### GeminiRouter: Registering Gemini Protocol Handlers Source: https://context7.com/kulak/geminirouter/llms.txt Illustrates how to register handlers for Gemini protocol requests using different route patterns, including those with named parameters. ```APIDOC ## Registering Gemini Protocol Handlers ### Description This section explains how to register specific `gemini.HandlerFunc` instances with the router for different URL paths using the `router.GEMINI()` method. It covers basic routes, routes with named parameters, and provides examples of how to access these parameters within the handler. ### Method `router.GEMINI(path string, handler gemini.HandlerFunc)` ### Endpoint `/` (Root path) `/user/:username` (Path with named parameter) `/blog/:category/:post` (Path with multiple named parameters) ### Parameters - `path` (string): The URL path pattern to match. Named parameters are denoted by a colon (e.g., `:username`). - `handler` (gemini.HandlerFunc): The function to execute when the path matches. ### Request Body N/A for registering handlers. ### Request Example ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func Index(w gemini.ResponseWriter, r *gemini.Request, _ geminirouter.Params) { w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprint(w, "# Welcome\n\nWelcome to my Gemini capsule!\n") } func UserProfile(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { username := ps.ByName("username") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# User Profile\n\nUsername: %s\n", username) } func BlogPost(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { category := ps.ByName("category") post := ps.ByName("post") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Blog Post\n\nCategory: %s\nPost: %s\n", category, post) } func main() { router := geminirouter.New() // Register Gemini protocol handlers router.GEMINI("/", Index) router.GEMINI("/user/:username", UserProfile) router.GEMINI("/blog/:category/:post", BlogPost) } ``` ### Response N/A (This function registers handlers.) ### Handler Parameter Access Within a handler, parameters can be accessed using `ps.ByName("paramName")`: - For `/user/alice`, `ps.ByName("username")` returns `"alice"`. - For `/blog/tech/golang`, `ps.ByName("category")` returns `"tech"` and `ps.ByName("post")` returns `"golang"`. ``` -------------------------------- ### Allowed Methods Calculation - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The allowed method calculates and returns a string representing the allowed HTTP methods for a given path. It iterates through registered methods and checks for path existence, optionally including OPTIONS. This is used internally, particularly for server-wide or specific path options. ```Go func (r *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 { if method == http.MethodOptions { continue } // Add request method to list of allowed methods allowed = append(allowed, method) } } else { return r.globalAllowed } } else { // specific path for method := range r.trees { // Skip the requested method - we already tried this one if method == reqMethod || method == http.MethodOptions { continue } handle, _, _ := r.trees[method].getValue(path, nil) if handle != nil { // Add request method to list of allowed methods allowed = append(allowed, method) } } } if len(allowed) > 0 { // Add request method to list of allowed methods allowed = append(allowed, http.MethodOptions) // Sort allowed methods. ``` -------------------------------- ### Path Cleaning Utility - Go Source: https://context7.com/kulak/geminirouter/llms.txt Demonstrates the CleanPath function from the geminirouter package, which canonicalizes URL paths by eliminating '.' and '..' elements and removing duplicate slashes. This utility is crucial for normalizing user input and preventing security vulnerabilities before routing. ```go package main import ( "fmt" "github.com/kulak/geminirouter" ) func main() { // CleanPath canonicalizes URL paths // Eliminates . and .. elements, removes duplicate slashes examples := map[string]string{ "": "/", "abc/def": "/abc/def", "/abc//def": "/abc/def", "/abc/./def": "/abc/def", "/abc/def/../ghi": "/abc/ghi", "/abc/def/../../": "/", "//multiple///slashes//": "/multiple/slashes/", "/path/./to/../resource": "/path/resource", } for input, expected := range examples { cleaned := geminirouter.CleanPath(input) fmt.Printf("Input: %-30s -> Output: %-20s (expected: %s)\n", input, cleaned, expected) if cleaned != expected { fmt.Printf(" ERROR: Got %s, expected %s\n", cleaned, expected) } } // CleanPath is useful for normalizing user input before routing userInput := "../../../etc/passwd" safe := geminirouter.CleanPath(userInput) fmt.Printf("\nUser input: %s\nCleaned: %s\n", userInput, safe) // Output: /etc/passwd } ``` -------------------------------- ### Adapting http.Handler to Router Handle - Go Router Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md The Handler method adapts an http.Handler interface to be used as a request handle within the router. It ensures that parameters from the route are available in the request context if any parameters exist. ```Go // Handler is an adapter which allows the usage of an http.Handler as a // request handle. // The Params are available in the request context under ParamsKey. func (r *Router) Handler(method, path string, handler http.Handler) { r.Handle(method, path, func(w http.ResponseWriter, req *http.Request, p Params) { if len(p) > 0 { ctx := req.Context() ctx = context.WithValue(ctx, ParamsKey, p) req = req.WithContext(ctx) } handler.ServeHTTP(w, req) }, ) } ``` -------------------------------- ### Extract URL Parameters with geminirouter Source: https://context7.com/kulak/geminirouter/llms.txt Shows how to extract URL parameters from a request using the geminirouter library. Parameters can be accessed by name using ps.ByName() or by index from the ps slice. It also demonstrates how to retrieve the matched route path if SaveMatchedRoutePath is enabled. ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func FileServer(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { // Extract parameters by name filepath := ps.ByName("filepath") // Access parameters by index if len(ps) > 0 { firstParamKey := ps[0].Key firstParamValue := ps[0].Value fmt.Printf("First param: %s = %s\n", firstParamKey, firstParamValue) } // Get matched route path if SaveMatchedRoutePath is enabled routePath := ps.MatchedRoutePath() w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# File Browser\n\nPath: %s\nRoute: %s\n", filepath, routePath) } func main() { router := geminirouter.New() router.SaveMatchedRoutePath = true router.GEMINI("/files/*filepath", FileServer) // Parameter examples: // /files/docs/readme.gmi -> filepath="/docs/readme.gmi" // ps.MatchedRoutePath() -> "/files/*filepath" } ``` -------------------------------- ### Register Custom Protocol Handlers with geminirouter Source: https://context7.com/kulak/geminirouter/llms.txt Demonstrates how to use the Handle method to register custom handlers for different protocols like Gemini and Titan. This allows for protocol-specific routing on the same path. The handler receives protocol-specific writer and request objects along with routing parameters. ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func CustomHandler(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { resource := ps.ByName("resource") w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprintf(w, "# Custom Protocol\n\nResource: %s\n", resource) } func main() { router := geminirouter.New() // Register handlers for both Gemini and Titan protocols router.Handle(gemini.SchemaGemini, "/resource/:resource", CustomHandler) router.Handle(gemini.SchemaTitan, "/resource/:resource", CustomHandler) // The Handle method allows protocol-specific routing // You can register the same path for different protocols } ``` -------------------------------- ### Save Matched Route Path in Request Context Source: https://github.com/kulak/geminirouter/blob/main/original/router.go.md A helper function that wraps a handler to optionally save the matched route path into the request context. This is useful for debugging or if downstream handlers need to know the specific route that was matched. It handles cases where parameters might be appended to the request. ```go func (r *Router) saveMatchedRoutePath(path string, handle Handle) Handle { return func(w http.ResponseWriter, req *http.Request, ps Params) { if ps == nil { psp := r.getParams() ps = (*psp)[0:1] ps[0] = Param{Key: MatchedRoutePathParam, Value: path} handle(w, req, ps) r.putParams(psp) } else { ps = append(ps, Param{Key: MatchedRoutePathParam, Value: path}) handle(w, req, ps) } } } ``` -------------------------------- ### Perform Manual Route Lookup with geminirouter Source: https://context7.com/kulak/geminirouter/llms.txt Demonstrates how to manually look up a route in the geminirouter. The Lookup method returns the handler function, extracted parameters, and a boolean indicating if a trailing slash redirect is recommended. This is useful for custom routing logic or debugging. ```go package main import ( "fmt" "github.com/kulak/gemini" "github.com/kulak/geminirouter" ) func main() { router := geminirouter.New() handler := func(w gemini.ResponseWriter, r *gemini.Request, ps geminirouter.Params) { w.WriteHeader(gemini.StatusSuccess, "text/gemini") fmt.Fprint(w, "# Handler\n") } router.GEMINI("/user/:id/posts/:post", handler) // Manually lookup a route handle, params, tsr := router.Lookup(gemini.SchemaGemini, "/user/123/posts/456") if handle != nil { fmt.Printf("Found handler\n") fmt.Printf("Params: %v\n", params) // params[0] = {Key: "id", Value: "123"} // params[1] = {Key: "post", Value: "456"} } else if tsr { fmt.Println("Trailing slash redirect recommended") } else { fmt.Println("No handler found") } // Lookup non-existent route handle, params, tsr = router.Lookup(gemini.SchemaGemini, "/nonexistent") if handle == nil && !tsr { fmt.Println("Route not found, no redirect available") } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.