Parka Handlers Documentation

Learn about Parka's specialized handlers for serving static content, templates, and commands

Sections

Terminology & Glossary
πŸ“– Documentation
Navigation
35 sectionsv0.1
πŸ“„ Parka Handlers Documentation β€” glaze help handlers
handlers

Parka Handlers Documentation

Learn about Parka's specialized handlers for serving static content, templates, and commands

Topichandlersstatic filestemplatescommandsweb serverNewStaticDirHandlerNewStaticFileHandlerNewTemplateHandlerNewTemplateDirHandlerNewCommandHandlerNewCommandDirHandlerWithDefaultFSWithLocalPathWithTemplateNameWithDevMode

Parka Static Handlers Documentation

Parka provides two specialized handlers for serving static content: StaticDirHandler and StaticFileHandler. These handlers are designed to serve static files from either the filesystem or embedded files, with different strategies for path handling and file organization.

StaticDirHandler

The StaticDirHandler is designed to serve an entire directory of static files, maintaining the directory structure when serving the files over HTTP.

Structure

type StaticDirHandler struct {
    fs        fs.FS
    localPath string
}
  • fs: The filesystem interface that provides access to the static files
  • localPath: The base path within the filesystem where the static files are located

Configuration Options

The handler can be configured using functional options:

  1. WithDefaultFS(fs fs.FS, localPath string): Sets a default filesystem and local path

    handler := NewStaticDirHandler(
        WithDefaultFS(embeddedFS, "static"),
    )
    
  2. WithLocalPath(localPath string): Sets up the handler to serve files from a local directory

    handler := NewStaticDirHandler(
        WithLocalPath("/path/to/static/files"),
    )
    

Creation Methods

  1. Basic creation with options:

    handler := NewStaticDirHandler(options...)
    
  2. Creation from configuration:

    handler := NewStaticDirHandlerFromConfig(staticConfig, options...)
    

Usage

The handler is registered with a Parka server using the Serve method:

// Create a new Parka server
server, err := server.NewServer(
    server.WithPort(8080),
    server.WithAddress("localhost"),
)
if err != nil {
    return err
}

// Create and configure the handler
handler := NewStaticDirHandler(
    WithLocalPath("./static"),
)

// Register the handler with a base path
err = handler.Serve(server, "/static")
if err != nil {
    return err
}

This will serve all files from the configured directory under the /static URL path.

Path Handling

  • If serving from a local path, the handler automatically creates a directory filesystem
  • Trailing slashes are automatically handled
  • The local path is prefixed to filesystem paths when necessary

StaticFileHandler

The StaticFileHandler is designed to serve individual files or specific subdirectories, with more precise control over the served paths.

Structure

type StaticFileHandler struct {
    fs        fs.FS
    localPath string
}
  • fs: The filesystem interface that provides access to the static files
  • localPath: The specific path to the file or subdirectory to serve

Configuration Options

  1. WithDefaultFS(fs fs.FS, localPath string): Sets a default filesystem and local path

    handler := NewStaticFileHandler(
        WithDefaultFS(embeddedFS, "assets/file.css"),
    )
    
  2. WithLocalPath(localPath string): Sets up the handler to serve from a local path

    handler := NewStaticFileHandler(
        WithLocalPath("/path/to/specific/file.js"),
    )
    

Creation Methods

  1. Basic creation with options:

    handler := NewStaticFileHandler(options...)
    
  2. Creation from configuration:

    handler := NewStaticFileHandlerFromConfig(staticFileConfig, options...)
    

Usage

The handler is registered with a Parka server using the Serve method:

// Create a new Parka server
server, err := server.NewServer(
    server.WithPort(8080),
    server.WithAddress("localhost"),
)
if err != nil {
    return err
}

// Create and configure the handler
handler := NewStaticFileHandler(
    WithLocalPath("/path/to/specific/file.js"),
)

// Register the handler with a specific URL path
err = handler.Serve(server, "/assets/js/script.js")
if err != nil {
    return err
}

Path Handling

  • Leading slashes in local paths are automatically handled
  • Uses Echo's MustSubFS for safe subpath handling
  • Maintains exact path mapping between filesystem and URL paths

Differences Between Handlers

  1. Scope:

    • StaticDirHandler: Serves entire directories with their structure
    • StaticFileHandler: Serves specific files or subdirectories with precise path control
  2. Path Handling:

    • StaticDirHandler: Automatically handles directory structure and trailing slashes
    • StaticFileHandler: Provides exact path mapping and uses Echo's subfilesystem functionality
  3. Use Cases:

    • StaticDirHandler: Best for serving static assets like images, CSS, and JavaScript files in their directory structure
    • StaticFileHandler: Best for serving individual files or when precise control over URL paths is needed

Best Practices

  1. Directory Structure:

    • Keep static files organized in a clear directory structure
    • Use StaticDirHandler for serving multiple related files
    • Use StaticFileHandler for specific files that need custom URL paths
  2. Security:

    • Always validate and sanitize paths
    • Be careful with directory traversal attacks
    • Use embedded filesystems when possible for better security
  3. Performance:

    • Consider using a CDN for large static assets
    • Enable compression middleware for text-based files
    • Use caching headers appropriately

Examples

Serving an Embedded Directory

//go:embed static/*
var staticFS embed.FS

handler := NewStaticDirHandler(
    WithDefaultFS(staticFS, "static"),
)
err = handler.Serve(server, "/assets")
if err != nil {
    return err
}

Serving a Local Directory

handler := NewStaticDirHandler(
    WithLocalPath("./static"),
)
err = handler.Serve(server, "/static")
if err != nil {
    return err
}

Serving a Specific File

handler := NewStaticFileHandler(
    WithLocalPath("./assets/main.js"),
)
err = handler.Serve(server, "/js/main.js")
if err != nil {
    return err
}

Configuration-based Setup

config := &config.Static{
    LocalPath: "./static",
}
handler := NewStaticDirHandlerFromConfig(config)
err = handler.Serve(server, "/assets")
if err != nil {
    return err
}

Error Handling

Both handlers handle errors gracefully:

  • Invalid paths return appropriate HTTP errors
  • Missing files return 404 Not Found
  • Permission issues return 403 Forbidden

Integration with Echo

Both handlers integrate seamlessly with Echo's static file serving capabilities:

  • Use Echo's StaticFS method internally
  • Support Echo's middleware stack
  • Compatible with Echo's error handling

Further Reading

Template Handlers

Parka provides two specialized handlers for serving templated content: TemplateHandler and TemplateDirHandler. These handlers enable dynamic content rendering using Go templates, with support for both HTML and Markdown files.

TemplateHandler

The TemplateHandler is designed to serve a single template file, rendering it with optional data and supporting both HTML and Markdown content.

Structure

type TemplateHandler struct {
    fs              fs.FS
    TemplateFile    string
    rendererOptions []render.RendererOption
    renderer        *render.Renderer
    alwaysReload    bool
}
  • fs: The filesystem interface that provides access to the template files
  • TemplateFile: The path to the template file to be rendered
  • rendererOptions: Additional options for configuring the template renderer
  • renderer: The renderer instance used to process templates
  • alwaysReload: Whether to reload templates on every request (useful for development)

Configuration Options

The handler can be configured using functional options:

  1. WithDefaultFS(fs fs.FS): Sets a default filesystem for template loading

    handler := NewTemplateHandler("index.tmpl.html",
        WithDefaultFS(embeddedFS),
    )
    
  2. WithAlwaysReload(alwaysReload bool): Enables template reloading for development

    handler := NewTemplateHandler("index.tmpl.html",
        WithAlwaysReload(true),
    )
    

Usage

The handler is registered with a Parka server using the Serve method:

// Create a new Parka server
server, err := server.NewServer(
    server.WithPort(8080),
    server.WithAddress("localhost"),
)
if err != nil {
    return err
}

// Create and configure the handler
handler := NewTemplateHandler("index.tmpl.html",
    WithDefaultFS(embeddedFS),
    WithAlwaysReload(true),
)

// Register the handler with a URL path
err = handler.Serve(server, "/")
if err != nil {
    return err
}

TemplateDirHandler

The TemplateDirHandler is designed to serve an entire directory of templates, supporting both HTML and Markdown files with automatic routing based on file paths.

Structure

type TemplateDirHandler struct {
    fs                       fs.FS
    LocalDirectory           string
    IndexTemplateName        string
    MarkdownBaseTemplateName string
    rendererOptions          []render.RendererOption
    renderer                 *render.Renderer
    alwaysReload            bool
}
  • fs: The filesystem interface that provides access to the template files
  • LocalDirectory: The base directory containing templates
  • IndexTemplateName: The template to use for directory index pages
  • MarkdownBaseTemplateName: The base template for rendering Markdown files
  • rendererOptions: Additional options for configuring the template renderer
  • renderer: The renderer instance used to process templates
  • alwaysReload: Whether to reload templates on every request

Configuration Options

  1. WithDefaultFS(fs fs.FS, localPath string): Sets a default filesystem and local path

    handler, err := NewTemplateDirHandler(
        WithDefaultFS(embeddedFS, "templates"),
    )
    
  2. WithLocalDirectory(localPath string): Sets up the handler to serve from a local directory

    handler, err := NewTemplateDirHandler(
        WithLocalDirectory("./templates"),
    )
    

Usage

The handler is registered with a Parka server using the Serve method:

// Create a new Parka server
server, err := server.NewServer(
    server.WithPort(8080),
    server.WithAddress("localhost"),
)
if err != nil {
    return err
}

// Create and configure the handler
handler, err := NewTemplateDirHandler(
    WithLocalDirectory("./templates"),
    WithAlwaysReload(true),
)
if err != nil {
    return err
}

// Register the handler with a base path
err = handler.Serve(server, "/")
if err != nil {
    return err
}

Template Discovery

The TemplateDirHandler automatically discovers and serves:

  • *.tmpl.md - Markdown templates
  • *.md - Plain Markdown files
  • *.tmpl.html - HTML templates
  • *.html - Plain HTML files

Differences Between Handlers

  1. Scope:

    • TemplateHandler: Serves a single template file
    • TemplateDirHandler: Serves an entire directory of templates with automatic routing
  2. File Support:

    • TemplateHandler: Focused on single template rendering
    • TemplateDirHandler: Supports multiple template types and automatic discovery
  3. Use Cases:

    • TemplateHandler: Best for single pages or specific templates
    • TemplateDirHandler: Best for documentation sites, multi-page applications, or content-heavy sites

Best Practices

  1. Template Organization:

    • Use clear naming conventions for templates
    • Separate content from layout templates
    • Use base templates for consistent styling
    • Keep templates modular and reusable
  2. Development Workflow:

    • Use WithAlwaysReload(true) during development
    • Create a base template for consistent layouts
    • Use partials for reusable components
    • Implement proper error handling in templates
  3. Performance:

    • Disable template reloading in production
    • Use caching headers appropriately
    • Minimize template complexity
    • Consider precompiling templates

Examples

Serving a Single Template

handler := NewTemplateHandler("index.tmpl.html",
    WithDefaultFS(embeddedFS),
    WithAlwaysReload(true),
)
server.AddHandler(handler, "/")

Serving a Documentation Site

handler, err := NewTemplateDirHandler(
    WithLocalDirectory("./docs"),
    WithAlwaysReload(true),
)
server.AddHandler(handler, "/docs")

Configuration-based Setup

config := &config.TemplateDir{
    LocalDirectory: "./templates",
    IndexTemplateName: "index.tmpl.html",
}
handler, err := NewTemplateDirHandlerFromConfig(config)
server.AddHandler(handler, "/content")

Error Handling

All handlers handle errors gracefully and return appropriate error types that should be checked:

  • Invalid configuration returns initialization errors
  • Registration errors are returned by the Serve method
  • Runtime errors are handled through Echo's error handling system

Integration with Echo

The handlers integrate with Echo's routing system:

  • Use Echo's context for request handling
  • Support middleware for authentication and logging
  • Compatible with Echo's error handling
  • Support streaming responses

Further Reading

Command Handlers

Parka provides three specialized handlers for serving commands: CommandHandler, CommandDirHandler, and GenericCommandHandler. These handlers enable exposing commands as HTTP endpoints with various output formats and interactive UIs.

GenericCommandHandler

The GenericCommandHandler is the base handler that provides core functionality for serving commands over HTTP. It's used internally by both CommandHandler and CommandDirHandler.

Structure

type GenericCommandHandler struct {
    Stream          bool
    AdditionalData  map[string]interface{}
    ParameterFilter *config.ParameterFilter
    TemplateName    string
    IndexTemplateName string
    TemplateLookup   render.TemplateLookup
    BasePath         string
    preMiddlewares   []middlewares.Middleware
    postMiddlewares  []middlewares.Middleware
    middlewares      []middlewares.Middleware
}
  • Stream: Whether to use row-based streaming output (true by default)
  • AdditionalData: Extra data passed to templates
  • ParameterFilter: Configuration for parameter filtering, defaults, and overrides
  • TemplateName: Template for rendering command output
  • IndexTemplateName: Template for rendering command indexes
  • TemplateLookup: Interface for finding templates
  • BasePath: Base URL path for the handler
  • preMiddlewares: Middleware chain to run before parameter filter middlewares
  • postMiddlewares: Middleware chain to run after parameter filter middlewares

Endpoints

The handler provides several endpoints for different output formats:

  1. /data/*: Returns command output in JSON format
  2. /text/*: Returns command output as plain text
  3. /streaming/*: Streams command output using Server-Sent Events (SSE)
  4. /datatables/*: Displays command output in an interactive DataTables UI
  5. /download/*: Allows downloading command output in various formats

Configuration Options

  1. WithTemplateName(name string): Sets the template for command output
  2. WithParameterFilter(filter *config.ParameterFilter): Configures parameter handling
  3. WithMergeAdditionalData(data map[string]interface{}, override bool): Adds template data
  4. WithPreMiddlewares(middlewares ...middlewares.Middleware): Add middlewares to run before parameter filter middlewares
  5. WithPostMiddlewares(middlewares ...middlewares.Middleware): Add middlewares to run after parameter filter middlewares

Example:

handler := NewGenericCommandHandler(
    WithTemplateName("command.tmpl.html"),
    WithParameterFilter(filter),
    WithPreMiddlewares(myPreMiddleware1, myPreMiddleware2),
    WithPostMiddlewares(myPostMiddleware1, myPostMiddleware2),
)

The middlewares will be executed in this order:

  1. Pre-middlewares (in the order they were added)
  2. Parameter filter middlewares
  3. Post-middlewares (in the order they were added)

CommandHandler

The CommandHandler is designed to serve a single command with multiple output formats.

Structure

type CommandHandler struct {
    GenericCommandHandler
    DevMode bool
    Command cmds.Command
}
  • Inherits all functionality from GenericCommandHandler
  • DevMode: Enables development features like template reloading
  • Command: The command to be served

Configuration Options

  1. WithDevMode(devMode bool): Enables development mode

    handler := NewCommandHandler(cmd,
        WithDevMode(true),
    )
    
  2. WithGenericCommandHandlerOptions(options ...GenericCommandHandlerOption): Adds generic options

    handler := NewCommandHandler(cmd,
        WithGenericCommandHandlerOptions(
            WithTemplateName("command.tmpl.html"),
            WithParameterFilter(filter),
        ),
    )
    

Creation Methods

  1. Basic creation with options:

    handler := NewCommandHandler(myCommand, options...)
    
  2. Creation from configuration:

    handler, err := NewCommandHandlerFromConfig(config, loader, options...)
    

Usage

// Create a new Parka server
server, err := server.NewServer(
    server.WithPort(8080),
    server.WithAddress("localhost"),
)
if err != nil {
    return err
}

// Create your command
cmd := &MyCommand{}

// Create and configure the handler
handler := NewCommandHandler(cmd,
    WithDevMode(true),
    WithGenericCommandHandlerOptions(
        WithTemplateName("command.tmpl.html"),
        WithParameterFilter(filter),
    ),
)

// Register the handler with a URL path
err = handler.Serve(server, "/my-command")
if err != nil {
    return err
}

CommandDirHandler

The CommandDirHandler serves multiple commands from a repository, providing automatic routing and discovery.

Structure

type CommandDirHandler struct {
    GenericCommandHandler
    DevMode    bool
    Repository *repositories.Repository
}
  • Inherits all functionality from GenericCommandHandler
  • DevMode: Enables development features
  • Repository: The command repository to serve

Configuration Options

  1. WithDevMode(devMode bool): Enables development mode

    handler := NewCommandDirHandler(
        WithDevMode(true),
    )
    
  2. WithRepository(r *repositories.Repository): Sets the command repository

    handler := NewCommandDirHandler(
        WithRepository(myRepo),
    )
    

Configuration File Example

routes:
  - path: /commands
    commandDirectory:
      includeDefaultRepositories: true
      repositories:
        - ~/code/my-commands
      templateLookup:
        directories:
          - ~/templates
      indexTemplateName: index.tmpl.html
      defaults:
        flags:
          limit: 100
      overrides:
        layers:
          glazed:
            filter:
              - id
              - name
      additionalData:
        title: "My Commands"

Usage

// Create a new Parka server
server, err := server.NewServer(
    server.WithPort(8080),
    server.WithAddress("localhost"),
)
if err != nil {
    return err
}

// Create and configure your repository
repo := repositories.NewRepository()
repo.AddCommand(commands.NewHelloCommand())
// Add more commands...

// Create and configure the handler
handler, err := NewCommandDirHandler(
    WithRepository(repo),
    WithDevMode(true),
    WithGenericCommandHandlerOptions(
        WithIndexTemplateName("commands/index.tmpl.html"),
        WithTemplateName("commands/view.tmpl.html"),
    ),
)
if err != nil {
    return err
}

// Register the handler with a base path
err = handler.Serve(server, "/commands")
if err != nil {
    return err
}

Best Practices

  1. Command Organization:

    • Group related commands in repositories
    • Use clear naming conventions
    • Provide comprehensive command documentation
    • Use appropriate output formats for different use cases
  2. Security:

    • Validate command parameters
    • Use parameter filters to restrict access
    • Consider authentication for sensitive commands
    • Implement proper error handling
  3. Performance:

    • Use streaming for large outputs
    • Enable caching when appropriate
    • Consider rate limiting for heavy commands
    • Monitor command execution times

Examples

Serving a Single Command

cmd := &MyCommand{}
handler := NewCommandHandler(cmd,
    WithDevMode(true),
    WithGenericCommandHandlerOptions(
        WithTemplateName("command.tmpl.html"),
        WithParameterFilter(filter),
    ),
)
server.AddHandler(handler, "/my-command")

Serving a Command Repository

repo := repositories.NewRepository()
repo.AddCommandFromFile("./commands/my-command.yaml")

handler, err := NewCommandDirHandler(
    WithRepository(repo),
    WithDevMode(true),
    WithGenericCommandHandlerOptions(
        WithIndexTemplateName("index.tmpl.html"),
        WithTemplateName("command.tmpl.html"),
    ),
)
server.AddHandler(handler, "/commands")

Configuration-based Setup

config := &config.CommandDir{
    IncludeDefaultRepositories: true,
    Repositories: []string{"./commands"},
    IndexTemplateName: "index.tmpl.html",
}
handler, err := NewCommandDirHandlerFromConfig(config)
server.AddHandler(handler, "/api")

Error Handling

All handlers handle errors gracefully and return appropriate error types that should be checked:

  • Invalid configuration returns initialization errors
  • Registration errors are returned by the Serve method
  • Runtime errors are handled through Echo's error handling system

Integration with Echo

The handlers integrate with Echo's routing system:

  • Use Echo's context for request handling
  • Support middleware for authentication and logging
  • Compatible with Echo's error handling
  • Support streaming responses

Further Reading