GoWindGoWind
Home
Framework
GoWind Toolkit
  • Open Source

    • GoWind Admin (Admin Platform)
    • GoWind CMS (Content Management)
    • GoWind UBA (Behavior Analytics)
    • GoWind Shop (E-commerce)
    • GoWind Kanban (Kanban Boards)
    • GoWind Ledger (Personal Finance)
    • GoWind ShortPlay (Short Drama)
    • GoWind Ops (Operations Platform)
    • RushWind (Rust Ecosystem)
  • Open Core (Community + Enterprise)

    • GoWind OA (Office Collaboration)
    • GoWind ERP (Procure-to-Pay)
    • GoWind WMS (Warehousing)
  • Commercial

    • GoWind IM (Instant Messaging)
    • GoWind IoT (IoT Platform)
    • GoWind Edu (AI Education)
    • GoWind MES (Manufacturing Execution)
    • GoWind CDC (Data Pipeline)
    • GoWind SDP (Zero-Trust Gateway)
    • GoWind Quant (Quant Research)
    • GoWind Trade Radar (Export Sourcing)
    • GoWind Game Radar (Game Greenlight)
  • 简体中文
  • English
GitHub
Home
Framework
GoWind Toolkit
  • Open Source

    • GoWind Admin (Admin Platform)
    • GoWind CMS (Content Management)
    • GoWind UBA (Behavior Analytics)
    • GoWind Shop (E-commerce)
    • GoWind Kanban (Kanban Boards)
    • GoWind Ledger (Personal Finance)
    • GoWind ShortPlay (Short Drama)
    • GoWind Ops (Operations Platform)
    • RushWind (Rust Ecosystem)
  • Open Core (Community + Enterprise)

    • GoWind OA (Office Collaboration)
    • GoWind ERP (Procure-to-Pay)
    • GoWind WMS (Warehousing)
  • Commercial

    • GoWind IM (Instant Messaging)
    • GoWind IoT (IoT Platform)
    • GoWind Edu (AI Education)
    • GoWind MES (Manufacturing Execution)
    • GoWind CDC (Data Pipeline)
    • GoWind SDP (Zero-Trust Gateway)
    • GoWind Quant (Quant Research)
    • GoWind Trade Radar (Export Sourcing)
    • GoWind Game Radar (Game Greenlight)
  • 简体中文
  • English
GitHub
  • Introduction

    • GoWind Framework
    • Framework Architecture
  • go-wind Core

    • go-wind Core Framework
    • App Lifecycle Management
    • Context Propagation
    • Transport Abstraction
    • Log Facade Interface
  • go-wind-plugins

    • go-wind-plugins Plugin Overview
    • Plugin Configuration System
    • Plugin Registration Mechanism (SPI)
    • Logging Adapter Plugins
    • Transport Protocol Plugins
    • Message Middleware Plugins
    • Encoding Plugins
    • Security and Authentication Plugins
    • Distributed Tracing Plugins
    • Cache Plugins
    • Object Storage Plugins (OSS)
    • Rate Limiting Plugins
    • Metrics Plugins
    • AI Plugins
    • Workflow Plugins
    • Database and Cache Plugins
    • Bootstrap Integration and Gateway Plugins
    • Compression and Serialization Plugins
    • Template Rendering and Validation Plugins
  • go-wind-bootstrap

    • go-wind-bootstrap: The Declarative Bootstrap
    • The Bootstrap Configuration System
    • The Bootstrap SPI Mechanism
    • Declarative Middleware Orchestration
    • Bootstrap Command-Line Integration
    • Bootstrap Practical Examples
  • go-wind-flow Workflow Engine

    • GoWind Flow Process Engine
  • Tutorials

    • Quick Start Tutorial
    • Custom Plugin Development Tutorial
    • Multi-Protocol Listening Tutorial
    • Framework Migration Guide

Custom Plugin Development Tutorial

This tutorial teaches you how to develop a go-wind plugin from scratch and register it with the Bootstrap SPI.

Goal

Develop an IP blocklist middleware plugin with the following features:

  • Read the IP list from YAML configuration
  • Intercept requests from blocklisted IPs
  • Support CIDR ranges

Step 1: Create the Plugin Directory

mkdir -p plugins/middleware/ip_blocklist
cd plugins/middleware/ip_blocklist

Step 2: Define Options

// plugins/middleware/ip_blocklist/option.go
package ip_blocklist

type Option func(*Config)

type Config struct {
    IPList    []string `yaml:"ip_list"`
    CIDRList  []string `yaml:"cidr_list"`
    StatusCode int     `yaml:"status_code"`
    Message   string   `yaml:"message"`
    SkipPaths []string `yaml:"skip_paths"`
}

func WithIPList(ips []string) Option {
    return func(c *Config) { c.IPList = ips }
}

func WithCIDRList(cidrs []string) Option {
    return func(c *Config) { c.CIDRList = cidrs }
}

func WithStatusCode(code int) Option {
    return func(c *Config) { c.StatusCode = code }
}

func WithMessage(msg string) Option {
    return func(c *Config) { c.Message = msg }
}

func WithSkipPaths(paths []string) Option {
    return func(c *Config) { c.SkipPaths = paths }
}

Step 3: Implement the Middleware

// plugins/middleware/ip_blocklist/middleware.go
package ip_blocklist

import (
    "net"
    "net/http"
    "strings"
)

type Middleware struct {
    config       *Config
    ipSet        map[string]bool
    cidrNetworks []*net.IPNet
}

func New(opts ...Option) *Middleware {
    cfg := &Config{
        StatusCode: http.StatusForbidden,
        Message:    "Access denied",
    }
    for _, opt := range opts {
        opt(cfg)
    }

    m := &Middleware{config: cfg}

    // Build the IP set
    m.ipSet = make(map[string]bool, len(cfg.IPList))
    for _, ip := range cfg.IPList {
        m.ipSet[strings.TrimSpace(ip)] = true
    }

    // Parse CIDRs
    for _, cidr := range cfg.CIDRList {
        if _, network, err := net.ParseCIDR(cidr); err == nil {
            m.cidrNetworks = append(m.cidrNetworks, network)
        }
    }

    return m
}

func (m *Middleware) Handler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Skip specific paths
        for _, path := range m.config.SkipPaths {
            if r.URL.Path == path {
                next.ServeHTTP(w, r)
                return
            }
        }

        clientIP := extractIP(r)

        // Check exact matches
        if m.ipSet[clientIP] {
            w.WriteHeader(m.config.StatusCode)
            w.Write([]byte(m.config.Message))
            return
        }

        // Check CIDR ranges
        ip := net.ParseIP(clientIP)
        for _, network := range m.cidrNetworks {
            if network.Contains(ip) {
                w.WriteHeader(m.config.StatusCode)
                w.Write([]byte(m.config.Message))
                return
            }
        }

        next.ServeHTTP(w, r)
    })
}

func extractIP(r *http.Request) string {
    // Prefer extracting from X-Forwarded-For
    if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
        if idx := strings.Index(xff, ","); idx > 0 {
            return strings.TrimSpace(xff[:idx])
        }
        return strings.TrimSpace(xff)
    }
    if xri := r.Header.Get("X-Real-IP"); xri != "" {
        return xri
    }
    // Extract from RemoteAddr
    host, _, err := net.SplitHostPort(r.RemoteAddr)
    if err != nil {
        return r.RemoteAddr
    }
    return host
}

Step 4: Register with the Bootstrap

// plugins/middleware/ip_blocklist/register.go
package ip_blocklist

import (
    "net/http"
    "github.com/tx7do/go-wind-bootstrap/middleware"
)

func init() {
    middleware.RegisterWithConfig("ip_blocklist", func(raw map[string]interface{}) func(http.Handler) http.Handler {
        cfg := &Config{
            StatusCode: http.StatusForbidden,
            Message:    "Access denied",
        }

        // Map from the YAML map to Config
        if v, ok := raw["ip_list"]; ok {
            for _, ip := range v.([]interface{}) {
                cfg.IPList = append(cfg.IPList, ip.(string))
            }
        }
        if v, ok := raw["cidr_list"]; ok {
            for _, cidr := range v.([]interface{}) {
                cfg.CIDRList = append(cfg.CIDRList, cidr.(string))
            }
        }
        if v, ok := raw["status_code"]; ok {
            cfg.StatusCode = int(v.(int))
        }
        if v, ok := raw["message"]; ok {
            cfg.Message = v.(string)
        }
        if v, ok := raw["skip_paths"]; ok {
            for _, p := range v.([]interface{}) {
                cfg.SkipPaths = append(cfg.SkipPaths, p.(string))
            }
        }

        m := New(
            WithIPList(cfg.IPList),
            WithCIDRList(cfg.CIDRList),
            WithStatusCode(cfg.StatusCode),
            WithMessage(cfg.Message),
            WithSkipPaths(cfg.SkipPaths),
        )

        return m.Handler
    })
}

Step 5: Configure YAML

# configs/config.yaml
server:
  http:
    addr: ":8080"
    middleware:
      - recovery
      - cors
      - ip_blocklist         # Use the custom middleware
      - logger

middleware:
  ip_blocklist:
    ip_list:
      - "192.168.1.100"
      - "10.0.0.50"
    cidr_list:
      - "10.10.0.0/24"
      - "172.16.0.0/16"
    status_code: 403
    message: "Your IP has been blocked"
    skip_paths:
      - /health
      - /metrics

Step 6: Use It

// cmd/server/main.go
package main

import (
    _ "github.com/tx7do/go-wind-plugins/transport/http"
    _ "github.com/tx7do/go-wind-plugins/log/zap"

    "github.com/tx7do/go-wind-bootstrap"
    _ "my-service/plugins/middleware/ip_blocklist"   // blank import
)

func main() {
    app := bootstrap.New("configs/config.yaml")
    app.Run()
}

Step 7: Test

# Normal request
curl http://localhost:8080/api/users
# 200 OK

# Blocklisted IP (simulated)
curl -H "X-Forwarded-For: 192.168.1.100" http://localhost:8080/api/users
# 403 Forbidden: Your IP has been blocked

# Blocklisted CIDR range
curl -H "X-Forwarded-For: 10.10.0.5" http://localhost:8080/api/users
# 403 Forbidden: Your IP has been blocked

# Health check is not restricted
curl -H "X-Forwarded-For: 192.168.1.100" http://localhost:8080/health
# 200 OK

Extension: A Dynamic Blocklist

Load a dynamic blocklist from Redis:

type DynamicMiddleware struct {
    config *Config
    cache  cache.Cache
    ttl    time.Duration
}

func (m *DynamicMiddleware) Handler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        clientIP := extractIP(r)

        // Check in Redis
        blocked, _ := m.cache.Exists(r.Context(), "blocklist:"+clientIP)
        if blocked {
            w.WriteHeader(http.StatusForbidden)
            w.Write([]byte("Your IP has been temporarily blocked"))
            return
        }

        next.ServeHTTP(w, r)
    })
}
// Dynamically block an IP
func BlockIP(ctx context.Context, cache cache.Cache, ip string, duration time.Duration) error {
    return cache.Set(ctx, "blocklist:"+ip, []byte("1"), duration)
}

Complete Directory Structure

plugins/middleware/ip_blocklist/
├── option.go          # Options and configuration
├── middleware.go      # Middleware implementation
├── register.go        # SPI registration
└── register_test.go   # Tests

Development Checklist

CheckDescription
Implement Option functional options✓ With* family of functions
Implement New(opts ...Option)✓ Factory function
Register with the Bootstrap SPI✓ Call Register inside init()
Support YAML configuration✓ RegisterWithConfig
Write tests✓ At least covering the core logic
Write documentation✓ README or doc comments

Related Documentation

  • The Plugin Registration Mechanism
  • The Bootstrap SPI Mechanism
  • Declarative Middleware Orchestration
  • Quick Start Tutorial
Edit this page
Last Updated:: 11/3/26, 2:14 PM
Contributors: Bobo
Prev
Quick Start Tutorial
Next
Multi-Protocol Listening Tutorial