first commit
This commit is contained in:
12
examples/plugins/http-transport-only/Makefile
Normal file
12
examples/plugins/http-transport-only/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
.PHONY: build clean
|
||||
|
||||
build:
|
||||
@echo "Building HTTP-Transport-Only plugin..."
|
||||
@mkdir -p build
|
||||
@go build -buildmode=plugin -o build/http-transport-only.so main.go
|
||||
@echo "Plugin built successfully: build/http-transport-only.so"
|
||||
|
||||
clean:
|
||||
@echo "Cleaning build directory..."
|
||||
@rm -rf build
|
||||
@echo "Clean complete"
|
||||
127
examples/plugins/http-transport-only/README.md
Normal file
127
examples/plugins/http-transport-only/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# HTTP-Transport-Only Plugin Example
|
||||
|
||||
This example demonstrates a plugin that only implements the `HTTPTransportPlugin` interface for HTTP-layer request/response interception.
|
||||
|
||||
## Features
|
||||
|
||||
- **HTTPTransportPreHook**: Intercepts HTTP requests before they enter Bifrost core
|
||||
- Authentication validation
|
||||
- Rate limiting (in-memory, per API key)
|
||||
- Request validation (size limits)
|
||||
- Custom header injection
|
||||
- Request short-circuiting for auth failures
|
||||
|
||||
- **HTTPTransportPostHook**: Intercepts HTTP responses after Bifrost core processing
|
||||
- CORS header injection
|
||||
- Security headers
|
||||
- Request duration tracking
|
||||
- Error response enrichment
|
||||
- Response logging
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Security**
|
||||
- Authentication/Authorization
|
||||
- API key validation
|
||||
- Request sanitization
|
||||
|
||||
- **Rate Limiting**
|
||||
- Per-user limits
|
||||
- Per-endpoint limits
|
||||
- Burst protection
|
||||
|
||||
- **Observability**
|
||||
- Request/response logging
|
||||
- Performance monitoring
|
||||
- Access tracking
|
||||
|
||||
- **Compliance**
|
||||
- CORS enforcement
|
||||
- Security headers
|
||||
- Request/response auditing
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
This creates `build/http-transport-only.so`
|
||||
|
||||
## Configuration
|
||||
|
||||
Add to your Bifrost config:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"path": "/path/to/http-transport-only.so",
|
||||
"name": "http-transport-only",
|
||||
"display_name": "Security & Rate Limiting",
|
||||
"enabled": true,
|
||||
"type": "http_transport",
|
||||
"config": {
|
||||
"require_auth": true,
|
||||
"rate_limit": 100,
|
||||
"rate_window": 60,
|
||||
"max_body_size": 1048576
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Note:**
|
||||
- `name` is the system identifier (from `GetName()`) and is **not editable**
|
||||
- `display_name` is shown in the UI and is **editable** by users
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `require_auth` | boolean | `true` | Enable/disable authentication header enforcement |
|
||||
| `rate_limit` | integer | `10` | Maximum requests per window (0 = unlimited) |
|
||||
| `rate_window` | integer | `60` | Rate limit window in seconds |
|
||||
| `max_body_size` | integer | `1048576` | Maximum request body size in bytes (0 = unlimited) |
|
||||
|
||||
### Example Configurations
|
||||
|
||||
**Disable authentication:**
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"require_auth": false,
|
||||
"rate_limit": 1000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unlimited rate limiting:**
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"require_auth": true,
|
||||
"rate_limit": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Strict limits:**
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"require_auth": true,
|
||||
"rate_limit": 10,
|
||||
"rate_window": 60,
|
||||
"max_body_size": 512000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- This plugin operates at the HTTP transport layer only
|
||||
- Works only when using bifrost-http, not when using Bifrost as a Go SDK
|
||||
- Rate limiter is in-memory (resets on restart)
|
||||
- For production, consider using Redis for distributed rate limiting
|
||||
32
examples/plugins/http-transport-only/go.mod
Normal file
32
examples/plugins/http-transport-only/go.mod
Normal file
@@ -0,0 +1,32 @@
|
||||
module github.com/maximhq/bifrost/examples/plugins/http-transport-only
|
||||
|
||||
go 1.26.2
|
||||
|
||||
replace github.com/maximhq/bifrost/core => ../../../core
|
||||
|
||||
require github.com/maximhq/bifrost/core v0.0.0-00010101000000-000000000000
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mark3labs/mcp-go v0.43.2 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.68.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
80
examples/plugins/http-transport-only/go.sum
Normal file
80
examples/plugins/http-transport-only/go.sum
Normal file
@@ -0,0 +1,80 @@
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
|
||||
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
|
||||
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
235
examples/plugins/http-transport-only/main.go
Normal file
235
examples/plugins/http-transport-only/main.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
)
|
||||
|
||||
// Plugin configuration
|
||||
type PluginConfig struct {
|
||||
RequireAuth bool `json:"require_auth"` // Toggle auth header enforcement
|
||||
RateLimit int `json:"rate_limit"` // Max requests per window (0 = unlimited)
|
||||
RateWindow int `json:"rate_window"` // Rate limit window in seconds (default: 60)
|
||||
MaxBodySize int `json:"max_body_size"` // Max request body size in bytes (0 = unlimited)
|
||||
}
|
||||
|
||||
var (
|
||||
// Default configuration
|
||||
pluginConfig = &PluginConfig{
|
||||
RequireAuth: true, // Require auth by default
|
||||
RateLimit: 10, // 10 requests per window by default
|
||||
RateWindow: 60, // 60 second window by default
|
||||
MaxBodySize: 1024 * 1024, // 1MB by default
|
||||
}
|
||||
|
||||
rateLimiter = &RateLimiter{
|
||||
requests: make(map[string][]time.Time),
|
||||
}
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex
|
||||
requests map[string][]time.Time
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) Allow(key string, limit int, window int) bool {
|
||||
// If rate limiting is disabled (limit = 0), allow all requests
|
||||
if limit <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
windowStart := now.Add(-time.Duration(window) * time.Second)
|
||||
|
||||
// Clean old requests
|
||||
if reqs, ok := rl.requests[key]; ok {
|
||||
validReqs := []time.Time{}
|
||||
for _, t := range reqs {
|
||||
if t.After(windowStart) {
|
||||
validReqs = append(validReqs, t)
|
||||
}
|
||||
}
|
||||
rl.requests[key] = validReqs
|
||||
|
||||
// Check if limit exceeded
|
||||
if len(validReqs) >= limit {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Add new request
|
||||
rl.requests[key] = append(rl.requests[key], now)
|
||||
return true
|
||||
}
|
||||
|
||||
// Init is called when the plugin is loaded (optional)
|
||||
func Init(config any) error {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] Init called")
|
||||
|
||||
// Parse configuration
|
||||
if configMap, ok := config.(map[string]interface{}); ok {
|
||||
// Parse require_auth toggle
|
||||
if requireAuth, ok := configMap["require_auth"].(bool); ok {
|
||||
pluginConfig.RequireAuth = requireAuth
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Auth enforcement: %v\n", pluginConfig.RequireAuth)
|
||||
}
|
||||
|
||||
// Parse rate_limit
|
||||
if rateLimit, ok := configMap["rate_limit"].(float64); ok {
|
||||
pluginConfig.RateLimit = int(rateLimit)
|
||||
if pluginConfig.RateLimit <= 0 {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] Rate limiting disabled")
|
||||
} else {
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Rate limit: %d requests per %d seconds\n",
|
||||
pluginConfig.RateLimit, pluginConfig.RateWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse rate_window
|
||||
if rateWindow, ok := configMap["rate_window"].(float64); ok {
|
||||
pluginConfig.RateWindow = int(rateWindow)
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Rate window: %d seconds\n", pluginConfig.RateWindow)
|
||||
}
|
||||
|
||||
// Parse max_body_size
|
||||
if maxBodySize, ok := configMap["max_body_size"].(float64); ok {
|
||||
pluginConfig.MaxBodySize = int(maxBodySize)
|
||||
if pluginConfig.MaxBodySize <= 0 {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] Request size validation disabled")
|
||||
} else {
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Max body size: %d bytes\n", pluginConfig.MaxBodySize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Configuration loaded: %+v\n", pluginConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the plugin (required)
|
||||
// This is the system identifier - not editable by users
|
||||
// Users can set a custom display_name in the config for the UI
|
||||
func GetName() string {
|
||||
return "http-transport-only"
|
||||
}
|
||||
|
||||
// HTTPTransportPreHook is called at the HTTP layer before requests enter Bifrost core
|
||||
// This example demonstrates authentication, rate limiting, and request validation
|
||||
func HTTPTransportPreHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] HTTPTransportPreHook called")
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Method: %s, Path: %s\n", req.Method, req.Path)
|
||||
|
||||
// Example 1: Authentication check (configurable)
|
||||
authHeader := req.CaseInsensitiveHeaderLookup("Authorization")
|
||||
if pluginConfig.RequireAuth && authHeader == "" {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] Missing authorization header")
|
||||
return &schemas.HTTPResponse{
|
||||
StatusCode: 401,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
Body: []byte(`{"error": "Unauthorized: Missing authorization header"}`),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Example 2: Rate limiting by API key (configurable)
|
||||
if pluginConfig.RateLimit > 0 {
|
||||
apiKey := authHeader // In real implementation, extract from Bearer token
|
||||
if apiKey == "" {
|
||||
apiKey = "anonymous" // Default key for unauthenticated requests
|
||||
}
|
||||
|
||||
if !rateLimiter.Allow(apiKey, pluginConfig.RateLimit, pluginConfig.RateWindow) {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] Rate limit exceeded")
|
||||
return &schemas.HTTPResponse{
|
||||
StatusCode: 429,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Retry-After": fmt.Sprintf("%d", pluginConfig.RateWindow),
|
||||
"X-RateLimit-Limit": fmt.Sprintf("%d", pluginConfig.RateLimit),
|
||||
},
|
||||
Body: []byte(`{"error": "Rate limit exceeded. Please try again later."}`),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Example 3: Request validation (configurable)
|
||||
if pluginConfig.MaxBodySize > 0 && req.Method == "POST" && len(req.Body) > pluginConfig.MaxBodySize {
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Request body too large: %d bytes (max: %d)\n",
|
||||
len(req.Body), pluginConfig.MaxBodySize)
|
||||
return &schemas.HTTPResponse{
|
||||
StatusCode: 413,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
Body: []byte(fmt.Sprintf(`{"error": "Request body too large. Max size: %d bytes"}`, pluginConfig.MaxBodySize)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Example 4: Add custom headers
|
||||
req.Headers["X-Plugin-Processed"] = "true"
|
||||
req.Headers["X-Request-Time"] = time.Now().Format(time.RFC3339)
|
||||
|
||||
// Store metadata in context for PostHook
|
||||
ctx.SetValue(schemas.BifrostContextKey("http-plugin-start-time"), time.Now())
|
||||
|
||||
// Return nil to continue processing
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// HTTPTransportPostHook is called at the HTTP layer after Bifrost core processes the request
|
||||
// This example demonstrates response modification and logging
|
||||
func HTTPTransportPostHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] HTTPTransportPostHook called")
|
||||
|
||||
// Calculate request duration
|
||||
startTime := ctx.Value(schemas.BifrostContextKey("http-plugin-start-time"))
|
||||
if t, ok := startTime.(time.Time); ok {
|
||||
duration := time.Since(t)
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Request duration: %v\n", duration)
|
||||
|
||||
// Add duration header
|
||||
resp.Headers["X-Request-Duration-Ms"] = fmt.Sprintf("%d", duration.Milliseconds())
|
||||
}
|
||||
|
||||
// Example: Add CORS headers
|
||||
resp.Headers["Access-Control-Allow-Origin"] = "*"
|
||||
resp.Headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
|
||||
resp.Headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
|
||||
|
||||
// Example: Add security headers
|
||||
resp.Headers["X-Content-Type-Options"] = "nosniff"
|
||||
resp.Headers["X-Frame-Options"] = "DENY"
|
||||
resp.Headers["X-XSS-Protection"] = "1; mode=block"
|
||||
|
||||
// Example: Log response details
|
||||
fmt.Printf("[HTTP-Transport-Only Plugin] Response status: %d, size: %d bytes\n",
|
||||
resp.StatusCode, len(resp.Body))
|
||||
|
||||
// Example: Modify error responses to add custom metadata
|
||||
if resp.StatusCode >= 400 {
|
||||
var errorBody map[string]interface{}
|
||||
if err := json.Unmarshal(resp.Body, &errorBody); err == nil {
|
||||
errorBody["timestamp"] = time.Now().Format(time.RFC3339)
|
||||
errorBody["request_id"] = ctx.Value(schemas.BifrostContextKey("request_id"))
|
||||
if newBody, err := json.Marshal(errorBody); err == nil {
|
||||
resp.Body = newBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup is called when the plugin is unloaded (required)
|
||||
func Cleanup() error {
|
||||
fmt.Println("[HTTP-Transport-Only Plugin] Cleanup called")
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user