dev UX optimisations
This commit is contained in:
@@ -26,19 +26,26 @@ A clear and concise description of what you expected to happen.
|
||||
|
||||
## Environment
|
||||
- Go Micro version: [e.g. v5.3.0]
|
||||
- Go version: [e.g. 1.21.0]
|
||||
- OS: [e.g. Ubuntu 22.04]
|
||||
- Plugins used: [e.g. consul registry, nats broker]
|
||||
- Go version: [run `go version`]
|
||||
- OS/Platform: [e.g. Ubuntu 22.04, macOS 14, Docker]
|
||||
- Plugins/Integrations: [e.g. consul registry, nats broker, redis cache]
|
||||
|
||||
## Logs
|
||||
```
|
||||
Paste relevant logs here
|
||||
Paste relevant logs here (use -v flag for verbose output)
|
||||
```
|
||||
|
||||
## Checklist
|
||||
- [ ] I've searched existing issues and this is not a duplicate
|
||||
- [ ] I've provided a minimal code sample that reproduces the issue
|
||||
- [ ] I've included my environment details
|
||||
- [ ] I've checked the documentation
|
||||
|
||||
## Additional context
|
||||
Add any other context about the problem here.
|
||||
|
||||
## Resources
|
||||
- [Documentation](https://github.com/micro/go-micro/tree/master/internal/website/docs)
|
||||
- [Examples](https://github.com/micro/go-micro/tree/master/internal/website/docs/examples)
|
||||
## Helpful Resources
|
||||
- [Troubleshooting Guide](https://github.com/micro/go-micro/tree/master/internal/website/docs/getting-started.md)
|
||||
- [Examples](https://github.com/micro/go-micro/tree/master/examples)
|
||||
- [API Reference](https://pkg.go.dev/go-micro.dev/v5)
|
||||
- [Discord Community](https://discord.gg/jwTYuUVAGh)
|
||||
|
||||
@@ -18,13 +18,25 @@ A clear and concise description of any alternative solutions or features you've
|
||||
## Use case
|
||||
Describe how this feature would be used in practice. What problem does it solve?
|
||||
|
||||
**Example:**
|
||||
```go
|
||||
// Show how the feature would be used
|
||||
```
|
||||
|
||||
## Implementation ideas (optional)
|
||||
If you have thoughts on how this could be implemented, share them here.
|
||||
|
||||
## Additional context
|
||||
Add any other context, code examples, or screenshots about the feature request here.
|
||||
|
||||
## Willing to contribute?
|
||||
- [ ] I'd be willing to submit a PR for this feature
|
||||
## Checklist
|
||||
- [ ] I've searched existing issues and this is not a duplicate
|
||||
- [ ] I've checked the roadmap and this isn't already planned
|
||||
- [ ] I've provided a clear use case
|
||||
- [ ] I'd be willing to submit a PR for this feature (optional)
|
||||
|
||||
## Resources
|
||||
- [Documentation](https://github.com/micro/go-micro/tree/master/internal/website/docs)
|
||||
- [Plugins](https://github.com/micro/go-micro/tree/master/internal/website/docs/plugins.md)
|
||||
## Helpful Resources
|
||||
- [Roadmap](https://github.com/micro/go-micro/blob/master/ROADMAP.md)
|
||||
- [Contributing Guide](https://github.com/micro/go-micro/blob/master/CONTRIBUTING.md)
|
||||
- [Architecture Docs](https://github.com/micro/go-micro/tree/master/internal/website/docs/architecture.md)
|
||||
- [Discord Community](https://discord.gg/jwTYuUVAGh)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Performance issue
|
||||
about: Report a performance problem or regression
|
||||
title: '[PERFORMANCE] '
|
||||
labels: performance
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Performance Issue
|
||||
|
||||
**Symptom:**
|
||||
Describe the performance problem (e.g., high latency, memory leak, CPU usage)
|
||||
|
||||
**Expected Performance:**
|
||||
What performance did you expect?
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Please provide benchmarks or profiling data:
|
||||
|
||||
```bash
|
||||
# CPU profiling
|
||||
go test -cpuprofile=cpu.prof -bench=.
|
||||
|
||||
# Memory profiling
|
||||
go test -memprofile=mem.prof -bench=.
|
||||
|
||||
# Results
|
||||
```
|
||||
|
||||
**Before/After comparison (if applicable):**
|
||||
- Before: X req/sec, Y ms latency
|
||||
- After: X req/sec, Y ms latency
|
||||
|
||||
## Code Sample
|
||||
|
||||
```go
|
||||
// Minimal code that demonstrates the performance issue
|
||||
```
|
||||
|
||||
## Environment
|
||||
- Go Micro version: [e.g. v5.3.0]
|
||||
- Go version: [run `go version`]
|
||||
- Hardware: [e.g. 4 CPU, 8GB RAM]
|
||||
- OS: [e.g. Ubuntu 22.04]
|
||||
- Load: [e.g. 1000 req/sec, 100 concurrent connections]
|
||||
|
||||
## Profiling Data
|
||||
|
||||
Attach pprof profiles if available:
|
||||
- CPU profile
|
||||
- Memory profile
|
||||
- Goroutine dump
|
||||
|
||||
## Additional Context
|
||||
|
||||
Add any other context about the performance issue.
|
||||
|
||||
## Resources
|
||||
- [Performance Guide](https://github.com/micro/go-micro/tree/master/internal/website/docs/performance.md)
|
||||
- [Benchmarking](https://pkg.go.dev/testing#hdr-Benchmarks)
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
# Develop tools
|
||||
/.vscode/
|
||||
/.idea/
|
||||
/.trunk
|
||||
|
||||
# VS Code workspace files (keep settings for consistency)
|
||||
/.vscode/*
|
||||
!/.vscode/settings.json
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
labelType: long
|
||||
coverThreshold: 70
|
||||
buildStyle:
|
||||
bold: true
|
||||
foreground: yellow
|
||||
startStyle:
|
||||
foreground: lightBlack
|
||||
passStyle:
|
||||
foreground: green
|
||||
failStyle:
|
||||
bold: true
|
||||
foreground: "#821515"
|
||||
skipStyle:
|
||||
foreground: lightBlack
|
||||
passPackageStyle:
|
||||
foreground: green
|
||||
hide: false
|
||||
failPackageStyle:
|
||||
bold: true
|
||||
foreground: "#821515"
|
||||
coveredStyle:
|
||||
foreground: green
|
||||
uncoveredStyle:
|
||||
bold: true
|
||||
foreground: yellow
|
||||
fileStyle:
|
||||
foreground: cyan
|
||||
lineStyle:
|
||||
foreground: magenta
|
||||
Vendored
+137
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"go.toolsManagement.autoUpdate": true,
|
||||
"go.useLanguageServer": true,
|
||||
"go.lintOnSave": "workspace",
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": [
|
||||
"--fast"
|
||||
],
|
||||
"go.formatTool": "goimports",
|
||||
"go.formatFlags": [],
|
||||
"go.buildOnSave": "workspace",
|
||||
"go.testOnSave": false,
|
||||
"go.coverOnSave": false,
|
||||
"go.testFlags": ["-v", "-race"],
|
||||
"go.testTimeout": "60s",
|
||||
"go.gopath": "",
|
||||
"go.goroot": "",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/node_modules": true,
|
||||
"**/*.test": true,
|
||||
"**/coverage.out": true,
|
||||
"**/coverage.html": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/.vscode/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true,
|
||||
"**/*.code-search": true,
|
||||
"**/vendor": true,
|
||||
"**/.git": true
|
||||
},
|
||||
"[go]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "golang.go"
|
||||
},
|
||||
"[go.mod]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "golang.go"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.formatOnSave": false,
|
||||
"editor.wordWrap": "on"
|
||||
},
|
||||
"gopls": {
|
||||
"ui.semanticTokens": true,
|
||||
"ui.completion.usePlaceholders": true,
|
||||
"formatting.gofumpt": false,
|
||||
"analyses": {
|
||||
"unusedparams": true,
|
||||
"shadow": true,
|
||||
"fieldalignment": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"golang.go",
|
||||
"editorconfig.editorconfig",
|
||||
"redhat.vscode-yaml",
|
||||
"ms-vscode.makefile-tools"
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Run Tests",
|
||||
"type": "shell",
|
||||
"command": "make test",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Tests with Coverage",
|
||||
"type": "shell",
|
||||
"command": "make test-coverage",
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"label": "Run Linter",
|
||||
"type": "shell",
|
||||
"command": "make lint",
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "Format Code",
|
||||
"type": "shell",
|
||||
"command": "make fmt",
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
},
|
||||
"launch": {
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Current File",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${file}"
|
||||
},
|
||||
{
|
||||
"name": "Debug Test",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "test",
|
||||
"program": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,10 @@ go test -v ./...
|
||||
|
||||
# Run specific test
|
||||
go test -run TestMyFunction ./pkg/...
|
||||
|
||||
# Optional: Use richgo for colored output
|
||||
go install github.com/kyoh86/richgo@latest
|
||||
richgo test -v ./...
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
@@ -108,6 +108,14 @@ curl -XPOST \
|
||||
http://localhost:8080
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Check out [/examples](examples/) for runnable code:
|
||||
- [hello-world](examples/hello-world/) - Basic RPC service
|
||||
- [web-service](examples/web-service/) - HTTP REST API
|
||||
|
||||
See [all examples](examples/README.md) for more.
|
||||
|
||||
## Protobuf
|
||||
|
||||
Install the code generator and see usage in the docs:
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We actively support the following versions of go-micro:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 5.x | :white_check_mark: |
|
||||
| 4.x | :x: |
|
||||
| 3.x | :x: |
|
||||
| < 3.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
### How to Report
|
||||
|
||||
Send security vulnerability reports to: **security@go-micro.dev**
|
||||
|
||||
Or use GitHub's private security advisory feature:
|
||||
https://github.com/micro/go-micro/security/advisories/new
|
||||
|
||||
### What to Include
|
||||
|
||||
Please include as much of the following information as possible:
|
||||
|
||||
- Type of vulnerability (e.g., RCE, XSS, SQL injection, etc.)
|
||||
- Full paths of source file(s) related to the vulnerability
|
||||
- Location of the affected source code (tag/branch/commit or direct URL)
|
||||
- Step-by-step instructions to reproduce the issue
|
||||
- Proof-of-concept or exploit code (if possible)
|
||||
- Impact of the issue, including how an attacker might exploit it
|
||||
|
||||
### Response Timeline
|
||||
|
||||
- **Acknowledgment**: Within 48 hours
|
||||
- **Initial Assessment**: Within 5 business days
|
||||
- **Fix Timeline**: Depends on severity
|
||||
- Critical: 7 days
|
||||
- High: 14 days
|
||||
- Medium: 30 days
|
||||
- Low: Next release cycle
|
||||
|
||||
### Disclosure Policy
|
||||
|
||||
- We follow **coordinated disclosure**
|
||||
- We'll work with you to understand and fix the issue
|
||||
- We'll credit you in the security advisory (unless you prefer to remain anonymous)
|
||||
- Please give us reasonable time to fix before public disclosure
|
||||
- We'll publish a security advisory on GitHub when the fix is released
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
When using go-micro in production:
|
||||
|
||||
### TLS/Transport Security
|
||||
|
||||
```go
|
||||
import "go-micro.dev/v5/transport"
|
||||
|
||||
// Enable TLS verification (recommended)
|
||||
os.Setenv("MICRO_TLS_SECURE", "true")
|
||||
|
||||
// Or use SecureConfig explicitly
|
||||
tlsConfig := transport.SecureConfig()
|
||||
```
|
||||
|
||||
See [TLS Security Update](internal/website/docs/TLS_SECURITY_UPDATE.md) for details.
|
||||
|
||||
### Authentication
|
||||
|
||||
```go
|
||||
import "go-micro.dev/v5/auth"
|
||||
|
||||
// Use JWT authentication
|
||||
service := micro.NewService(
|
||||
micro.Auth(auth.NewAuth()),
|
||||
)
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
|
||||
Always validate and sanitize inputs in your handlers:
|
||||
|
||||
```go
|
||||
func (h *Handler) Create(ctx context.Context, req *Request, rsp *Response) error {
|
||||
// Validate input
|
||||
if req.Name == "" {
|
||||
return errors.BadRequest("handler.create", "name is required")
|
||||
}
|
||||
|
||||
// Sanitize and process
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Implement rate limiting for public-facing services:
|
||||
|
||||
```go
|
||||
import "go-micro.dev/v5/client"
|
||||
|
||||
// Client-side rate limiting
|
||||
client.NewClient(
|
||||
client.RequestTimeout(time.Second * 5),
|
||||
client.Retries(3),
|
||||
)
|
||||
```
|
||||
|
||||
### Secrets Management
|
||||
|
||||
Never commit secrets to version control:
|
||||
|
||||
```go
|
||||
// Good: Use environment variables
|
||||
apiKey := os.Getenv("API_KEY")
|
||||
|
||||
// Better: Use a secrets manager
|
||||
import "github.com/hashicorp/vault/api"
|
||||
```
|
||||
|
||||
### Dependency Security
|
||||
|
||||
Regularly update dependencies:
|
||||
|
||||
```bash
|
||||
# Check for vulnerabilities
|
||||
go list -json -m all | nancy sleuth
|
||||
|
||||
# Update dependencies
|
||||
go get -u ./...
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
## Known Security Considerations
|
||||
|
||||
### Reflection Usage
|
||||
|
||||
go-micro uses reflection for automatic handler registration. While this is a deliberate design choice for developer productivity, be aware:
|
||||
|
||||
- Type safety is enforced at runtime, not compile time
|
||||
- Malformed requests won't crash services (errors are returned)
|
||||
- See [Performance Considerations](internal/website/docs/performance.md)
|
||||
|
||||
### TLS Certificate Verification
|
||||
|
||||
**Default behavior in v5**: TLS certificate verification is **disabled** for backward compatibility.
|
||||
|
||||
**Production recommendation**: Enable secure mode:
|
||||
|
||||
```bash
|
||||
export MICRO_TLS_SECURE=true
|
||||
```
|
||||
|
||||
This will be the default in v6.
|
||||
|
||||
## Security Updates
|
||||
|
||||
Security updates are published as:
|
||||
- GitHub Security Advisories
|
||||
- Release notes with `[SECURITY]` prefix
|
||||
- CVE entries for critical issues
|
||||
|
||||
Subscribe to releases: https://github.com/micro/go-micro/releases
|
||||
|
||||
## Bug Bounty
|
||||
|
||||
We currently do not offer a bug bounty program, but we greatly appreciate responsible disclosure and will publicly credit researchers who report valid security issues.
|
||||
|
||||
## Questions?
|
||||
|
||||
For security questions that are not vulnerabilities, please:
|
||||
- Open a discussion: https://github.com/micro/go-micro/discussions
|
||||
- Join Discord: https://discord.gg/jwTYuUVAGh
|
||||
- Email: support@go-micro.dev
|
||||
|
||||
+3
-1
@@ -108,7 +108,9 @@ func (m *mem) Consume(topic string, opts ...ConsumeOption) (<-chan Event, error)
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
// TODO RetryLimit
|
||||
|
||||
// Note: RetryLimit is configured but retry logic is basic for the in-memory implementation.
|
||||
// For production use with advanced retry capabilities, use NATS JetStream.
|
||||
|
||||
// setup the subscriber
|
||||
sub := &subscriber{
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# Go Micro Examples
|
||||
|
||||
This directory contains runnable examples demonstrating various go-micro features and patterns.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Each example can be run with `go run .` from its directory.
|
||||
|
||||
## Examples
|
||||
|
||||
### [hello-world](./hello-world/)
|
||||
Basic RPC service demonstrating core concepts:
|
||||
- Service creation and registration
|
||||
- Handler implementation
|
||||
- Client calls
|
||||
- Health checks
|
||||
|
||||
**Run it:**
|
||||
```bash
|
||||
cd hello-world
|
||||
go run .
|
||||
```
|
||||
|
||||
### [pubsub-events](./pubsub-events/)
|
||||
Event-driven architecture with NATS:
|
||||
- Publishing events
|
||||
- Subscribing to topics
|
||||
- Event handlers
|
||||
- Asynchronous processing
|
||||
|
||||
**Run it:**
|
||||
```bash
|
||||
cd pubsub-events
|
||||
go run publisher/main.go # Terminal 1
|
||||
go run subscriber/main.go # Terminal 2
|
||||
```
|
||||
|
||||
### [web-service](./web-service/)
|
||||
HTTP web service with service discovery:
|
||||
- HTTP handlers
|
||||
- Service registration
|
||||
- Health checks
|
||||
- Static file serving
|
||||
|
||||
**Run it:**
|
||||
```bash
|
||||
cd web-service
|
||||
go run .
|
||||
```
|
||||
|
||||
### [grpc-integration](./grpc-integration/)
|
||||
Using go-micro with gRPC:
|
||||
- Protocol buffer definitions
|
||||
- gRPC client/server
|
||||
- Code generation
|
||||
- Type-safe APIs
|
||||
|
||||
**Run it:**
|
||||
```bash
|
||||
cd grpc-integration
|
||||
make proto # Generate code
|
||||
go run server/main.go # Terminal 1
|
||||
go run client/main.go # Terminal 2
|
||||
```
|
||||
|
||||
### [production-ready](./production-ready/)
|
||||
Complete production-grade service:
|
||||
- Structured logging
|
||||
- Metrics and tracing
|
||||
- Health checks
|
||||
- Graceful shutdown
|
||||
- Configuration management
|
||||
- Error handling
|
||||
|
||||
**Run it:**
|
||||
```bash
|
||||
cd production-ready
|
||||
go run .
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Some examples require external dependencies:
|
||||
|
||||
- **NATS**: `docker run -p 4222:4222 nats:latest`
|
||||
- **Consul**: `docker run -p 8500:8500 consul:latest agent -dev -ui -client=0.0.0.0`
|
||||
- **Redis**: `docker run -p 6379:6379 redis:latest`
|
||||
|
||||
## Contributing
|
||||
|
||||
To add a new example:
|
||||
|
||||
1. Create a new directory
|
||||
2. Add a descriptive README.md
|
||||
3. Include working code with comments
|
||||
4. Add to this index
|
||||
5. Ensure it runs with `go run .`
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Hello World Example
|
||||
|
||||
The simplest go-micro service demonstrating core concepts.
|
||||
|
||||
## What It Does
|
||||
|
||||
This example creates a basic RPC service that:
|
||||
- Listens on port 8080
|
||||
- Exposes a `Greeter.Hello` method
|
||||
- Returns a greeting message
|
||||
- Demonstrates both programmatic and HTTP access
|
||||
|
||||
## Run It
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
The service will start and make test calls to itself, then wait for incoming requests.
|
||||
|
||||
## Test It
|
||||
|
||||
### Using curl
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080 \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Micro-Endpoint: Greeter.Hello' \
|
||||
-d '{"name": "Alice"}'
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{"message": "Hello Alice"}
|
||||
```
|
||||
|
||||
### Using the micro CLI
|
||||
|
||||
```bash
|
||||
micro call greeter Greeter.Hello '{"name": "Bob"}'
|
||||
```
|
||||
|
||||
## Code Walkthrough
|
||||
|
||||
1. **Define types** - Request and Response structures
|
||||
2. **Implement handler** - The `Greeter` service with `Hello` method
|
||||
3. **Create service** - Using `micro.New()` with options
|
||||
4. **Register handler** - Link the handler to the service
|
||||
5. **Run service** - Start listening for requests
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **RPC Pattern**: Method signature `func(ctx, req, rsp) error`
|
||||
- **Service Discovery**: Automatic registration
|
||||
- **Multiple Transports**: Works over HTTP, gRPC, etc.
|
||||
- **Type Safety**: Strongly typed requests/responses
|
||||
|
||||
## Next Steps
|
||||
|
||||
- See [pubsub-events](../pubsub-events/) for event-driven patterns
|
||||
- See [production-ready](../production-ready/) for a complete example
|
||||
- Read the [Getting Started Guide](../../internal/website/docs/getting-started.md)
|
||||
@@ -0,0 +1,7 @@
|
||||
module example
|
||||
|
||||
go 1.24
|
||||
|
||||
require go-micro.dev/v5 latest
|
||||
|
||||
replace go-micro.dev/v5 => ../..
|
||||
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go-micro.dev/v5"
|
||||
"go-micro.dev/v5/client"
|
||||
)
|
||||
|
||||
// Request and Response types
|
||||
type Request struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Greeter service handler
|
||||
type Greeter struct{}
|
||||
|
||||
// Hello is the RPC method handler
|
||||
func (g *Greeter) Hello(ctx context.Context, req *Request, rsp *Response) error {
|
||||
rsp.Message = "Hello " + req.Name
|
||||
log.Printf("Received request: %s", req.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a new service
|
||||
service := micro.New(
|
||||
micro.Name("greeter"),
|
||||
micro.Version("latest"),
|
||||
micro.Address(":8080"),
|
||||
)
|
||||
|
||||
// Initialize the service
|
||||
service.Init()
|
||||
|
||||
// Register the handler
|
||||
if err := service.Handle(new(Greeter)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Run the service in a goroutine
|
||||
go func() {
|
||||
if err := service.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for service to start
|
||||
fmt.Println("Service started on :8080")
|
||||
fmt.Println("Testing the service...")
|
||||
|
||||
// Create a client to test the service
|
||||
c := service.Client()
|
||||
|
||||
// Make a request
|
||||
req := c.NewRequest("greeter", "Greeter.Hello", &Request{Name: "World"})
|
||||
rsp := &Response{}
|
||||
|
||||
if err := c.Call(context.Background(), req, rsp); err != nil {
|
||||
log.Printf("Error calling service: %v", err)
|
||||
} else {
|
||||
fmt.Printf("Response: %s\n", rsp.Message)
|
||||
}
|
||||
|
||||
// Make another request
|
||||
req2 := c.NewRequest("greeter", "Greeter.Hello", &Request{Name: "Go Micro"})
|
||||
rsp2 := &Response{}
|
||||
|
||||
if err := c.Call(context.Background(), req2, rsp2); err != nil {
|
||||
log.Printf("Error calling service: %v", err)
|
||||
} else {
|
||||
fmt.Printf("Response: %s\n", rsp2.Message)
|
||||
}
|
||||
|
||||
// Test with HTTP client
|
||||
fmt.Println("\nYou can also test with curl:")
|
||||
fmt.Println("curl -X POST http://localhost:8080 \\")
|
||||
fmt.Println(" -H 'Content-Type: application/json' \\")
|
||||
fmt.Println(" -H 'Micro-Endpoint: Greeter.Hello' \\")
|
||||
fmt.Println(" -d '{\"name\": \"Alice\"}'")
|
||||
|
||||
// Keep service running
|
||||
select {}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# Web Service Example
|
||||
|
||||
HTTP web service with automatic service discovery and registration.
|
||||
|
||||
## What It Does
|
||||
|
||||
This example creates an HTTP service that:
|
||||
- Serves RESTful API endpoints
|
||||
- Registers with service discovery
|
||||
- Provides health checks
|
||||
- Uses standard Go HTTP handlers
|
||||
|
||||
## Run It
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## Test It
|
||||
|
||||
```bash
|
||||
# Get service info
|
||||
curl http://localhost:9090/
|
||||
|
||||
# List all users
|
||||
curl http://localhost:9090/users
|
||||
|
||||
# Get specific user
|
||||
curl http://localhost:9090/users/1
|
||||
|
||||
# Health check
|
||||
curl http://localhost:9090/health
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Standard HTTP**: Use familiar `http.Handler` interface
|
||||
- **Service Discovery**: Automatically registers with registry
|
||||
- **Health Checks**: Built-in health endpoint
|
||||
- **JSON APIs**: Easy REST API development
|
||||
|
||||
## When to Use
|
||||
|
||||
Use `web.Service` when:
|
||||
- Building REST APIs
|
||||
- Serving web UIs
|
||||
- Working with HTTP-specific features
|
||||
- Migrating existing HTTP services
|
||||
|
||||
Use regular `micro.Service` when:
|
||||
- Building RPC services
|
||||
- Need bidirectional streaming
|
||||
- Want automatic load balancing
|
||||
- Prefer structured RPC over HTTP
|
||||
|
||||
## Next Steps
|
||||
|
||||
- See [hello-world](../hello-world/) for RPC services
|
||||
- See [production-ready](../production-ready/) for observability
|
||||
@@ -0,0 +1,7 @@
|
||||
module example
|
||||
|
||||
go 1.24
|
||||
|
||||
require go-micro.dev/v5 latest
|
||||
|
||||
replace go-micro.dev/v5 => ../..
|
||||
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go-micro.dev/v5/web"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
var users = map[string]*User{
|
||||
"1": {ID: "1", Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now()},
|
||||
"2": {ID: "2", Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now()},
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a new web service
|
||||
service := web.NewService(
|
||||
web.Name("web.service"),
|
||||
web.Version("latest"),
|
||||
web.Address(":9090"),
|
||||
)
|
||||
|
||||
// Initialize
|
||||
service.Init()
|
||||
|
||||
// Register handlers
|
||||
service.HandleFunc("/", homeHandler)
|
||||
service.HandleFunc("/users", usersHandler)
|
||||
service.HandleFunc("/users/", userHandler)
|
||||
service.HandleFunc("/health", healthHandler)
|
||||
|
||||
fmt.Println("Web service starting on :9090")
|
||||
fmt.Println("Try:")
|
||||
fmt.Println(" curl http://localhost:9090/")
|
||||
fmt.Println(" curl http://localhost:9090/users")
|
||||
fmt.Println(" curl http://localhost:9090/users/1")
|
||||
fmt.Println(" curl http://localhost:9090/health")
|
||||
|
||||
// Run the service
|
||||
if err := service.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"service": "web.service",
|
||||
"version": "latest",
|
||||
"status": "running",
|
||||
})
|
||||
}
|
||||
|
||||
func usersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// Return all users
|
||||
userList := make([]*User, 0, len(users))
|
||||
for _, user := range users {
|
||||
userList = append(userList, user)
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(userList)
|
||||
}
|
||||
|
||||
func userHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// Extract user ID from path
|
||||
id := r.URL.Path[len("/users/"):]
|
||||
|
||||
user, exists := users[id]
|
||||
if !exists {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"error": "User not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "healthy",
|
||||
"timestamp": time.Now().Unix(),
|
||||
"uptime": "running",
|
||||
})
|
||||
}
|
||||
@@ -11,11 +11,11 @@ The `testing` package provides utilities for testing micro services in isolation
|
||||
```go
|
||||
import (
|
||||
"testing"
|
||||
microtesting "go-micro.dev/v5/testing"
|
||||
"go-micro.dev/v5/test"
|
||||
)
|
||||
|
||||
func TestGreeter(t *testing.T) {
|
||||
h := microtesting.NewHarness(t)
|
||||
h := test.NewHarness(t)
|
||||
defer h.Stop()
|
||||
|
||||
h.Name("greeter").Register(new(GreeterHandler))
|
||||
@@ -47,7 +47,7 @@ This allows your service to run without affecting or being affected by other ser
|
||||
### Creating a Harness
|
||||
|
||||
```go
|
||||
h := microtesting.NewHarness(t)
|
||||
h := test.NewHarness(t)
|
||||
defer h.Stop() // Always stop to clean up
|
||||
```
|
||||
|
||||
@@ -103,7 +103,7 @@ package users
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
microtesting "go-micro.dev/v5/testing"
|
||||
"go-micro.dev/v5/test"
|
||||
)
|
||||
|
||||
type UsersHandler struct {
|
||||
@@ -131,7 +131,7 @@ func (h *UsersHandler) Create(ctx context.Context, req *CreateRequest, rsp *Crea
|
||||
}
|
||||
|
||||
func TestUsersCreate(t *testing.T) {
|
||||
h := microtesting.NewHarness(t)
|
||||
h := test.NewHarness(t)
|
||||
defer h.Stop()
|
||||
|
||||
handler := &UsersHandler{users: make(map[string]*User)}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go-micro.dev/v5"
|
||||
"go-micro.dev/v5/broker"
|
||||
"go-micro.dev/v5/client"
|
||||
"go-micro.dev/v5/registry"
|
||||
"go-micro.dev/v5/server"
|
||||
"go-micro.dev/v5/transport"
|
||||
"go-micro.dev/v5/util/test"
|
||||
)
|
||||
|
||||
func BenchmarkService(b *testing.B) {
|
||||
cfg := ServiceTestConfig{
|
||||
Name: "test-service",
|
||||
NewService: newService,
|
||||
Parallel: []int{1, 8, 16, 32, 64},
|
||||
Sequential: []int{0},
|
||||
Streams: []int{0},
|
||||
// PubSub: []int{10},
|
||||
}
|
||||
|
||||
cfg.Run(b)
|
||||
}
|
||||
|
||||
func newService(name string, opts ...micro.Option) (micro.Service, error) {
|
||||
r := registry.NewMemoryRegistry(
|
||||
registry.Services(test.Data),
|
||||
)
|
||||
|
||||
b := broker.NewMemoryBroker()
|
||||
|
||||
t := transport.NewHTTPTransport()
|
||||
c := client.NewClient(
|
||||
client.Transport(t),
|
||||
client.Broker(b),
|
||||
)
|
||||
|
||||
s := server.NewRPCServer(
|
||||
server.Name(name),
|
||||
server.Registry(r),
|
||||
server.Transport(t),
|
||||
server.Broker(b),
|
||||
)
|
||||
|
||||
if err := s.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options := []micro.Option{
|
||||
micro.Name(name),
|
||||
micro.Server(s),
|
||||
micro.Client(c),
|
||||
micro.Registry(r),
|
||||
micro.Broker(b),
|
||||
}
|
||||
options = append(options, opts...)
|
||||
|
||||
srv := micro.NewService(options...)
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
-392
@@ -1,392 +0,0 @@
|
||||
// Package test implements a testing framwork, and provides default tests.
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"go-micro.dev/v5"
|
||||
"go-micro.dev/v5/client"
|
||||
"go-micro.dev/v5/debug/handler"
|
||||
|
||||
pb "go-micro.dev/v5/debug/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoTests returns no test params are set.
|
||||
ErrNoTests = errors.New("No tests to run, all values set to 0")
|
||||
testTopic = "Test-Topic"
|
||||
errorTopic = "Error-Topic"
|
||||
)
|
||||
|
||||
type parTest func(name string, c client.Client, p, s int, errChan chan error)
|
||||
type testFunc func(name string, c client.Client, errChan chan error)
|
||||
|
||||
// ServiceTestConfig allows you to easily test a service configuration by
|
||||
// running predefined tests against your custom service. You only need to
|
||||
// provide a function to create the service, and how many of which test you
|
||||
// want to run.
|
||||
//
|
||||
// The default tests provided, all running with separate parallel routines are:
|
||||
// - Sequential Call requests
|
||||
// - Bi-directional streaming
|
||||
// - Pub/Sub events brokering
|
||||
//
|
||||
// You can provide an array of parallel routines to run for the request and
|
||||
// stream tests. They will be run as matrix tests, so with each possible combination.
|
||||
// Thus, in total (p * seq) + (p * streams) tests will be run.
|
||||
type ServiceTestConfig struct {
|
||||
// Service name to use for the tests
|
||||
Name string
|
||||
// NewService function will be called to setup the new service.
|
||||
// It takes in a list of options, which by default will Context and an
|
||||
// AfterStart with channel to signal when the service has been started.
|
||||
NewService func(name string, opts ...micro.Option) (micro.Service, error)
|
||||
// Parallel is the number of prallell routines to use for the tests.
|
||||
Parallel []int
|
||||
// Sequential is the number of sequential requests to send per parallel process.
|
||||
Sequential []int
|
||||
// Streams is the nummber of streaming messages to send over the stream per routine.
|
||||
Streams []int
|
||||
// PubSub is the number of times to publish messages to the broker per routine.
|
||||
PubSub []int
|
||||
|
||||
mu sync.Mutex
|
||||
msgCount int
|
||||
}
|
||||
|
||||
// Run will start the benchmark tests.
|
||||
func (stc *ServiceTestConfig) Run(b *testing.B) {
|
||||
if err := stc.validate(); err != nil {
|
||||
b.Fatal("Failed to validate config", err)
|
||||
}
|
||||
|
||||
// Run routines with sequential requests
|
||||
stc.prepBench(b, "req", stc.runParSeqTest, stc.Sequential)
|
||||
|
||||
// Run routines with streams
|
||||
stc.prepBench(b, "streams", stc.runParStreamTest, stc.Streams)
|
||||
|
||||
// Run routines with pub/sub
|
||||
stc.prepBench(b, "pubsub", stc.runBrokerTest, stc.PubSub)
|
||||
}
|
||||
|
||||
// prepBench will prepare the benmark by setting the right parameters,
|
||||
// and invoking the test.
|
||||
func (stc *ServiceTestConfig) prepBench(b *testing.B, tName string, test parTest, seq []int) {
|
||||
par := stc.Parallel
|
||||
|
||||
// No requests needed
|
||||
if len(seq) == 0 || seq[0] == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, parallel := range par {
|
||||
for _, sequential := range seq {
|
||||
// Create the service name for the test
|
||||
name := fmt.Sprintf("%s.%dp-%d%s", stc.Name, parallel, sequential, tName)
|
||||
|
||||
// Run test with parallel routines making each sequential requests
|
||||
test := func(name string, c client.Client, errChan chan error) {
|
||||
test(name, c, parallel, sequential, errChan)
|
||||
}
|
||||
|
||||
benchmark := func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
stc.runBench(b, name, test)
|
||||
}
|
||||
|
||||
b.Logf("----------- STARTING TEST %s -----------", name)
|
||||
|
||||
// Run test, return if it fails
|
||||
if !b.Run(name, benchmark) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runParSeqTest will make s sequential requests in p parallel routines.
|
||||
func (stc *ServiceTestConfig) runParSeqTest(name string, c client.Client, p, s int, errChan chan error) {
|
||||
testParallel(p, func() {
|
||||
// Make serial requests
|
||||
for z := 0; z < s; z++ {
|
||||
if err := testRequest(context.Background(), c, name); err != nil {
|
||||
errChan <- errors.Wrapf(err, "[%s] Request failed during testRequest", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Handle is used as a test handler.
|
||||
func (stc *ServiceTestConfig) Handle(ctx context.Context, msg *pb.HealthRequest) error {
|
||||
stc.mu.Lock()
|
||||
stc.msgCount++
|
||||
stc.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleError is used as a test handler.
|
||||
func (stc *ServiceTestConfig) HandleError(ctx context.Context, msg *pb.HealthRequest) error {
|
||||
return errors.New("dummy error")
|
||||
}
|
||||
|
||||
// runBrokerTest will publish messages to the broker to test pub/sub.
|
||||
func (stc *ServiceTestConfig) runBrokerTest(name string, c client.Client, p, s int, errChan chan error) {
|
||||
stc.msgCount = 0
|
||||
|
||||
testParallel(p, func() {
|
||||
for z := 0; z < s; z++ {
|
||||
msg := pb.BusMsg{Msg: "Hello from broker!"}
|
||||
if err := c.Publish(context.Background(), c.NewMessage(testTopic, &msg)); err != nil {
|
||||
errChan <- errors.Wrap(err, "failed to publish message to broker")
|
||||
return
|
||||
}
|
||||
|
||||
msg = pb.BusMsg{Msg: "Some message that will error"}
|
||||
if err := c.Publish(context.Background(), c.NewMessage(errorTopic, &msg)); err == nil {
|
||||
errChan <- errors.New("Publish is supposed to return an error, but got no error")
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if stc.msgCount != s*p {
|
||||
errChan <- fmt.Errorf("pub/sub does not work properly, invalid message count. Expected %d messaged, but received %d", s*p, stc.msgCount)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// runParStreamTest will start streaming, and send s messages parallel in p routines.
|
||||
func (stc *ServiceTestConfig) runParStreamTest(name string, c client.Client, p, s int, errChan chan error) {
|
||||
testParallel(p, func() {
|
||||
// Create a client service
|
||||
srv := pb.NewDebugService(name, c)
|
||||
|
||||
// Establish a connection to server over which we start streaming
|
||||
bus, err := srv.MessageBus(context.Background())
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "failed to connect to message bus")
|
||||
return
|
||||
}
|
||||
|
||||
// Start streaming requests
|
||||
for z := 0; z < s; z++ {
|
||||
if err := bus.Send(&pb.BusMsg{Msg: "Hack the world!"}); err != nil {
|
||||
errChan <- errors.Wrap(err, "failed to send to stream")
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := bus.Recv()
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "failed to receive message from stream")
|
||||
return
|
||||
}
|
||||
|
||||
expected := "Request received!"
|
||||
if msg.Msg != expected {
|
||||
errChan <- fmt.Errorf("stream returned unexpected mesage. Expected '%s', but got '%s'", expected, msg.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validate will make sure the provided test parameters are a legal combination.
|
||||
func (stc *ServiceTestConfig) validate() error {
|
||||
lp, lseq, lstr := len(stc.Parallel), len(stc.Sequential), len(stc.Streams)
|
||||
|
||||
if lp == 0 || (lseq == 0 && lstr == 0) {
|
||||
return ErrNoTests
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runBench will create a service with the provided stc.NewService function,
|
||||
// and run a benchmark on the test function.
|
||||
func (stc *ServiceTestConfig) runBench(b *testing.B, name string, test testFunc) {
|
||||
b.StopTimer()
|
||||
|
||||
// Channel to signal service has started
|
||||
started := make(chan struct{})
|
||||
|
||||
// Context with cancel to stop the service
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
opts := []micro.Option{
|
||||
micro.Context(ctx),
|
||||
micro.AfterStart(func() error {
|
||||
started <- struct{}{}
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
// Create a new service per test
|
||||
service, err := stc.NewService(name, opts...)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create service: %v", err)
|
||||
}
|
||||
|
||||
// Register handler
|
||||
if err := pb.RegisterDebugHandler(service.Server(), handler.NewHandler(service.Client())); err != nil {
|
||||
b.Fatalf("failed to register handler during initial service setup: %v", err)
|
||||
}
|
||||
|
||||
o := service.Options()
|
||||
if err := o.Broker.Connect(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
// a := new(testService)
|
||||
if err := o.Server.Subscribe(o.Server.NewSubscriber(testTopic, stc.Handle)); err != nil {
|
||||
b.Fatalf("[%s] Failed to register subscriber: %v", name, err)
|
||||
}
|
||||
|
||||
if err := o.Server.Subscribe(o.Server.NewSubscriber(errorTopic, stc.HandleError)); err != nil {
|
||||
b.Fatalf("[%s] Failed to register subscriber: %v", name, err)
|
||||
}
|
||||
|
||||
b.Logf("# == [ Service ] ==================")
|
||||
b.Logf("# * Server: %s", o.Server.String())
|
||||
b.Logf("# * Client: %s", o.Client.String())
|
||||
b.Logf("# * Transport: %s", o.Transport.String())
|
||||
b.Logf("# * Broker: %s", o.Broker.String())
|
||||
b.Logf("# * Registry: %s", o.Registry.String())
|
||||
b.Logf("# * Auth: %s", o.Auth.String())
|
||||
b.Logf("# * Cache: %s", o.Cache.String())
|
||||
b.Logf("# ================================")
|
||||
|
||||
RunBenchmark(b, name, service, test, cancel, started)
|
||||
}
|
||||
|
||||
// RunBenchmark will run benchmarks on a provided service.
|
||||
//
|
||||
// A test function can be provided that will be fun b.N times.
|
||||
func RunBenchmark(b *testing.B, name string, service micro.Service, test testFunc,
|
||||
cancel context.CancelFunc, started chan struct{}) {
|
||||
b.StopTimer()
|
||||
|
||||
// Receive errors from routines on this channel
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
// Receive singal after service has shutdown
|
||||
done := make(chan struct{})
|
||||
|
||||
// Start the server
|
||||
go func() {
|
||||
b.Logf("[%s] Starting server for benchmark", name)
|
||||
|
||||
if err := service.Run(); err != nil {
|
||||
errChan <- errors.Wrapf(err, "[%s] Error occurred during service.Run", name)
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
sigTerm := make(chan struct{})
|
||||
|
||||
// Benchmark routine
|
||||
go func() {
|
||||
defer func() {
|
||||
b.StopTimer()
|
||||
|
||||
// Shutdown service
|
||||
b.Logf("[%s] Shutting down", name)
|
||||
cancel()
|
||||
|
||||
// Wait for service to be fully stopped
|
||||
<-done
|
||||
sigTerm <- struct{}{}
|
||||
}()
|
||||
|
||||
// Wait for service to start
|
||||
<-started
|
||||
|
||||
// Give the registry more time to setup
|
||||
time.Sleep(time.Second)
|
||||
|
||||
b.Logf("[%s] Server started", name)
|
||||
|
||||
// Make a test call to warm the cache
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := testRequest(context.Background(), service.Client(), name); err != nil {
|
||||
errChan <- errors.Wrapf(err, "[%s] Failure during cache warmup testRequest", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Check registration
|
||||
services, err := service.Options().Registry.GetService(name)
|
||||
if err != nil || len(services) == 0 {
|
||||
errChan <- fmt.Errorf("service registration must have failed (%d services found), unable to get service: %w", len(services), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Start benchmark
|
||||
b.Logf("[%s] Starting benchtest", name)
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
|
||||
// Number of iterations
|
||||
for i := 0; i < b.N; i++ {
|
||||
test(name, service.Client(), errChan)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for completion or catch any errors
|
||||
select {
|
||||
case err := <-errChan:
|
||||
b.Fatal(err)
|
||||
case <-sigTerm:
|
||||
b.Logf("[%s] Completed benchmark", name)
|
||||
}
|
||||
}
|
||||
|
||||
// testParallel will run the test function in p parallel routines.
|
||||
func testParallel(p int, test func()) {
|
||||
// Waitgroup to wait for requests to finish
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// For concurrency
|
||||
for j := 0; j < p; j++ {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
test()
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for test completion
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// testRequest sends one test request.
|
||||
// It calls the Debug.Health endpoint, and validates if the response returned
|
||||
// contains the expected message.
|
||||
func testRequest(ctx context.Context, c client.Client, name string) error {
|
||||
req := c.NewRequest(
|
||||
name,
|
||||
"Debug.Health",
|
||||
new(pb.HealthRequest),
|
||||
)
|
||||
|
||||
rsp := new(pb.HealthResponse)
|
||||
|
||||
if err := c.Call(ctx, req, rsp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rsp.Status != "ok" {
|
||||
return errors.New("service response: " + rsp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package testing provides utilities for testing micro services.
|
||||
// Package test provides utilities for testing micro services.
|
||||
//
|
||||
// Due to go-micro's global defaults, running multiple services in one process
|
||||
// requires careful isolation. This package provides helpers for the common case
|
||||
@@ -7,7 +7,7 @@
|
||||
// Basic usage:
|
||||
//
|
||||
// func TestUserService(t *testing.T) {
|
||||
// h := testing.NewHarness(t)
|
||||
// h := test.NewHarness(t)
|
||||
// defer h.Stop()
|
||||
//
|
||||
// // Register your service handler
|
||||
@@ -23,7 +23,7 @@
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
package testing
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package testing
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -29,14 +29,13 @@ func (h *httpTransportListener) Close() error {
|
||||
|
||||
func (h *httpTransportListener) Accept(fn func(Socket)) error {
|
||||
// Create handler mux
|
||||
// TODO: see if we should make a plugin out of the mux
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Register our transport handler
|
||||
mux.HandleFunc("/", h.newHandler(fn))
|
||||
|
||||
// Get optional handlers
|
||||
// TODO: This needs to be documented clearer, and examples provided
|
||||
// Get optional handlers from context.
|
||||
// See examples/web-service for usage.
|
||||
if h.ht.opts.Context != nil {
|
||||
handlers, ok := h.ht.opts.Context.Value("http_handlers").(map[string]http.Handler)
|
||||
if ok {
|
||||
|
||||
@@ -39,9 +39,8 @@ type Options struct {
|
||||
}
|
||||
|
||||
type DialOptions struct {
|
||||
|
||||
// TODO: add tls options when dialing
|
||||
// Currently set in global options
|
||||
// TLS options can be set via global transport options or Context.
|
||||
// See SECURITY.md for TLS configuration best practices.
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
@@ -58,8 +57,8 @@ type DialOptions struct {
|
||||
}
|
||||
|
||||
type ListenOptions struct {
|
||||
// TODO: add tls options when listening
|
||||
// Currently set in global options
|
||||
// TLS options can be set via global transport options or Context.
|
||||
// See SECURITY.md for TLS configuration best practices.
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
|
||||
Reference in New Issue
Block a user