parser

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2025 License: MIT Imports: 20 Imported by: 0

README

High-Performance HTTP Template Parser

A Go module that provides a high-performance parser based on text/template for HTTP requests. This module is designed to handle non-rewindable HTTP input streams efficiently while providing template caching and automatic recompilation on file changes.

Performance: Capable of processing 40,000+ requests per second with template caching, optimized for high-throughput web applications and microservices.

Features

  • Re-readable HTTP Requests: Handles non-rewindable HTTP input streams by buffering them in memory
  • Template Caching: High-performance template caching with LRU eviction
  • File Watching: Automatic template recompilation when template files change
  • Flexible Template Loading: Supports file system and memory-based template loaders
  • Rich Request Data: Extracts headers, query parameters, form data, and body content
  • Custom Function Maps: Support for custom template functions
  • Thread-Safe: Concurrent access safe across goroutines
  • Benchmarked Performance: Optimized for high-throughput scenarios

Installation

go get github.com/fabricates/parser

Quick Start

package main

import (
    "bytes"
    "fmt"
    "net/http"
    "strings"
    
    "github.com/fabricates/parser"
)

func main() {
    // Simple configuration (uses MemoryLoader by default)
    config := parser.Config{
        MaxCacheSize: 100,
        FuncMap:      parser.DefaultFuncMap(),
    }
    
    // Create parser
    p, err := parser.NewParser(config)
    if err != nil {
        panic(err)
    }
    defer p.Close()
    
    // Add template dynamically
    err = p.UpdateTemplate("greeting", "Hello {{.Request.Method}} from {{.Request.URL.Path}}!", "v1")
    if err != nil {
        panic(err)
    }
    
    // Create HTTP request
    req, _ := http.NewRequest("GET", "http://example.com/api/users", nil)
    
    // Parse template
    var output bytes.Buffer
    err = p.Parse("greeting", req, &output)
    if err != nil {
        panic(err)
    }
    
    fmt.Print(output.String()) // Output: Hello GET from /api/users!
}
Alternative: Explicit MemoryLoader
func main() {
    // Create a memory-based template loader explicitly
    loader := parser.NewMemoryLoader()
    loader.AddTemplate("greeting", "Hello {{.Request.Method}} from {{.Request.URL.Path}}!")
    
    // Create parser configuration
    config := parser.Config{
        TemplateLoader: loader,
        MaxCacheSize:   100,
        WatchFiles:     false,
        FuncMap:        parser.DefaultFuncMap(),
    }
    
    // Create parser
    p, err := parser.NewParser(config)
    if err != nil {
        panic(err)
    }
    defer p.Close()
    
    // Create HTTP request
    req, _ := http.NewRequest("GET", "http://example.com/api/users", nil)
    
    // Parse template
    var output bytes.Buffer
    err = p.Parse("greeting", req, &output)
    if err != nil {
        panic(err)
    }
    
    fmt.Print(output.String()) // Output: Hello GET from /api/users!
}

Core Components

Parser Interface

The main interface provides methods for template parsing and management:

type Parser interface {
    Parse(templateName string, request *http.Request, output io.Writer) error
    ParseWith(templateName string, request *http.Request, data interface{}, output io.Writer) error
    UpdateTemplate(name string, content string, hash string) error
    GetCacheStats() CacheStats
    Close() error
}
Template Loaders
File System Loader

Load templates from the file system with optional file watching:

loader := parser.NewFileSystemLoader("/path/to/templates", ".tmpl", true)

config := parser.Config{
    TemplateLoader: loader,
    WatchFiles:     true, // Enable automatic reloading
    MaxCacheSize:   50,
}

p, err := parser.NewParser(config)
Memory Loader

For testing or when templates are embedded:

loader := parser.NewMemoryLoader()
loader.AddTemplate("welcome", "Welcome {{.Custom.username}}!")

config := parser.Config{
    TemplateLoader: loader,
    MaxCacheSize:   10,
}

p, err := parser.NewParser(config)
Request Data Structure

Templates have access to structured request data:

type RequestData struct {
    Request *http.Request           // Original HTTP request
    Headers map[string][]string     // HTTP headers
    Query   map[string][]string     // Query parameters
    Form    map[string][]string     // Form data (for POST requests)
    Body    string                  // Request body as string
    Custom  interface{}             // Custom data passed to ParseWith
}
Dynamic Template Updates

You can dynamically add or update templates at runtime using the UpdateTemplate method:

// Add a new template
templateContent := "Hello {{.Request.Method}} from {{.Request.URL.Path}}!"
err := parser.UpdateTemplate("greeting", templateContent, "hash123")
if err != nil {
    log.Fatalf("Failed to update template: %v", err)
}

// Later, update the same template with new content
newContent := "Updated: {{.Request.Method}} {{.Request.URL.Path}}"
err = parser.UpdateTemplate("greeting", newContent, "hash456")
if err != nil {
    log.Fatalf("Failed to update template: %v", err)
}

// Use the updated template
var output bytes.Buffer
err = parser.Parse("greeting", request, &output)

Template Examples

Basic Request Information
Method: {{.Request.Method}}
URL: {{.Request.URL.Path}}
User-Agent: {{index .Headers "User-Agent" 0}}
Query Parameters
{{if .Query.name}}
Hello {{index .Query "name" 0}}!
{{end}}
Form Data
{{if .Form.username}}
Username: {{index .Form "username" 0}}
{{end}}
Request Body
{{if .Body}}
Received: {{.Body}}
{{end}}
Custom Data
customData := map[string]interface{}{
    "user_id": 123,
    "role":    "admin",
}

err := parser.ParseWith("template", request, customData, output)
User ID: {{.Custom.user_id}}
Role: {{.Custom.role}}

Built-in Template Functions

The parser includes useful template functions:

  • upper: Convert string to uppercase
  • lower: Convert string to lowercase
  • title: Convert string to title case
  • trim: Remove leading/trailing whitespace
  • default: Provide default value for empty/nil values
  • header: Get request header value
  • query: Get query parameter value
  • form: Get form field value

Example usage:

Name: {{.Custom.name | upper | default "Anonymous"}}
Content-Type: {{header .Request "Content-Type"}}

File Watching

When WatchFiles is enabled, the parser automatically detects template file changes and recompiles them:

config := parser.Config{
    TemplateLoader: parser.NewFileSystemLoader("./templates", ".tmpl", true),
    WatchFiles:     true,
    MaxCacheSize:   100,
}

p, err := parser.NewParser(config)
// Templates will be automatically reloaded when files change

Performance Optimizations

Template Caching

Templates are cached after compilation with LRU eviction:

config := parser.Config{
    TemplateLoader: loader,
    MaxCacheSize:   100, // Cache up to 100 templates
}

// Get cache statistics
stats := p.GetCacheStats()
fmt.Printf("Cache: %d/%d, Hits: %d\n", stats.Size, stats.MaxSize, stats.HitCount)
Re-readable Requests

HTTP request bodies are automatically buffered to allow multiple reads:

// The parser handles this automatically
rereadableReq, err := parser.NewRereadableRequest(originalRequest)
rereadableReq.Reset() // Reset body for re-reading

Error Handling

The module defines specific error types:

var (
    ErrTemplateNotFound = errors.New("template not found")
    ErrWatcherClosed    = errors.New("file watcher is closed")
    ErrInvalidConfig    = errors.New("invalid configuration")
    ErrParserClosed     = errors.New("parser is closed")
)

Testing

Run the test suite:

go test -v

Run benchmarks:

go test -bench=.

Example benchmark results:

BenchmarkParserParse-2           193795    6008 ns/op
BenchmarkRequestExtraction-2     314965    3690 ns/op

Examples

See the /examples directory for complete usage examples:

  • examples/basic/: Basic usage with different request types
  • More examples coming soon!

Configuration

type Config struct {
    TemplateLoader TemplateLoader    // How to load templates (defaults to MemoryLoader if nil)
    WatchFiles     bool              // Enable file watching (FileSystemLoader only)
    MaxCacheSize   int               // Template cache size (0 = unlimited)
    FuncMap        template.FuncMap  // Custom template functions
}
Default Behavior

If no TemplateLoader is specified in the config, the parser will automatically use a MemoryLoader by default. This allows you to create a parser with minimal configuration:

// Simple config with default MemoryLoader
config := parser.Config{
    MaxCacheSize: 100,
}

p, err := parser.NewParser(config)
if err != nil {
    panic(err)
}

// Add templates dynamically using UpdateTemplate
err = p.UpdateTemplate("greeting", "Hello {{.Request.Method}}!", "hash123")

Performance

The parser is designed for high performance with several optimizations:

Benchmark Results

Performance characteristics on a typical server (Intel Xeon E5-2680 v2 @ 2.80GHz):

Operation Throughput Memory per Op Allocations
Basic Parsing ~48,000 ops/sec 4.9 KB 67 allocs
Request Extraction ~105,000 ops/sec 4.9 KB 41 allocs
Generic String Output ~50,000 ops/sec 4.5 KB 69 allocs
Generic JSON Output ~22,000 ops/sec 6.2 KB 104 allocs
Template Caching ~89,000 ops/sec 3.7 KB 34 allocs
Template Updates ~85,000 ops/sec 3.3 KB 43 allocs
Re-readable Requests ~437,000 ops/sec 1.2 KB 10 allocs
Complex Templates ~8,000 ops/sec 14.3 KB 268 allocs
Cache Performance

Template caching provides significant performance benefits:

Cache Size Performance Memory Efficiency
Size 1 ~46,000 ops/sec Most memory efficient
Size 10 ~50,000 ops/sec Balanced
Size 100 ~42,000 ops/sec Best hit rate
Unlimited ~44,000 ops/sec Highest memory usage

Recommendation: Use cache size 10-50 for most applications.

Body Size Impact

Request body size affects memory usage and performance:

Body Size Throughput Memory per Op
Small (100B) ~45,000 ops/sec 5.2 KB
Medium (10KB) ~15,000 ops/sec 60.9 KB
Large (100KB) ~2,000 ops/sec 625.4 KB
Concurrent Performance

The parser maintains good performance under concurrent load:

  • Concurrent Parsing: ~40,000 ops/sec with multiple goroutines
  • Thread-safe: No performance degradation with concurrent access
  • Lock-free reads: Template cache uses efficient concurrent access patterns
Optimization Tips
  1. Use appropriate cache size: 10-50 templates for most applications
  2. Minimize template complexity: Simpler templates execute faster
  3. Reuse parser instances: Creating parsers has overhead
  4. Pre-load templates: Use UpdateTemplate to warm the cache
  5. Monitor cache hit rates: Use GetCacheStats() to optimize cache size

Thread Safety

All components are designed to be thread-safe and can be used concurrently across multiple goroutines.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTemplateNotFound = errors.New("template not found")
	ErrWatcherClosed    = errors.New("file watcher is closed")
	ErrInvalidConfig    = errors.New("invalid configuration")
	ErrParserClosed     = errors.New("parser is closed")
)

Common errors

Functions

func DefaultFuncMap

func DefaultFuncMap() template.FuncMap

Helper function to create default function map with useful template functions

Types

type CacheStats

type CacheStats struct {
	Size     int   // Current number of cached templates
	MaxSize  int   // Maximum cache size (0 = unlimited)
	HitCount int64 // Total number of cache hits
}

CacheStats holds cache statistics

type CachedTemplate

type CachedTemplate struct {
	Template     *template.Template
	LastModified time.Time
	AccessTime   time.Time
	AccessCount  int64
}

CachedTemplate holds a compiled template with metadata

type Config

type Config struct {
	// TemplateLoader specifies how to load templates
	TemplateLoader TemplateLoader

	// WatchFiles enables automatic template reloading on file changes
	WatchFiles bool

	// MaxCacheSize limits the number of cached templates (0 = unlimited)
	MaxCacheSize int

	// FuncMap provides custom template functions
	FuncMap template.FuncMap
}

Config holds configuration for the parser

type FileSystemLoader

type FileSystemLoader struct {
	// RootDir is the root directory for templates
	RootDir string

	// Extension is the file extension for templates (e.g., ".tmpl", ".tpl")
	Extension string

	// Recursive enables recursive directory scanning
	Recursive bool
	// contains filtered or unexported fields
}

FileSystemLoader loads templates from the file system

func NewFileSystemLoader

func NewFileSystemLoader(rootDir, extension string, recursive bool) *FileSystemLoader

NewFileSystemLoader creates a new file system template loader

func (*FileSystemLoader) LastModified

func (f *FileSystemLoader) LastModified(name string) (time.Time, error)

LastModified implements TemplateLoader

func (*FileSystemLoader) List

func (f *FileSystemLoader) List() ([]string, error)

List implements TemplateLoader

func (*FileSystemLoader) Load

func (f *FileSystemLoader) Load(name string) (string, error)

Load implements TemplateLoader

func (*FileSystemLoader) Watch

func (f *FileSystemLoader) Watch(ctx context.Context, callback func(name string)) error

Watch implements TemplateLoader

type FileWatcher

type FileWatcher interface {
	// Watch starts watching the specified directory for changes
	Watch(ctx context.Context, dir, extension string, recursive bool, callback func(name string)) error

	// Close stops watching and cleans up resources
	Close() error
}

FileWatcher watches for file system changes

func NewFileWatcher

func NewFileWatcher() (FileWatcher, error)

NewFileWatcher creates a new file watcher

type GenericParser

type GenericParser[T any] interface {
	// Parse executes the named template and returns the result as type T
	Parse(templateName string, request *http.Request) (T, error)

	// ParseWith executes the named template with custom data and returns the result as type T
	ParseWith(templateName string, request *http.Request, data interface{}) (T, error)

	// UpdateTemplate loads or updates a template with the given content and hash
	UpdateTemplate(name string, content string, hash string) error

	// GetCacheStats returns cache statistics
	GetCacheStats() CacheStats

	// Close cleanly shuts down the parser and releases resources
	Close() error
}

GenericParser provides type-safe template parsing for HTTP requests T is the target type that the parsed template will be converted to

func NewGenericParser

func NewGenericParser[T any](config Config) (GenericParser[T], error)

NewGenericParser creates a new generic template parser with the given configuration

type MemoryLoader

type MemoryLoader struct {
	// contains filtered or unexported fields
}

MemoryLoader loads templates from memory (useful for testing)

func NewMemoryLoader

func NewMemoryLoader() *MemoryLoader

NewMemoryLoader creates a new memory-based template loader

func (*MemoryLoader) AddTemplate

func (m *MemoryLoader) AddTemplate(name, content string)

AddTemplate adds a template to memory

func (*MemoryLoader) LastModified

func (m *MemoryLoader) LastModified(name string) (time.Time, error)

LastModified implements TemplateLoader (returns current time for memory loader)

func (*MemoryLoader) List

func (m *MemoryLoader) List() ([]string, error)

List implements TemplateLoader

func (*MemoryLoader) Load

func (m *MemoryLoader) Load(name string) (string, error)

Load implements TemplateLoader

func (*MemoryLoader) Watch

func (m *MemoryLoader) Watch(ctx context.Context, callback func(name string)) error

Watch implements TemplateLoader (no-op for memory loader)

type Parser

type Parser interface {
	// Parse executes the named template with the given HTTP request data
	Parse(templateName string, request *http.Request, output io.Writer) error

	// ParseWith executes the named template with custom data along with HTTP request
	ParseWith(templateName string, request *http.Request, data interface{}, output io.Writer) error

	// UpdateTemplate loads or updates a template with the given content and hash
	UpdateTemplate(name string, content string, hash string) error

	// GetCacheStats returns cache statistics
	GetCacheStats() CacheStats

	// Close cleanly shuts down the parser and releases resources
	Close() error
}

Parser provides high-performance template parsing for HTTP requests

func NewParser

func NewParser(config Config) (Parser, error)

NewParser creates a new template parser with the given configuration

type RequestData

type RequestData struct {
	// Request is the original HTTP request
	Request *http.Request

	// Headers contains all HTTP headers
	Headers map[string][]string

	// Query contains query parameters
	Query map[string][]string

	// Form contains form data (for POST requests)
	Form map[string][]string

	// Body contains the request body as string
	Body string

	// BodyJSON contains parsed JSON data when Content-Type is application/json
	BodyJSON map[string]interface{}

	// BodyXML contains parsed XML data when Content-Type is text/xml or application/xml
	BodyXML map[string]interface{}

	// Custom contains any additional custom data
	Custom interface{}
}

RequestData represents the data structure available to templates

func ExtractRequestData

func ExtractRequestData(r *RereadableRequest, customData interface{}) (*RequestData, error)

ExtractRequestData extracts structured data from the HTTP request for template use

type RereadableRequest

type RereadableRequest struct {
	*http.Request
	// contains filtered or unexported fields
}

RereadableRequest wraps an HTTP request to make it re-readable

func NewRereadableRequest

func NewRereadableRequest(r *http.Request) (*RereadableRequest, error)

NewRereadableRequest creates a new re-readable HTTP request

func (*RereadableRequest) Body

func (r *RereadableRequest) Body() string

Body returns the request body as a string

func (*RereadableRequest) BodyBytes

func (r *RereadableRequest) BodyBytes() []byte

BodyBytes returns the request body as bytes

func (*RereadableRequest) Reset

func (r *RereadableRequest) Reset()

Reset resets the request body to the beginning for re-reading

type TemplateCache

type TemplateCache struct {
	// contains filtered or unexported fields
}

TemplateCache provides efficient caching of compiled templates

func NewTemplateCache

func NewTemplateCache(maxSize int, funcMap template.FuncMap) *TemplateCache

NewTemplateCache creates a new template cache

func (*TemplateCache) Clear

func (c *TemplateCache) Clear()

Clear clears all templates from the cache

func (*TemplateCache) Get

func (c *TemplateCache) Get(name string, loader TemplateLoader) (*template.Template, error)

Get retrieves a template from the cache or compiles it if not found

func (*TemplateCache) Remove

func (c *TemplateCache) Remove(name string)

Remove removes a template from the cache

func (*TemplateCache) Set

func (c *TemplateCache) Set(name string, tmpl *template.Template, hash string)

Set directly sets a template in the cache with the given hash

func (*TemplateCache) Stats

func (c *TemplateCache) Stats() CacheStats

Stats returns cache statistics

type TemplateLoader

type TemplateLoader interface {
	// Load returns the template content by name
	Load(name string) (content string, err error)

	// List returns all available template names
	List() ([]string, error)

	// Watch starts watching for template changes and calls the callback
	// when a template is modified. Returns a context cancel function.
	Watch(ctx context.Context, callback func(name string)) error

	// LastModified returns the last modification time of a template
	LastModified(name string) (time.Time, error)
}

TemplateLoader defines the interface for loading templates

type XMLHelper

type XMLHelper struct{}

XMLHelper provides template functions for XML manipulation

func (XMLHelper) GetXMLAttribute

func (h XMLHelper) GetXMLAttribute(xmlMap map[string]interface{}, elementName, attrName string) string

GetXMLAttribute extracts a specific attribute from an XML node map Usage: {{xmlAttr .BodyXML "key" "attr1"}} to get the 'attr1' attribute from 'key' element Works with format (key/attr)

func (XMLHelper) GetXMLAttributeArray

func (h XMLHelper) GetXMLAttributeArray(xmlMap map[string]interface{}, elementName, attrName string) []string

GetXMLAttributeArray extracts all attribute values as an array Usage: {{xmlAttrArray .BodyXML "item" "id"}} to get all 'id' attributes from 'item' elements

func (XMLHelper) GetXMLText

func (h XMLHelper) GetXMLText(xmlMap map[string]interface{}, elementName string) string

GetXMLText extracts text content from an XML element

func (XMLHelper) GetXMLTextArray

func (h XMLHelper) GetXMLTextArray(xmlMap map[string]interface{}, elementName string) []string

GetXMLTextArray extracts all text content from XML elements as string array

func (XMLHelper) GetXMLValue

func (h XMLHelper) GetXMLValue(xmlMap map[string]interface{}, elementName string) interface{}

GetXMLValue extracts the value of an XML element Usage: {{xmlValue .BodyXML "key"}} to get the value of 'key' element For arrays: returns the first element

func (XMLHelper) GetXMLValueArray

func (h XMLHelper) GetXMLValueArray(xmlMap map[string]interface{}, elementName string) []interface{}

GetXMLValueArray extracts all values of an XML element as an array Usage: {{xmlValueArray .BodyXML "item"}} to get all 'item' element values

func (XMLHelper) HasXMLAttribute

func (h XMLHelper) HasXMLAttribute(xmlMap map[string]interface{}, elementName, attrName string) bool

HasXMLAttribute checks if an XML element has a specific attribute Usage: {{hasXMLAttr .BodyXML "key" "attr1"}}

func (XMLHelper) HasXMLElement

func (h XMLHelper) HasXMLElement(xmlMap map[string]interface{}, elementName string) bool

HasXMLElement checks if an XML element exists Usage: {{hasXMLElement .BodyXML "key"}}

func (XMLHelper) IsXMLArray

func (h XMLHelper) IsXMLArray(xmlMap map[string]interface{}, elementName string) bool

IsXMLArray checks if an XML element is an array (has multiple values) Usage: {{isXMLArray .BodyXML "item"}}

func (XMLHelper) ListXMLAttributes

func (h XMLHelper) ListXMLAttributes(xmlMap map[string]interface{}, elementName string) []string

ListXMLAttributes returns all attribute names for a specific element Usage: {{range xmlAttrs .BodyXML "key"}}{{.}}{{end}}

func (XMLHelper) ListXMLElements

func (h XMLHelper) ListXMLElements(xmlMap map[string]interface{}) []string

ListXMLElements returns all element names from the XML map Usage: {{range xmlElements .BodyXML}}{{.}}{{end}}

func (XMLHelper) XMLArrayLength

func (h XMLHelper) XMLArrayLength(xmlMap map[string]interface{}, elementName string) int

XMLArrayLength returns the length of an XML element array Usage: {{xmlArrayLen .BodyXML "item"}}

Directories

Path Synopsis
examples
basic command
slog_demo command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL