mirror of
https://github.com/go-micro/go-micro.git
synced 2025-11-23 21:44:41 +02:00
move micro cli and protoc-gen-micro to cmd/
This commit is contained in:
@@ -30,11 +30,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type rpcClient struct {
|
type rpcClient struct {
|
||||||
seq uint64
|
seq uint64
|
||||||
opts Options
|
opts Options
|
||||||
once atomic.Value
|
once atomic.Value
|
||||||
pool pool.Pool
|
pool pool.Pool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRPCClient(opt ...Option) Client {
|
func newRPCClient(opt ...Option) Client {
|
||||||
|
|||||||
212
cmd/README.md
Normal file
212
cmd/README.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# Micro
|
||||||
|
|
||||||
|
Go Micro Command Line
|
||||||
|
|
||||||
|
## Install the CLI
|
||||||
|
|
||||||
|
Install `micro` via `go install`
|
||||||
|
|
||||||
|
```
|
||||||
|
go install go-micro.dev/v5/cmd/micro@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via install script
|
||||||
|
|
||||||
|
|
||||||
|
## Create a service
|
||||||
|
|
||||||
|
Create your service (all setup is now automatic!):
|
||||||
|
|
||||||
|
```
|
||||||
|
micro new helloworld
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Create a new service in the `helloworld` directory
|
||||||
|
- Automatically run `go mod tidy` and `make proto` for you
|
||||||
|
- Show the updated project tree including generated files
|
||||||
|
- Warn you if `protoc` is not installed, with install instructions
|
||||||
|
|
||||||
|
## Run the service
|
||||||
|
|
||||||
|
Run the service
|
||||||
|
|
||||||
|
```
|
||||||
|
micro run
|
||||||
|
```
|
||||||
|
|
||||||
|
List services to see it's running and registered itself
|
||||||
|
|
||||||
|
```
|
||||||
|
micro services
|
||||||
|
```
|
||||||
|
|
||||||
|
## Describe the service
|
||||||
|
|
||||||
|
Describe the service to see available endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
micro describe helloworld
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"name": "helloworld",
|
||||||
|
"version": "latest",
|
||||||
|
"metadata": null,
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"name": "Request",
|
||||||
|
"type": "Request",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"values": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"name": "Response",
|
||||||
|
"type": "Response",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "msg",
|
||||||
|
"type": "string",
|
||||||
|
"values": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"name": "Helloworld.Call"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"name": "Context",
|
||||||
|
"type": "Context",
|
||||||
|
"values": null
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"name": "Stream",
|
||||||
|
"type": "Stream",
|
||||||
|
"values": null
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"stream": "true"
|
||||||
|
},
|
||||||
|
"name": "Helloworld.Stream"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"broker": "http",
|
||||||
|
"protocol": "mucp",
|
||||||
|
"registry": "mdns",
|
||||||
|
"server": "mucp",
|
||||||
|
"transport": "http"
|
||||||
|
},
|
||||||
|
"id": "helloworld-31e55be7-ac83-4810-89c8-a6192fb3ae83",
|
||||||
|
"address": "127.0.0.1:39963"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Call the service
|
||||||
|
|
||||||
|
Call via RPC endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
micro call helloworld Helloworld.Call '{"name": "Asim"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a client
|
||||||
|
|
||||||
|
Create a client to call the service
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go-micro.dev/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := micro.New("helloworld").Client()
|
||||||
|
|
||||||
|
req := client.NewRequest("helloworld", "Helloworld.Call", &Request{Name: "John"})
|
||||||
|
|
||||||
|
var rsp Response
|
||||||
|
|
||||||
|
err := client.Call(context.TODO(), req, &rsp)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(rsp.Message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protobuf
|
||||||
|
|
||||||
|
Use protobuf for code generation with [protoc-gen-micro](https://github.com/micro/go-micro/tree/master/cmd/protoc-gen-micro)
|
||||||
|
|
||||||
|
## Server
|
||||||
|
|
||||||
|
The micro server is an api and web dashboard that provide a fixed entrypoint for seeing and querying services.
|
||||||
|
|
||||||
|
Run it like so
|
||||||
|
|
||||||
|
```
|
||||||
|
micro server
|
||||||
|
```
|
||||||
|
|
||||||
|
Then browse to [localhost:8080](http://localhost:8080)
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
The API provides a fixed HTTP entrypoint for calling services
|
||||||
|
|
||||||
|
```
|
||||||
|
curl http://localhost:8080/api/helloworld/Helloworld/Call -d '{"name": "John"}'
|
||||||
|
```
|
||||||
|
See /api for more details and documentation for each service
|
||||||
|
|
||||||
|
### Web Dashboard
|
||||||
|
|
||||||
|
The web dashboard provides a modern, secure UI for managing and exploring your Micro services. Major features include:
|
||||||
|
|
||||||
|
- **Dynamic Service & Endpoint Forms**: Browse all registered services and endpoints. For each endpoint, a dynamic form is generated for easy testing and exploration.
|
||||||
|
- **API Documentation**: The `/api` page lists all available services and endpoints, with request/response schemas and a sidebar for quick navigation. A documentation banner explains authentication requirements.
|
||||||
|
- **JWT Authentication**: All login and token management uses a custom JWT utility. Passwords are securely stored with bcrypt. All `/api/x` endpoints and authenticated pages require an `Authorization: Bearer <token>` header (or `micro_token` cookie as fallback).
|
||||||
|
- **Token Management**: The `/auth/tokens` page allows you to generate, view (obfuscated), and copy JWT tokens. Tokens are stored and can be revoked. When a user is deleted, all their tokens are revoked immediately.
|
||||||
|
- **User Management**: The `/auth/users` page allows you to create, list, and delete users. Passwords are never shown or stored in plaintext.
|
||||||
|
- **Token Revocation**: JWT tokens are stored and checked for revocation on every request. Revoked or deleted tokens are immediately invalidated.
|
||||||
|
- **Security**: All protected endpoints use consistent authentication logic. Unauthorized or revoked tokens receive a 401 error. All sensitive actions require authentication.
|
||||||
|
- **Logs & Status**: View service logs and status (PID, uptime, etc) directly from the dashboard.
|
||||||
|
|
||||||
|
To get started, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
micro server
|
||||||
|
```
|
||||||
|
|
||||||
|
Then browse to [localhost:8080](http://localhost:8080) and log in with the default admin account (`admin`/`micro`).
|
||||||
|
|
||||||
|
> **Note:** See the `/api` page for details on API authentication and how to generate tokens for use with the HTTP API
|
||||||
14
cmd/cmd.go
14
cmd/cmd.go
@@ -4,12 +4,16 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sort"
|
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"go-micro.dev/v5/auth"
|
||||||
|
"go-micro.dev/v5/broker"
|
||||||
|
nbroker "go-micro.dev/v5/broker/nats"
|
||||||
|
rabbit "go-micro.dev/v5/broker/rabbitmq"
|
||||||
"go-micro.dev/v5/cache"
|
"go-micro.dev/v5/cache"
|
||||||
"go-micro.dev/v5/cache/redis"
|
"go-micro.dev/v5/cache/redis"
|
||||||
"go-micro.dev/v5/client"
|
"go-micro.dev/v5/client"
|
||||||
@@ -19,15 +23,11 @@ import (
|
|||||||
"go-micro.dev/v5/debug/profile/pprof"
|
"go-micro.dev/v5/debug/profile/pprof"
|
||||||
"go-micro.dev/v5/debug/trace"
|
"go-micro.dev/v5/debug/trace"
|
||||||
"go-micro.dev/v5/events"
|
"go-micro.dev/v5/events"
|
||||||
"go-micro.dev/v5/logger"
|
|
||||||
mprofile "go-micro.dev/v5/profile"
|
|
||||||
"go-micro.dev/v5/auth"
|
|
||||||
"go-micro.dev/v5/broker"
|
|
||||||
nbroker "go-micro.dev/v5/broker/nats"
|
|
||||||
rabbit "go-micro.dev/v5/broker/rabbitmq"
|
|
||||||
"go-micro.dev/v5/genai"
|
"go-micro.dev/v5/genai"
|
||||||
"go-micro.dev/v5/genai/gemini"
|
"go-micro.dev/v5/genai/gemini"
|
||||||
"go-micro.dev/v5/genai/openai"
|
"go-micro.dev/v5/genai/openai"
|
||||||
|
"go-micro.dev/v5/logger"
|
||||||
|
mprofile "go-micro.dev/v5/profile"
|
||||||
"go-micro.dev/v5/registry"
|
"go-micro.dev/v5/registry"
|
||||||
"go-micro.dev/v5/registry/consul"
|
"go-micro.dev/v5/registry/consul"
|
||||||
"go-micro.dev/v5/registry/etcd"
|
"go-micro.dev/v5/registry/etcd"
|
||||||
|
|||||||
223
cmd/micro/cli/README.md
Normal file
223
cmd/micro/cli/README.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# Micro [](https://opensource.org/licenses/Apache-2.0)
|
||||||
|
|
||||||
|
A Go microservices toolkit
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Micro is a toolkit for Go microservices development. It provides the foundation for building services in the cloud.
|
||||||
|
The core of Micro is the [Go Micro](https://github.com/micro/go-micro) framework, which developers import and use in their code to
|
||||||
|
write services. Surrounding this we introduce a number of tools to make it easy to serve and consume services.
|
||||||
|
|
||||||
|
## Install the CLI
|
||||||
|
|
||||||
|
Install `micro` via `go install`
|
||||||
|
|
||||||
|
```
|
||||||
|
go install go-micro.dev/v5@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via install script
|
||||||
|
|
||||||
|
```
|
||||||
|
wget -q https://raw.githubusercontent.com/micro/micro/master/scripts/install.sh -O - | /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
For releases see the [latest](https://go-micro.dev/releases/latest) tag
|
||||||
|
|
||||||
|
## Create a service
|
||||||
|
|
||||||
|
Create your service (all setup is now automatic!):
|
||||||
|
|
||||||
|
```
|
||||||
|
micro new helloworld
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Create a new service in the `helloworld` directory
|
||||||
|
- Automatically run `go mod tidy` and `make proto` for you
|
||||||
|
- Show the updated project tree including generated files
|
||||||
|
- Warn you if `protoc` is not installed, with install instructions
|
||||||
|
|
||||||
|
## Run the service
|
||||||
|
|
||||||
|
Run the service
|
||||||
|
|
||||||
|
```
|
||||||
|
micro run
|
||||||
|
```
|
||||||
|
|
||||||
|
List services to see it's running and registered itself
|
||||||
|
|
||||||
|
```
|
||||||
|
micro services
|
||||||
|
```
|
||||||
|
|
||||||
|
## Describe the service
|
||||||
|
|
||||||
|
Describe the service to see available endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
micro describe helloworld
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"name": "helloworld",
|
||||||
|
"version": "latest",
|
||||||
|
"metadata": null,
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"name": "Request",
|
||||||
|
"type": "Request",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"values": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"name": "Response",
|
||||||
|
"type": "Response",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "msg",
|
||||||
|
"type": "string",
|
||||||
|
"values": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"name": "Helloworld.Call"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"name": "Context",
|
||||||
|
"type": "Context",
|
||||||
|
"values": null
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"name": "Stream",
|
||||||
|
"type": "Stream",
|
||||||
|
"values": null
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"stream": "true"
|
||||||
|
},
|
||||||
|
"name": "Helloworld.Stream"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"broker": "http",
|
||||||
|
"protocol": "mucp",
|
||||||
|
"registry": "mdns",
|
||||||
|
"server": "mucp",
|
||||||
|
"transport": "http"
|
||||||
|
},
|
||||||
|
"id": "helloworld-31e55be7-ac83-4810-89c8-a6192fb3ae83",
|
||||||
|
"address": "127.0.0.1:39963"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Call the service
|
||||||
|
|
||||||
|
Call via RPC endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
micro call helloworld Helloworld.Call '{"name": "Asim"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a client
|
||||||
|
|
||||||
|
Create a client to call the service
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go-micro.dev/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := micro.New("helloworld").Client()
|
||||||
|
|
||||||
|
req := client.NewRequest("helloworld", "Helloworld.Call", &Request{Name: "John"})
|
||||||
|
|
||||||
|
var rsp Response
|
||||||
|
|
||||||
|
err := client.Call(context.TODO(), req, &rsp)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(rsp.Message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protobuf
|
||||||
|
|
||||||
|
Use protobuf for code generation with [protoc-gen-micro](https://go-micro.dev/tree/master/cmd/protoc-gen-micro)
|
||||||
|
|
||||||
|
## Server
|
||||||
|
|
||||||
|
The micro server is an api and web dashboard that provide a fixed entrypoint for seeing and querying services.
|
||||||
|
|
||||||
|
Run it like so
|
||||||
|
|
||||||
|
```
|
||||||
|
micro server
|
||||||
|
```
|
||||||
|
|
||||||
|
Then browse to [localhost:8080](http://localhost:8080)
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
The API provides a fixed HTTP entrypoint for calling services
|
||||||
|
|
||||||
|
```
|
||||||
|
curl http://localhost:8080/api/helloworld/Helloworld/Call -d '{"name": "John"}'
|
||||||
|
```
|
||||||
|
See /api for more details and documentation for each service
|
||||||
|
|
||||||
|
### Web Dashboard
|
||||||
|
|
||||||
|
The web dashboard provides a modern, secure UI for managing and exploring your Micro services. Major features include:
|
||||||
|
|
||||||
|
- **Dynamic Service & Endpoint Forms**: Browse all registered services and endpoints. For each endpoint, a dynamic form is generated for easy testing and exploration.
|
||||||
|
- **API Documentation**: The `/api` page lists all available services and endpoints, with request/response schemas and a sidebar for quick navigation. A documentation banner explains authentication requirements.
|
||||||
|
- **JWT Authentication**: All login and token management uses a custom JWT utility. Passwords are securely stored with bcrypt. All `/api/x` endpoints and authenticated pages require an `Authorization: Bearer <token>` header (or `micro_token` cookie as fallback).
|
||||||
|
- **Token Management**: The `/auth/tokens` page allows you to generate, view (obfuscated), and copy JWT tokens. Tokens are stored and can be revoked. When a user is deleted, all their tokens are revoked immediately.
|
||||||
|
- **User Management**: The `/auth/users` page allows you to create, list, and delete users. Passwords are never shown or stored in plaintext.
|
||||||
|
- **Token Revocation**: JWT tokens are stored and checked for revocation on every request. Revoked or deleted tokens are immediately invalidated.
|
||||||
|
- **Security**: All protected endpoints use consistent authentication logic. Unauthorized or revoked tokens receive a 401 error. All sensitive actions require authentication.
|
||||||
|
- **Logs & Status**: View service logs and status (PID, uptime, etc) directly from the dashboard.
|
||||||
|
|
||||||
|
To get started, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
micro server
|
||||||
|
```
|
||||||
|
|
||||||
|
Then browse to [localhost:8080](http://localhost:8080) and log in with the default admin account (`admin`/`micro`).
|
||||||
|
|
||||||
|
> **Note:** See the `/api` page for details on API authentication and how to generate tokens for use with the HTTP API
|
||||||
369
cmd/micro/cli/cli.go
Normal file
369
cmd/micro/cli/cli.go
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
package microcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go-micro.dev/v5/client"
|
||||||
|
"go-micro.dev/v5/cmd"
|
||||||
|
"go-micro.dev/v5/codec/bytes"
|
||||||
|
"go-micro.dev/v5/genai"
|
||||||
|
"go-micro.dev/v5/registry"
|
||||||
|
|
||||||
|
"go-micro.dev/v5/cmd/micro/cli/new"
|
||||||
|
"go-micro.dev/v5/cmd/micro/cli/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// version is set by the release action
|
||||||
|
// this is the default for local builds
|
||||||
|
version = "5.0.0-dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
func genProtoHandler(c *cli.Context) error {
|
||||||
|
cmd := exec.Command("find", ".", "-name", "*.proto", "-exec", "protoc", "--proto_path=.", "--micro_out=.", "--go_out=.", `{}`, `;`)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTextHandler(c *cli.Context) error {
|
||||||
|
prompt := c.String("prompt")
|
||||||
|
if len(prompt) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gen := genai.DefaultGenAI
|
||||||
|
if gen.String() == "noop" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := gen.Generate(prompt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(res.Text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastNonEmptyLine(s string) string {
|
||||||
|
lines := strings.Split(s, "\n")
|
||||||
|
for i := len(lines) - 1; i >= 0; i-- {
|
||||||
|
if strings.TrimSpace(lines[i]) != "" {
|
||||||
|
return lines[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastLogLine(path string) string {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
var last string
|
||||||
|
scan := bufio.NewScanner(f)
|
||||||
|
for scan.Scan() {
|
||||||
|
if strings.TrimSpace(scan.Text()) != "" {
|
||||||
|
last = scan.Text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitAndCleanup(procs []*exec.Cmd, pidFiles []string) {
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(ch, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-ch
|
||||||
|
for _, proc := range procs {
|
||||||
|
if proc.Process != nil {
|
||||||
|
_ = proc.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pf := range pidFiles {
|
||||||
|
_ = os.Remove(pf)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}()
|
||||||
|
for i, proc := range procs {
|
||||||
|
_ = proc.Wait()
|
||||||
|
if proc.Process != nil {
|
||||||
|
_ = os.Remove(pidFiles[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd.Register([]*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "new",
|
||||||
|
Usage: "Create a new service",
|
||||||
|
Action: new.Run,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "gen",
|
||||||
|
Usage: "Generate various things",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "text",
|
||||||
|
Usage: "Generate text via an LLM",
|
||||||
|
Action: genTextHandler,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "prompt",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "The prompt to generate text from",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "proto",
|
||||||
|
Usage: "Generate proto requires protoc and protoc-gen-micro",
|
||||||
|
Action: genProtoHandler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "services",
|
||||||
|
Usage: "List available services",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
services, err := registry.ListServices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, service := range services {
|
||||||
|
fmt.Println(service.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "call",
|
||||||
|
Usage: "Call a service",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
args := ctx.Args()
|
||||||
|
|
||||||
|
if args.Len() < 2 {
|
||||||
|
return fmt.Errorf("Usage: [service] [endpoint] [request]")
|
||||||
|
}
|
||||||
|
|
||||||
|
service := args.Get(0)
|
||||||
|
endpoint := args.Get(1)
|
||||||
|
request := `{}`
|
||||||
|
|
||||||
|
if args.Len() == 3 {
|
||||||
|
request = args.Get(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := client.NewRequest(service, endpoint, &bytes.Frame{Data: []byte(request)})
|
||||||
|
var rsp bytes.Frame
|
||||||
|
err := client.Call(context.TODO(), req, &rsp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(string(rsp.Data))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "describe",
|
||||||
|
Usage: "Describe a service",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
args := ctx.Args()
|
||||||
|
|
||||||
|
if args.Len() != 1 {
|
||||||
|
return fmt.Errorf("Usage: [service]")
|
||||||
|
}
|
||||||
|
|
||||||
|
service := args.Get(0)
|
||||||
|
services, err := registry.GetService(service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(services) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b, _ := json.MarshalIndent(services[0], "", " ")
|
||||||
|
fmt.Println(string(b))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "status",
|
||||||
|
Usage: "Check status of running services",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get home dir: %w", err)
|
||||||
|
}
|
||||||
|
runDir := filepath.Join(homeDir, "micro", "run")
|
||||||
|
files, err := os.ReadDir(runDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read run dir: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%-20s %-8s %-8s %s\n", "SERVICE", "PID", "STATUS", "DIRECTORY")
|
||||||
|
for _, f := range files {
|
||||||
|
if f.IsDir() || !strings.HasSuffix(f.Name(), ".pid") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
service := f.Name()[:len(f.Name())-4]
|
||||||
|
pidFilePath := filepath.Join(runDir, f.Name())
|
||||||
|
pidFile, err := os.Open(pidFilePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var pid int
|
||||||
|
var dir string
|
||||||
|
scanner := bufio.NewScanner(pidFile)
|
||||||
|
if scanner.Scan() {
|
||||||
|
fmt.Sscanf(scanner.Text(), "%d", &pid)
|
||||||
|
}
|
||||||
|
if scanner.Scan() {
|
||||||
|
dir = scanner.Text()
|
||||||
|
}
|
||||||
|
pidFile.Close()
|
||||||
|
status := "stopped"
|
||||||
|
if pid > 0 {
|
||||||
|
proc, err := os.FindProcess(pid)
|
||||||
|
if err == nil {
|
||||||
|
if err := proc.Signal(syscall.Signal(0)); err == nil {
|
||||||
|
status = "running"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("%-20s %-8d %-8s %-40s %s\n", service, pid, status, "", dir)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "stop",
|
||||||
|
Usage: "Stop a running service",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
return fmt.Errorf("Usage: micro stop [service]")
|
||||||
|
}
|
||||||
|
service := ctx.Args().Get(0)
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get home dir: %w", err)
|
||||||
|
}
|
||||||
|
runDir := filepath.Join(homeDir, "micro", "run")
|
||||||
|
pidFilePath := filepath.Join(runDir, service+".pid")
|
||||||
|
pidFile, err := os.Open(pidFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("no pid file for service %s", service)
|
||||||
|
}
|
||||||
|
var pid int
|
||||||
|
var dir string
|
||||||
|
scanner := bufio.NewScanner(pidFile)
|
||||||
|
if scanner.Scan() {
|
||||||
|
fmt.Sscanf(scanner.Text(), "%d", &pid)
|
||||||
|
}
|
||||||
|
if scanner.Scan() {
|
||||||
|
dir = scanner.Text()
|
||||||
|
}
|
||||||
|
pidFile.Close()
|
||||||
|
if pid <= 0 {
|
||||||
|
_ = os.Remove(pidFilePath)
|
||||||
|
return fmt.Errorf("service %s is not running", service)
|
||||||
|
}
|
||||||
|
proc, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(pidFilePath)
|
||||||
|
return fmt.Errorf("could not find process for %s", service)
|
||||||
|
}
|
||||||
|
if err := proc.Signal(syscall.SIGTERM); err != nil {
|
||||||
|
_ = os.Remove(pidFilePath)
|
||||||
|
return fmt.Errorf("failed to stop service %s: %v", service, err)
|
||||||
|
}
|
||||||
|
_ = os.Remove(pidFilePath)
|
||||||
|
fmt.Printf("Stopped service %s (pid %d) in directory %s\n", service, pid, dir)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "logs",
|
||||||
|
Usage: "Show logs for a service, or list available logs if no service is specified",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get home dir: %w", err)
|
||||||
|
}
|
||||||
|
logsDir := filepath.Join(homeDir, "micro", "logs")
|
||||||
|
if ctx.Args().Len() == 0 {
|
||||||
|
// List available logs
|
||||||
|
dirEntries, err := os.ReadDir(logsDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not list logs directory: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Available logs:")
|
||||||
|
found := false
|
||||||
|
for _, entry := range dirEntries {
|
||||||
|
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") {
|
||||||
|
fmt.Println(" ", strings.TrimSuffix(entry.Name(), ".log"))
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
fmt.Println(" (no logs found)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
service := ctx.Args().Get(0)
|
||||||
|
logFilePath := filepath.Join(logsDir, service+".log")
|
||||||
|
f, err := os.Open(logFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not open log file for service %s: %v", service, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
scan := bufio.NewScanner(f)
|
||||||
|
for scan.Scan() {
|
||||||
|
fmt.Println(scan.Text())
|
||||||
|
}
|
||||||
|
return scan.Err()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
|
||||||
|
cmd.App().Action = func(c *cli.Context) error {
|
||||||
|
if c.Args().Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := exec.LookPath("micro-" + c.Args().First())
|
||||||
|
if err == nil {
|
||||||
|
ce := exec.Command(v, c.Args().Slice()[1:]...)
|
||||||
|
ce.Stdout = os.Stdout
|
||||||
|
ce.Stderr = os.Stderr
|
||||||
|
return ce.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
command := c.Args().Get(0)
|
||||||
|
args := c.Args().Slice()
|
||||||
|
|
||||||
|
if srv, err := util.LookupService(command); err != nil {
|
||||||
|
return util.CliError(err)
|
||||||
|
} else if srv != nil && util.ShouldRenderHelp(args) {
|
||||||
|
return cli.Exit(util.FormatServiceUsage(srv, c), 0)
|
||||||
|
} else if srv != nil {
|
||||||
|
err := util.CallService(srv, args)
|
||||||
|
return util.CliError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
257
cmd/micro/cli/new/new.go
Normal file
257
cmd/micro/cli/new/new.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// Package new generates micro service templates
|
||||||
|
package new
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/xlab/treeprint"
|
||||||
|
tmpl "go-micro.dev/v5/cmd/micro/cli/new/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func protoComments(goDir, alias string) []string {
|
||||||
|
return []string{
|
||||||
|
"\ndownload protoc zip packages (protoc-$VERSION-$PLATFORM.zip) and install:\n",
|
||||||
|
"visit https://github.com/protocolbuffers/protobuf/releases",
|
||||||
|
"\ncompile the proto file " + alias + ".proto:\n",
|
||||||
|
"cd " + alias,
|
||||||
|
"go mod tidy",
|
||||||
|
"make proto\n",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
// foo
|
||||||
|
Alias string
|
||||||
|
// github.com/micro/foo
|
||||||
|
Dir string
|
||||||
|
// $GOPATH/src/github.com/micro/foo
|
||||||
|
GoDir string
|
||||||
|
// $GOPATH
|
||||||
|
GoPath string
|
||||||
|
// UseGoPath
|
||||||
|
UseGoPath bool
|
||||||
|
// Files
|
||||||
|
Files []file
|
||||||
|
// Comments
|
||||||
|
Comments []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
Path string
|
||||||
|
Tmpl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(c config, file, tmpl string) error {
|
||||||
|
fn := template.FuncMap{
|
||||||
|
"title": func(s string) string {
|
||||||
|
return strings.ReplaceAll(strings.Title(s), "-", "")
|
||||||
|
},
|
||||||
|
"dehyphen": func(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "-", "")
|
||||||
|
},
|
||||||
|
"lower": func(s string) string {
|
||||||
|
return strings.ToLower(s)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
t, err := template.New("f").Funcs(fn).Parse(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Execute(f, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func create(c config) error {
|
||||||
|
// check if dir exists
|
||||||
|
if _, err := os.Stat(c.Dir); !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("%s already exists", c.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Creating service %s\n\n", c.Alias)
|
||||||
|
|
||||||
|
t := treeprint.New()
|
||||||
|
|
||||||
|
// write the files
|
||||||
|
for _, file := range c.Files {
|
||||||
|
f := filepath.Join(c.Dir, file.Path)
|
||||||
|
dir := filepath.Dir(f)
|
||||||
|
|
||||||
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addFileToTree(t, file.Path)
|
||||||
|
if err := write(c, f, file.Tmpl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print tree
|
||||||
|
fmt.Println(t.String())
|
||||||
|
|
||||||
|
for _, comment := range c.Comments {
|
||||||
|
fmt.Println(comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// just wait
|
||||||
|
<-time.After(time.Millisecond * 250)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFileToTree(root treeprint.Tree, file string) {
|
||||||
|
split := strings.Split(file, "/")
|
||||||
|
curr := root
|
||||||
|
for i := 0; i < len(split)-1; i++ {
|
||||||
|
n := curr.FindByValue(split[i])
|
||||||
|
if n != nil {
|
||||||
|
curr = n
|
||||||
|
} else {
|
||||||
|
curr = curr.AddBranch(split[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if curr.FindByValue(split[len(split)-1]) == nil {
|
||||||
|
curr.AddNode(split[len(split)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(ctx *cli.Context) error {
|
||||||
|
dir := ctx.Args().First()
|
||||||
|
if len(dir) == 0 {
|
||||||
|
fmt.Println("specify service name")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the path is absolute, we don't want this
|
||||||
|
// we want to a relative path so we can install in GOPATH
|
||||||
|
if path.IsAbs(dir) {
|
||||||
|
fmt.Println("require relative path as service will be installed in GOPATH")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for protoc
|
||||||
|
if _, err := exec.LookPath("protoc"); err != nil {
|
||||||
|
fmt.Println("WARNING: protoc is not installed or not in your PATH.")
|
||||||
|
fmt.Println("Please install protoc from https://github.com/protocolbuffers/protobuf/releases")
|
||||||
|
fmt.Println("After installing, re-run 'make proto' in your service directory if needed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var goPath string
|
||||||
|
var goDir string
|
||||||
|
|
||||||
|
goPath = build.Default.GOPATH
|
||||||
|
|
||||||
|
// don't know GOPATH, runaway....
|
||||||
|
if len(goPath) == 0 {
|
||||||
|
fmt.Println("unknown GOPATH")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to split path if not windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
goPath = strings.Split(goPath, ";")[0]
|
||||||
|
} else {
|
||||||
|
goPath = strings.Split(goPath, ":")[0]
|
||||||
|
}
|
||||||
|
goDir = filepath.Join(goPath, "src", path.Clean(dir))
|
||||||
|
|
||||||
|
c := config{
|
||||||
|
Alias: dir,
|
||||||
|
Comments: nil, // Remove redundant protoComments
|
||||||
|
Dir: dir,
|
||||||
|
GoDir: goDir,
|
||||||
|
GoPath: goPath,
|
||||||
|
UseGoPath: false,
|
||||||
|
Files: []file{
|
||||||
|
{"main.go", tmpl.MainSRV},
|
||||||
|
{"handler/" + dir + ".go", tmpl.HandlerSRV},
|
||||||
|
{"proto/" + dir + ".proto", tmpl.ProtoSRV},
|
||||||
|
{"Makefile", tmpl.Makefile},
|
||||||
|
{"README.md", tmpl.Readme},
|
||||||
|
{".gitignore", tmpl.GitIgnore},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// set gomodule
|
||||||
|
if os.Getenv("GO111MODULE") != "off" {
|
||||||
|
c.Files = append(c.Files, file{"go.mod", tmpl.Module})
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the files
|
||||||
|
if err := create(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run go mod tidy and make proto
|
||||||
|
fmt.Println("\nRunning 'go mod tidy' and 'make proto'...")
|
||||||
|
if err := runInDir(dir, "go mod tidy"); err != nil {
|
||||||
|
fmt.Printf("Error running 'go mod tidy': %v\n", err)
|
||||||
|
}
|
||||||
|
if err := runInDir(dir, "make proto"); err != nil {
|
||||||
|
fmt.Printf("Error running 'make proto': %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print updated tree including generated files
|
||||||
|
fmt.Println("\nProject structure after 'make proto':")
|
||||||
|
printTree(dir)
|
||||||
|
|
||||||
|
fmt.Println("\nService created successfully! Start coding in your new service directory.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInDir(dir, cmd string) error {
|
||||||
|
parts := strings.Fields(cmd)
|
||||||
|
c := exec.Command(parts[0], parts[1:]...)
|
||||||
|
c.Dir = dir
|
||||||
|
c.Stdout = os.Stdout
|
||||||
|
c.Stderr = os.Stderr
|
||||||
|
return c.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTree(dir string) {
|
||||||
|
t := treeprint.New()
|
||||||
|
walk := func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(dir, path)
|
||||||
|
if rel == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
parts := strings.Split(rel, string(os.PathSeparator))
|
||||||
|
curr := t
|
||||||
|
for i := 0; i < len(parts)-1; i++ {
|
||||||
|
n := curr.FindByValue(parts[i])
|
||||||
|
if n != nil {
|
||||||
|
curr = n
|
||||||
|
} else {
|
||||||
|
curr = curr.AddBranch(parts[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
curr.AddNode(parts[len(parts)-1])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
filepath.Walk(dir, walk)
|
||||||
|
fmt.Println(t.String())
|
||||||
|
}
|
||||||
66
cmd/micro/cli/new/template/handler.go
Normal file
66
cmd/micro/cli/new/template/handler.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
HandlerSRV = `package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
log "go-micro.dev/v5/logger"
|
||||||
|
|
||||||
|
pb "{{.Dir}}/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{title .Alias}} struct{}
|
||||||
|
|
||||||
|
// Return a new handler
|
||||||
|
func New() *{{title .Alias}} {
|
||||||
|
return &{{title .Alias}}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call is a single request handler called via client.Call or the generated client code
|
||||||
|
func (e *{{title .Alias}}) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
|
||||||
|
log.Info("Received {{title .Alias}}.Call request")
|
||||||
|
rsp.Msg = "Hello " + req.Name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream is a server side stream handler called via client.Stream or the generated client code
|
||||||
|
func (e *{{title .Alias}}) Stream(ctx context.Context, req *pb.StreamingRequest, stream pb.{{title .Alias}}_StreamStream) error {
|
||||||
|
log.Infof("Received {{title .Alias}}.Stream request with count: %d", req.Count)
|
||||||
|
|
||||||
|
for i := 0; i < int(req.Count); i++ {
|
||||||
|
log.Infof("Responding: %d", i)
|
||||||
|
if err := stream.Send(&pb.StreamingResponse{
|
||||||
|
Count: int64(i),
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
SubscriberSRV = `package subscriber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
log "go-micro.dev/v5/logger"
|
||||||
|
|
||||||
|
pb "{{.Dir}}/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{title .Alias}} struct{}
|
||||||
|
|
||||||
|
func (e *{{title .Alias}}) Handle(ctx context.Context, msg *pb.Message) error {
|
||||||
|
log.Info("Handler Received message: ", msg.Say)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Handler(ctx context.Context, msg *pb.Message) error {
|
||||||
|
log.Info("Function Received message: ", msg.Say)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
7
cmd/micro/cli/new/template/ignore.go
Normal file
7
cmd/micro/cli/new/template/ignore.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
GitIgnore = `
|
||||||
|
{{.Alias}}
|
||||||
|
`
|
||||||
|
)
|
||||||
27
cmd/micro/cli/new/template/main.go
Normal file
27
cmd/micro/cli/new/template/main.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
MainSRV = `package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"{{.Dir}}/handler"
|
||||||
|
pb "{{.Dir}}/proto"
|
||||||
|
|
||||||
|
"go-micro.dev/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create service
|
||||||
|
service := micro.New("{{lower .Alias}}")
|
||||||
|
|
||||||
|
// Initialize service
|
||||||
|
service.Init()
|
||||||
|
|
||||||
|
// Register handler
|
||||||
|
pb.Register{{title .Alias}}Handler(service.Server(), handler.New())
|
||||||
|
|
||||||
|
// Run service
|
||||||
|
service.Run()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
32
cmd/micro/cli/new/template/makefile.go
Normal file
32
cmd/micro/cli/new/template/makefile.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
Makefile = `
|
||||||
|
GOPATH:=$(shell go env GOPATH)
|
||||||
|
.PHONY: init
|
||||||
|
init:
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
|
go install go-micro.dev/v5/cmd/protoc-gen-micro@latest
|
||||||
|
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
|
||||||
|
|
||||||
|
.PHONY: api
|
||||||
|
api:
|
||||||
|
protoc --openapi_out=. --proto_path=. proto/{{.Alias}}.proto
|
||||||
|
|
||||||
|
.PHONY: proto
|
||||||
|
proto:
|
||||||
|
protoc --proto_path=. --micro_out=. --go_out=:. proto/{{.Alias}}.proto
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
go build -o {{.Alias}} *.go
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test -v ./... -cover
|
||||||
|
|
||||||
|
.PHONY: docker
|
||||||
|
docker:
|
||||||
|
docker build . -t {{.Alias}}:latest
|
||||||
|
`
|
||||||
|
)
|
||||||
14
cmd/micro/cli/new/template/module.go
Normal file
14
cmd/micro/cli/new/template/module.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
Module = `module {{.Dir}}
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
go-micro.dev/v5 latest
|
||||||
|
github.com/golang/protobuf latest
|
||||||
|
google.golang.org/protobuf latest
|
||||||
|
)
|
||||||
|
`
|
||||||
|
)
|
||||||
35
cmd/micro/cli/new/template/proto.go
Normal file
35
cmd/micro/cli/new/template/proto.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
ProtoSRV = `syntax = "proto3";
|
||||||
|
|
||||||
|
package {{dehyphen .Alias}};
|
||||||
|
|
||||||
|
option go_package = "./proto;{{dehyphen .Alias}}";
|
||||||
|
|
||||||
|
service {{title .Alias}} {
|
||||||
|
rpc Call(Request) returns (Response) {}
|
||||||
|
rpc Stream(StreamingRequest) returns (stream StreamingResponse) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
string say = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StreamingRequest {
|
||||||
|
int64 count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StreamingResponse {
|
||||||
|
int64 count = 1;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
30
cmd/micro/cli/new/template/readme.go
Normal file
30
cmd/micro/cli/new/template/readme.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
var (
|
||||||
|
Readme = `# {{title .Alias}} Service
|
||||||
|
|
||||||
|
This is the {{title .Alias}} service
|
||||||
|
|
||||||
|
Generated with
|
||||||
|
|
||||||
|
` + "```" +
|
||||||
|
`
|
||||||
|
micro new {{.Alias}}
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Generate the proto code
|
||||||
|
|
||||||
|
` + "```" +
|
||||||
|
`
|
||||||
|
make proto
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
Run the service
|
||||||
|
|
||||||
|
` + "```" +
|
||||||
|
`
|
||||||
|
micro run .
|
||||||
|
` + "```"
|
||||||
|
)
|
||||||
416
cmd/micro/cli/util/dynamic.go
Normal file
416
cmd/micro/cli/util/dynamic.go
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/stretchr/objx"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go-micro.dev/v5/client"
|
||||||
|
"go-micro.dev/v5/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LookupService queries the service for a service with the given alias. If
|
||||||
|
// no services are found for a given alias, the registry will return nil and
|
||||||
|
// the error will also be nil. An error is only returned if there was an issue
|
||||||
|
// listing from the registry.
|
||||||
|
func LookupService(name string) (*registry.Service, error) {
|
||||||
|
// return a lookup in the default domain as a catch all
|
||||||
|
return serviceWithName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatServiceUsage returns a string containing the service usage.
|
||||||
|
func FormatServiceUsage(srv *registry.Service, c *cli.Context) string {
|
||||||
|
alias := c.Args().First()
|
||||||
|
subcommand := c.Args().Get(1)
|
||||||
|
|
||||||
|
commands := make([]string, len(srv.Endpoints))
|
||||||
|
endpoints := make([]*registry.Endpoint, len(srv.Endpoints))
|
||||||
|
for i, e := range srv.Endpoints {
|
||||||
|
// map "Helloworld.Call" to "helloworld.call"
|
||||||
|
parts := strings.Split(e.Name, ".")
|
||||||
|
for i, part := range parts {
|
||||||
|
parts[i] = lowercaseInitial(part)
|
||||||
|
}
|
||||||
|
name := strings.Join(parts, ".")
|
||||||
|
|
||||||
|
// remove the prefix if it is the service name, e.g. rather than
|
||||||
|
// "micro run helloworld helloworld call", it would be
|
||||||
|
// "micro run helloworld call".
|
||||||
|
name = strings.TrimPrefix(name, alias+".")
|
||||||
|
|
||||||
|
// instead of "micro run helloworld foo.bar", the command should
|
||||||
|
// be "micro run helloworld foo bar".
|
||||||
|
commands[i] = strings.Replace(name, ".", " ", 1)
|
||||||
|
endpoints[i] = e
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
if len(subcommand) > 0 && subcommand != "--help" {
|
||||||
|
result += fmt.Sprintf("NAME:\n\tmicro %v %v\n\n", alias, subcommand)
|
||||||
|
result += fmt.Sprintf("USAGE:\n\tmicro %v %v [flags]\n\n", alias, subcommand)
|
||||||
|
result += fmt.Sprintf("FLAGS:\n")
|
||||||
|
|
||||||
|
for i, command := range commands {
|
||||||
|
if command == subcommand {
|
||||||
|
result += renderFlags(endpoints[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// sort the command names alphabetically
|
||||||
|
sort.Strings(commands)
|
||||||
|
|
||||||
|
result += fmt.Sprintf("NAME:\n\tmicro %v\n\n", alias)
|
||||||
|
result += fmt.Sprintf("VERSION:\n\t%v\n\n", srv.Version)
|
||||||
|
result += fmt.Sprintf("USAGE:\n\tmicro %v [command]\n\n", alias)
|
||||||
|
result += fmt.Sprintf("COMMANDS:\n\t%v\n", strings.Join(commands, "\n\t"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowercaseInitial(str string) string {
|
||||||
|
for i, v := range str {
|
||||||
|
return string(unicode.ToLower(v)) + str[i+1:]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderFlags(endpoint *registry.Endpoint) string {
|
||||||
|
ret := ""
|
||||||
|
for _, value := range endpoint.Request.Values {
|
||||||
|
ret += renderValue([]string{}, value) + "\n"
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderValue(path []string, value *registry.Value) string {
|
||||||
|
if len(value.Values) > 0 {
|
||||||
|
renders := []string{}
|
||||||
|
for _, v := range value.Values {
|
||||||
|
renders = append(renders, renderValue(append(path, value.Name), v))
|
||||||
|
}
|
||||||
|
return strings.Join(renders, "\n")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("\t--%v %v", strings.Join(append(path, value.Name), "_"), value.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallService will call a service using the arguments and flags provided
|
||||||
|
// in the context. It will print the result or error to stdout. If there
|
||||||
|
// was an error performing the call, it will be returned.
|
||||||
|
func CallService(srv *registry.Service, args []string) error {
|
||||||
|
// parse the flags and args
|
||||||
|
args, flags, err := splitCmdArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct the endpoint
|
||||||
|
endpoint, err := constructEndpoint(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure the endpoint exists on the service
|
||||||
|
var ep *registry.Endpoint
|
||||||
|
for _, e := range srv.Endpoints {
|
||||||
|
if e.Name == endpoint {
|
||||||
|
ep = e
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ep == nil {
|
||||||
|
return fmt.Errorf("Endpoint %v not found for service %v", endpoint, srv.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the flags
|
||||||
|
body, err := FlagsToRequest(flags, ep.Request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a context for the call based on the cli context
|
||||||
|
callCtx := context.TODO()
|
||||||
|
|
||||||
|
// TODO: parse out --header or --metadata
|
||||||
|
|
||||||
|
// construct and execute the request using the json content type
|
||||||
|
req := client.DefaultClient.NewRequest(srv.Name, endpoint, body, client.WithContentType("application/json"))
|
||||||
|
var rsp json.RawMessage
|
||||||
|
|
||||||
|
if err := client.DefaultClient.Call(callCtx, req, &rsp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// format the response
|
||||||
|
var out bytes.Buffer
|
||||||
|
defer out.Reset()
|
||||||
|
if err := json.Indent(&out, rsp, "", "\t"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.Write([]byte("\n"))
|
||||||
|
out.WriteTo(os.Stdout)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitCmdArgs takes a cli context and parses out the args and flags, for
|
||||||
|
// example "micro helloworld --name=foo call apple" would result in "call",
|
||||||
|
// "apple" as args and {"name":"foo"} as the flags.
|
||||||
|
func splitCmdArgs(arguments []string) ([]string, map[string][]string, error) {
|
||||||
|
args := []string{}
|
||||||
|
flags := map[string][]string{}
|
||||||
|
|
||||||
|
prev := ""
|
||||||
|
for _, a := range arguments {
|
||||||
|
if !strings.HasPrefix(a, "--") {
|
||||||
|
if len(prev) == 0 {
|
||||||
|
args = append(args, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, exists := flags[prev]
|
||||||
|
if !exists {
|
||||||
|
flags[prev] = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags[prev] = append(flags[prev], a)
|
||||||
|
prev = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// comps would be "foo", "bar" for "--foo=bar"
|
||||||
|
comps := strings.Split(strings.TrimPrefix(a, "--"), "=")
|
||||||
|
_, exists := flags[comps[0]]
|
||||||
|
if !exists {
|
||||||
|
flags[comps[0]] = []string{}
|
||||||
|
}
|
||||||
|
switch len(comps) {
|
||||||
|
case 1:
|
||||||
|
prev = comps[0]
|
||||||
|
case 2:
|
||||||
|
flags[comps[0]] = append(flags[comps[0]], comps[1])
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("Invalid flag: %v. Expected format: --foo=bar", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args, flags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructEndpoint takes a slice of args and converts it into a valid endpoint
|
||||||
|
// such as Helloworld.Call or Foo.Bar, it will return an error if an invalid number
|
||||||
|
// of arguments were provided
|
||||||
|
func constructEndpoint(args []string) (string, error) {
|
||||||
|
var epComps []string
|
||||||
|
switch len(args) {
|
||||||
|
case 1:
|
||||||
|
epComps = append(args, "call")
|
||||||
|
case 2:
|
||||||
|
epComps = args
|
||||||
|
case 3:
|
||||||
|
epComps = args[1:3]
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Incorrect number of arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform the endpoint components, e.g ["helloworld", "call"] to the
|
||||||
|
// endpoint name: "Helloworld.Call".
|
||||||
|
return fmt.Sprintf("%v.%v", strings.Title(epComps[0]), strings.Title(epComps[1])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldRenderHelp returns true if the help flag was passed
|
||||||
|
func ShouldRenderHelp(args []string) bool {
|
||||||
|
args, flags, _ := splitCmdArgs(args)
|
||||||
|
|
||||||
|
// only 1 arg e.g micro helloworld
|
||||||
|
if len(args) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range flags {
|
||||||
|
if key == "help" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlagsToRequest parses a set of flags, e.g {name:"Foo", "options_surname","Bar"} and
|
||||||
|
// converts it into a request body. If the key is not a valid object in the request, an
|
||||||
|
// error will be returned.
|
||||||
|
//
|
||||||
|
// This function constructs []interface{} slices
|
||||||
|
// as opposed to typed ([]string etc) slices for easier testing
|
||||||
|
func FlagsToRequest(flags map[string][]string, req *registry.Value) (map[string]interface{}, error) {
|
||||||
|
coerceValue := func(valueType string, value []string) (interface{}, error) {
|
||||||
|
switch valueType {
|
||||||
|
case "bool":
|
||||||
|
if len(value) == 0 || len(strings.TrimSpace(value[0])) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return strconv.ParseBool(value[0])
|
||||||
|
case "int32":
|
||||||
|
i, err := strconv.Atoi(value[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if i < math.MinInt32 || i > math.MaxInt32 {
|
||||||
|
return nil, fmt.Errorf("value out of range for int32: %d", i)
|
||||||
|
}
|
||||||
|
return int32(i), nil
|
||||||
|
case "int64":
|
||||||
|
return strconv.ParseInt(value[0], 0, 64)
|
||||||
|
case "float64":
|
||||||
|
return strconv.ParseFloat(value[0], 64)
|
||||||
|
case "[]bool":
|
||||||
|
// length is one if it's a `,` separated int slice
|
||||||
|
if len(value) == 1 {
|
||||||
|
value = strings.Split(value[0], ",")
|
||||||
|
}
|
||||||
|
ret := []interface{}{}
|
||||||
|
for _, v := range value {
|
||||||
|
i, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret = append(ret, i)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case "[]int32":
|
||||||
|
// length is one if it's a `,` separated int slice
|
||||||
|
if len(value) == 1 {
|
||||||
|
value = strings.Split(value[0], ",")
|
||||||
|
}
|
||||||
|
ret := []interface{}{}
|
||||||
|
for _, v := range value {
|
||||||
|
i, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if i < math.MinInt32 || i > math.MaxInt32 {
|
||||||
|
return nil, fmt.Errorf("value out of range for int32: %d", i)
|
||||||
|
}
|
||||||
|
ret = append(ret, int32(i))
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case "[]int64":
|
||||||
|
// length is one if it's a `,` separated int slice
|
||||||
|
if len(value) == 1 {
|
||||||
|
value = strings.Split(value[0], ",")
|
||||||
|
}
|
||||||
|
ret := []interface{}{}
|
||||||
|
for _, v := range value {
|
||||||
|
i, err := strconv.ParseInt(v, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret = append(ret, i)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case "[]float64":
|
||||||
|
// length is one if it's a `,` separated float slice
|
||||||
|
if len(value) == 1 {
|
||||||
|
value = strings.Split(value[0], ",")
|
||||||
|
}
|
||||||
|
ret := []interface{}{}
|
||||||
|
for _, v := range value {
|
||||||
|
i, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret = append(ret, i)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case "[]string":
|
||||||
|
// length is one it's a `,` separated string slice
|
||||||
|
if len(value) == 1 {
|
||||||
|
value = strings.Split(value[0], ",")
|
||||||
|
}
|
||||||
|
ret := []interface{}{}
|
||||||
|
for _, v := range value {
|
||||||
|
ret = append(ret, v)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case "string":
|
||||||
|
return value[0], nil
|
||||||
|
case "map[string]string":
|
||||||
|
var val map[string]string
|
||||||
|
if err := json.Unmarshal([]byte(value[0]), &val); err != nil {
|
||||||
|
return value[0], nil
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
default:
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := objx.MustFromJSON("{}")
|
||||||
|
|
||||||
|
var flagType func(key string, values []*registry.Value, path ...string) (string, bool)
|
||||||
|
|
||||||
|
flagType = func(key string, values []*registry.Value, path ...string) (string, bool) {
|
||||||
|
for _, attr := range values {
|
||||||
|
if strings.Join(append(path, attr.Name), "-") == key {
|
||||||
|
return attr.Type, true
|
||||||
|
}
|
||||||
|
if attr.Values != nil {
|
||||||
|
typ, found := flagType(key, attr.Values, append(path, attr.Name)...)
|
||||||
|
if found {
|
||||||
|
return typ, found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range flags {
|
||||||
|
ty, found := flagType(key, req.Values)
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("Unknown flag: %v", key)
|
||||||
|
}
|
||||||
|
parsed, err := coerceValue(ty, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// objx.Set does not create the path,
|
||||||
|
// so we do that here
|
||||||
|
if strings.Contains(key, "-") {
|
||||||
|
parts := strings.Split(key, "-")
|
||||||
|
for i, _ := range parts {
|
||||||
|
pToCreate := strings.Join(parts[0:i], ".")
|
||||||
|
if i > 0 && i < len(parts) && !result.Has(pToCreate) {
|
||||||
|
result.Set(pToCreate, map[string]interface{}{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path := strings.Replace(key, "-", ".", -1)
|
||||||
|
result.Set(path, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a service in a domain matching the name
|
||||||
|
func serviceWithName(name string) (*registry.Service, error) {
|
||||||
|
srvs, err := registry.GetService(name)
|
||||||
|
if err == registry.ErrNotFound {
|
||||||
|
return nil, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(srvs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return srvs[0], nil
|
||||||
|
}
|
||||||
379
cmd/micro/cli/util/dynamic_test.go
Normal file
379
cmd/micro/cli/util/dynamic_test.go
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
goregistry "go-micro.dev/v5/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseCase struct {
|
||||||
|
args []string
|
||||||
|
values *goregistry.Value
|
||||||
|
expected map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynamicFlagParsing(t *testing.T) {
|
||||||
|
cases := []parseCase{
|
||||||
|
{
|
||||||
|
args: []string{"--ss=a,b"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "ss",
|
||||||
|
Type: "[]string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"ss": []interface{}{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--ss", "a,b"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "ss",
|
||||||
|
Type: "[]string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"ss": []interface{}{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--ss=a", "--ss=b"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "ss",
|
||||||
|
Type: "[]string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"ss": []interface{}{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--ss", "a", "--ss", "b"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "ss",
|
||||||
|
Type: "[]string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"ss": []interface{}{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--bs=true,false"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "bs",
|
||||||
|
Type: "[]bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"bs": []interface{}{true, false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--bs", "true,false"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "bs",
|
||||||
|
Type: "[]bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"bs": []interface{}{true, false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--bs=true", "--bs=false"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "bs",
|
||||||
|
Type: "[]bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"bs": []interface{}{true, false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--bs", "true", "--bs", "false"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "bs",
|
||||||
|
Type: "[]bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"bs": []interface{}{true, false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is=10,20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int32",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int32(10), int32(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is", "10,20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int32",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int32(10), int32(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is=10", "--is=20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int32",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int32(10), int32(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is", "10", "--is", "20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int32",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int32(10), int32(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is=10,20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int64(10), int64(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is", "10,20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int64(10), int64(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is=10", "--is=20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int64(10), int64(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--is", "10", "--is", "20"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "is",
|
||||||
|
Type: "[]int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"is": []interface{}{int64(10), int64(20)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--fs=10.1,20.2"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "fs",
|
||||||
|
Type: "[]float64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--fs", "10.1,20.2"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "fs",
|
||||||
|
Type: "[]float64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--fs=10.1", "--fs=20.2"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "fs",
|
||||||
|
Type: "[]float64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--fs", "10.1", "--fs", "20.2"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "fs",
|
||||||
|
Type: "[]float64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--user_email=someemail"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "user_email",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"user_email": "someemail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--user_email=someemail", "--user_name=somename"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "user_email",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "user_name",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"user_email": "someemail",
|
||||||
|
"user_name": "somename",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--b"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "b",
|
||||||
|
Type: "bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"b": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--user_friend_email=hi"},
|
||||||
|
values: &goregistry.Value{
|
||||||
|
Values: []*goregistry.Value{
|
||||||
|
{
|
||||||
|
Name: "user_friend_email",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"user_friend_email": "hi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(strings.Join(c.args, " "), func(t *testing.T) {
|
||||||
|
_, flags, err := splitCmdArgs(c.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req, err := FlagsToRequest(flags, c.values)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(c.expected, req) {
|
||||||
|
spew.Dump("Expected:", c.expected, "got: ", req)
|
||||||
|
t.Fatalf("Expected %v, got %v", c.expected, req)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
72
cmd/micro/cli/util/util.go
Normal file
72
cmd/micro/cli/util/util.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Package cliutil contains methods used across all cli commands
|
||||||
|
// @todo: get rid of os.Exits and use errors instread
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
merrors "go-micro.dev/v5/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Exec func(*cli.Context, []string) ([]byte, error)
|
||||||
|
|
||||||
|
func Print(e Exec) func(*cli.Context) error {
|
||||||
|
return func(c *cli.Context) error {
|
||||||
|
rsp, err := e(c, c.Args().Slice())
|
||||||
|
if err != nil {
|
||||||
|
return CliError(err)
|
||||||
|
}
|
||||||
|
if len(rsp) > 0 {
|
||||||
|
fmt.Printf("%s\n", string(rsp))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CliError returns a user friendly message from error. If we can't determine a good one returns an error with code 128
|
||||||
|
func CliError(err error) cli.ExitCoder {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// if it's already a cli.ExitCoder we use this
|
||||||
|
cerr, ok := err.(cli.ExitCoder)
|
||||||
|
if ok {
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
|
||||||
|
// grpc errors
|
||||||
|
if mname := regexp.MustCompile(`malformed method name: \\?"(\w+)\\?"`).FindStringSubmatch(err.Error()); len(mname) > 0 {
|
||||||
|
return cli.Exit(fmt.Sprintf(`Method name "%s" invalid format. Expecting service.endpoint`, mname[1]), 3)
|
||||||
|
}
|
||||||
|
if service := regexp.MustCompile(`service ([\w\.]+): route not found`).FindStringSubmatch(err.Error()); len(service) > 0 {
|
||||||
|
return cli.Exit(fmt.Sprintf(`Service "%s" not found`, service[1]), 4)
|
||||||
|
}
|
||||||
|
if service := regexp.MustCompile(`unknown service ([\w\.]+)`).FindStringSubmatch(err.Error()); len(service) > 0 {
|
||||||
|
if strings.Contains(service[0], ".") {
|
||||||
|
return cli.Exit(fmt.Sprintf(`Service method "%s" not found`, service[1]), 5)
|
||||||
|
}
|
||||||
|
return cli.Exit(fmt.Sprintf(`Service "%s" not found`, service[1]), 5)
|
||||||
|
}
|
||||||
|
if address := regexp.MustCompile(`Error while dialing dial tcp.*?([\w]+\.[\w:\.]+): `).FindStringSubmatch(err.Error()); len(address) > 0 {
|
||||||
|
return cli.Exit(fmt.Sprintf(`Failed to connect to micro server at %s`, address[1]), 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
merr, ok := err.(*merrors.Error)
|
||||||
|
if !ok {
|
||||||
|
return cli.Exit(err, 128)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch merr.Code {
|
||||||
|
case 408:
|
||||||
|
return cli.Exit("Request timed out", 1)
|
||||||
|
case 401:
|
||||||
|
// TODO check if not signed in, prompt to sign in
|
||||||
|
return cli.Exit("Not authorized to perform this request", 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to using the detail from the merr
|
||||||
|
return cli.Exit(merr.Detail, 127)
|
||||||
|
}
|
||||||
26
cmd/micro/main.go
Normal file
26
cmd/micro/main.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"go-micro.dev/v5/cmd"
|
||||||
|
|
||||||
|
_ "go-micro.dev/v5/cmd/micro/cli"
|
||||||
|
_ "go-micro.dev/v5/cmd/micro/run"
|
||||||
|
"go-micro.dev/v5/cmd/micro/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed web/styles.css web/main.js web/templates/*
|
||||||
|
var webFS embed.FS
|
||||||
|
|
||||||
|
var version = "5.0.0-dev"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
server.HTML = webFS
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Init(
|
||||||
|
cmd.Name("micro"),
|
||||||
|
cmd.Version(version),
|
||||||
|
)
|
||||||
|
}
|
||||||
227
cmd/micro/run/run.go
Normal file
227
cmd/micro/run/run.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package run
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go-micro.dev/v5/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Color codes for log output
|
||||||
|
var colors = []string{
|
||||||
|
"\033[31m", // red
|
||||||
|
"\033[32m", // green
|
||||||
|
"\033[33m", // yellow
|
||||||
|
"\033[34m", // blue
|
||||||
|
"\033[35m", // magenta
|
||||||
|
"\033[36m", // cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorFor(idx int) string {
|
||||||
|
return colors[idx%len(colors)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(c *cli.Context) error {
|
||||||
|
dir := c.Args().Get(0)
|
||||||
|
var tmpDir string
|
||||||
|
if len(dir) == 0 {
|
||||||
|
dir = "."
|
||||||
|
} else if strings.HasPrefix(dir, "github.com/") || strings.HasPrefix(dir, "https://github.com/") {
|
||||||
|
// Handle git URLs
|
||||||
|
repo := dir
|
||||||
|
if strings.HasPrefix(repo, "https://") {
|
||||||
|
repo = strings.TrimPrefix(repo, "https://")
|
||||||
|
}
|
||||||
|
// Clone to a temp directory
|
||||||
|
tmp, err := os.MkdirTemp("", "micro-run-")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temp dir: %w", err)
|
||||||
|
}
|
||||||
|
tmpDir = tmp
|
||||||
|
cloneURL := repo
|
||||||
|
if !strings.HasPrefix(cloneURL, "https://") {
|
||||||
|
cloneURL = "https://" + repo
|
||||||
|
}
|
||||||
|
// Run git clone
|
||||||
|
cmd := exec.Command("git", "clone", cloneURL, tmpDir)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to clone repo %s: %w", cloneURL, err)
|
||||||
|
}
|
||||||
|
dir = tmpDir
|
||||||
|
}
|
||||||
|
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get home dir: %w", err)
|
||||||
|
}
|
||||||
|
logsDir := filepath.Join(homeDir, "micro", "logs")
|
||||||
|
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create logs dir: %w", err)
|
||||||
|
}
|
||||||
|
runDir := filepath.Join(homeDir, "micro", "run")
|
||||||
|
if err := os.MkdirAll(runDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create run dir: %w", err)
|
||||||
|
}
|
||||||
|
binDir := filepath.Join(homeDir, "micro", "bin")
|
||||||
|
if err := os.MkdirAll(binDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create bin dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always run all services (find all main.go)
|
||||||
|
var mainFiles []string
|
||||||
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if info.Name() == "main.go" {
|
||||||
|
mainFiles = append(mainFiles, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error walking the path: %w", err)
|
||||||
|
}
|
||||||
|
if len(mainFiles) == 0 {
|
||||||
|
return fmt.Errorf("no main.go files found in %s", dir)
|
||||||
|
}
|
||||||
|
var procs []*exec.Cmd
|
||||||
|
var pidFiles []string
|
||||||
|
for i, mainFile := range mainFiles {
|
||||||
|
serviceDir := filepath.Dir(mainFile)
|
||||||
|
var serviceName string
|
||||||
|
absServiceDir, _ := filepath.Abs(serviceDir)
|
||||||
|
// Determine service name: if absServiceDir matches the provided dir (which may be "."), use cwd
|
||||||
|
if absServiceDir == dir {
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
serviceName = filepath.Base(cwd)
|
||||||
|
} else {
|
||||||
|
serviceName = filepath.Base(serviceDir)
|
||||||
|
}
|
||||||
|
serviceNameForPid := serviceName + "-" + fmt.Sprintf("%x", md5.Sum([]byte(absServiceDir)))[:8]
|
||||||
|
logFilePath := filepath.Join(logsDir, serviceNameForPid+".log")
|
||||||
|
binPath := filepath.Join(binDir, serviceNameForPid)
|
||||||
|
pidFilePath := filepath.Join(runDir, serviceNameForPid+".pid")
|
||||||
|
|
||||||
|
// Check if pid file exists and process is running
|
||||||
|
if pidBytes, err := os.ReadFile(pidFilePath); err == nil {
|
||||||
|
lines := strings.Split(string(pidBytes), "\n")
|
||||||
|
if len(lines) > 0 && len(lines[0]) > 0 {
|
||||||
|
pid := lines[0]
|
||||||
|
if _, err := os.FindProcess(parsePid(pid)); err == nil {
|
||||||
|
if processRunning(pid) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Service %s already running (pid %s)\n", serviceNameForPid, pid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to open log file for %s: %v\n", serviceName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buildCmd := exec.Command("go", "build", "-o", binPath, ".")
|
||||||
|
buildCmd.Dir = serviceDir
|
||||||
|
buildOut, buildErr := buildCmd.CombinedOutput()
|
||||||
|
if buildErr != nil {
|
||||||
|
logFile.WriteString(string(buildOut))
|
||||||
|
logFile.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to build %s: %v\n", serviceName, buildErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmd := exec.Command(binPath)
|
||||||
|
cmd.Dir = serviceDir
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
cmd.Stdout = pw
|
||||||
|
cmd.Stderr = pw
|
||||||
|
color := colorFor(i)
|
||||||
|
go func(name string, color string, pr *io.PipeReader, logFile *os.File) {
|
||||||
|
defer logFile.Close()
|
||||||
|
scanner := bufio.NewScanner(pr)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
// Write to terminal with color and service name
|
||||||
|
fmt.Printf("%s[%s]\033[0m %s\n", color, name, line)
|
||||||
|
// Write to log file with service name prefix
|
||||||
|
logFile.WriteString("[" + name + "] " + line + "\n")
|
||||||
|
}
|
||||||
|
}(serviceName, color, pr, logFile)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to start service %s: %v\n", serviceName, err)
|
||||||
|
pw.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procs = append(procs, cmd)
|
||||||
|
pidFiles = append(pidFiles, pidFilePath)
|
||||||
|
os.WriteFile(pidFilePath, []byte(fmt.Sprintf("%d\n%s\n%s\n%s\n", cmd.Process.Pid, absServiceDir, serviceName, time.Now().Format(time.RFC3339))), 0644)
|
||||||
|
}
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(ch, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-ch
|
||||||
|
for _, proc := range procs {
|
||||||
|
if proc.Process != nil {
|
||||||
|
_ = proc.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pf := range pidFiles {
|
||||||
|
_ = os.Remove(pf)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}()
|
||||||
|
for _, proc := range procs {
|
||||||
|
_ = proc.Wait()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add helpers for process check
|
||||||
|
func parsePid(pidStr string) int {
|
||||||
|
pid, _ := strconv.Atoi(pidStr)
|
||||||
|
return pid
|
||||||
|
}
|
||||||
|
func processRunning(pidStr string) bool {
|
||||||
|
pid := parsePid(pidStr)
|
||||||
|
if pid <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
proc, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// On Unix, sending signal 0 checks if process exists
|
||||||
|
return proc.Signal(syscall.Signal(0)) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd.Register(&cli.Command{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run all services in a directory",
|
||||||
|
Action: Run,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "address",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "Address to bind the micro web UI (default :8080)",
|
||||||
|
Value: ":8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
1072
cmd/micro/server/server.go
Normal file
1072
cmd/micro/server/server.go
Normal file
File diff suppressed because it is too large
Load Diff
91
cmd/micro/server/util_jwt.go
Normal file
91
cmd/micro/server/util_jwt.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
jwtPrivateKey *rsa.PrivateKey
|
||||||
|
jwtPublicKey *rsa.PublicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load or generate RSA keys for JWT
|
||||||
|
func InitJWTKeys(privPath, pubPath string) error {
|
||||||
|
var err error
|
||||||
|
if _, err = os.Stat(privPath); os.IsNotExist(err) {
|
||||||
|
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
privBytes := x509.MarshalPKCS1PrivateKey(priv)
|
||||||
|
privPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})
|
||||||
|
os.WriteFile(privPath, privPem, 0600)
|
||||||
|
pubBytes, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey)
|
||||||
|
pubPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes})
|
||||||
|
os.WriteFile(pubPath, pubPem, 0644)
|
||||||
|
}
|
||||||
|
privPem, err := os.ReadFile(privPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode(privPem)
|
||||||
|
if block == nil {
|
||||||
|
return errors.New("invalid private key PEM")
|
||||||
|
}
|
||||||
|
jwtPrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pubPem, err := os.ReadFile(pubPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, _ = pem.Decode(pubPem)
|
||||||
|
if block == nil {
|
||||||
|
return errors.New("invalid public key PEM")
|
||||||
|
}
|
||||||
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
jwtPublicKey, ok = pub.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not RSA public key")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a JWT for a user
|
||||||
|
func GenerateJWT(userID, userType string, scopes []string, expiry time.Duration) (string, error) {
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"sub": userID,
|
||||||
|
"type": userType,
|
||||||
|
"scopes": scopes,
|
||||||
|
"exp": time.Now().Add(expiry).Unix(),
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||||
|
return token.SignedString(jwtPrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and validate a JWT, returns claims if valid
|
||||||
|
func ParseJWT(tokenStr string) (jwt.MapClaims, error) {
|
||||||
|
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
|
return nil, errors.New("unexpected signing method")
|
||||||
|
}
|
||||||
|
return jwtPublicKey, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid token")
|
||||||
|
}
|
||||||
34
cmd/micro/web/main.js
Normal file
34
cmd/micro/web/main.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Minimal JS for reactive form submissions
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.querySelectorAll('form[data-reactive]')?.forEach(function(form) {
|
||||||
|
form.addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const params = {};
|
||||||
|
for (const [key, value] of formData.entries()) {
|
||||||
|
params[key] = value;
|
||||||
|
}
|
||||||
|
const action = form.getAttribute('action');
|
||||||
|
const method = form.getAttribute('method') || 'POST';
|
||||||
|
try {
|
||||||
|
const resp = await fetch(action, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(params)
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
// Find or create a response container
|
||||||
|
let respDiv = form.querySelector('.js-response');
|
||||||
|
if (!respDiv) {
|
||||||
|
respDiv = document.createElement('div');
|
||||||
|
respDiv.className = 'js-response';
|
||||||
|
form.appendChild(respDiv);
|
||||||
|
}
|
||||||
|
respDiv.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
||||||
|
} catch (err) {
|
||||||
|
alert('Error: ' + err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
236
cmd/micro/web/styles.css
Normal file
236
cmd/micro/web/styles.css
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
body {
|
||||||
|
background: #fff;
|
||||||
|
color: #111;
|
||||||
|
font-family: 'Inter', 'Segoe UI', 'Arial', 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, nav, footer {
|
||||||
|
background: #fff;
|
||||||
|
color: #111;
|
||||||
|
padding: 1.2em 2em 1.2em 2em;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
margin: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2em 1em 3em 1em;
|
||||||
|
background: #fff;
|
||||||
|
margin-left: 100px; /* leave space for sidebar */
|
||||||
|
margin-right: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
color: #111;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.2em;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #222;
|
||||||
|
margin: 2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #111;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
margin: 1em 0 1em 2em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
background: #f7f7f7;
|
||||||
|
color: #111;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.98em;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #222;
|
||||||
|
padding: 1.5em 1.5em 1em 1.5em;
|
||||||
|
margin: 2em 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
background: #fff;
|
||||||
|
color: #111;
|
||||||
|
border: 1px solid #222;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.5em 0.7em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
transition: border 0.2s;
|
||||||
|
}
|
||||||
|
input:focus, select:focus, textarea:focus {
|
||||||
|
border: 1.5px solid #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, input[type="submit"], .button {
|
||||||
|
background: #fff;
|
||||||
|
color: #111;
|
||||||
|
border: 1.5px solid #111;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.5em 1.2em;
|
||||||
|
margin: 0.5em 0.2em 0.5em 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
button:hover, input[type="submit"]:hover, .button:hover {
|
||||||
|
background: #111;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table, table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: #fff;
|
||||||
|
margin: 2em 0;
|
||||||
|
}
|
||||||
|
table th, table td {
|
||||||
|
border: none;
|
||||||
|
padding: 0.7em 1em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
table th {
|
||||||
|
background: #f7f7f7;
|
||||||
|
color: #111;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
table tr:nth-child(even) {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-bullets {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #111;
|
||||||
|
border: 1px solid #222;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 0.95em;
|
||||||
|
padding: 0.2em 0.7em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: #111;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert, .error, .success {
|
||||||
|
background: #fff;
|
||||||
|
color: #111;
|
||||||
|
border: 1px solid #222;
|
||||||
|
padding: 1em 1.5em;
|
||||||
|
margin: 2em 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
main {
|
||||||
|
max-width: 98vw;
|
||||||
|
padding: 1em 0.2em 2em 0.2em;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline/unstyled form for delete button */
|
||||||
|
.form-inline, .form-plain {
|
||||||
|
display: inline;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.form-inline input, .form-inline button, .form-plain input, .form-plain button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.delete-btn, .form-inline .delete-btn, .form-plain .delete-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #c00;
|
||||||
|
border: 1.5px solid #c00;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
margin: 0 0.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
.delete-btn:hover {
|
||||||
|
background: #c00;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.log-link:hover {
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
34
cmd/micro/web/templates/api.html
Normal file
34
cmd/micro/web/templates/api.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">API</h2>
|
||||||
|
<p class="api-auth-info" style="background:#f8f8e8; border:1px solid #e0e0b0; padding:1em; margin-bottom:2em; font-size:1.08em; border-radius:6px;">
|
||||||
|
<b>API Authentication Required:</b> All API calls to <code>/api/...</code> endpoints (except this page) must include an <b>Authorization: Bearer <token></b> header.<br>
|
||||||
|
You can generate tokens on the <a href="/auth/tokens">Tokens page</a>.
|
||||||
|
</p>
|
||||||
|
{{range .Services}}
|
||||||
|
<h3 id="{{.Anchor}}" style="margin-top:3em; font-size:1.2em; font-weight:bold;">{{.Name}}</h3>
|
||||||
|
{{if .Endpoints}}
|
||||||
|
<div style="margin-bottom:3em;">
|
||||||
|
{{range .Endpoints}}
|
||||||
|
<div style="margin-bottom:2.8em; padding:1.3em 1.5em; background:#fafbfc; border-radius:7px; border:1px solid #eee;">
|
||||||
|
<div style="font-size:1.12em; margin-bottom:0.7em;"><a href="{{.Path}}" class="micro-link" style="font-weight:bold;">{{.Name}}</a></div>
|
||||||
|
<div style="margin-bottom:0.8em; color:#888; font-size:1em;">
|
||||||
|
<b>HTTP Path:</b> <code>{{.Path}}</code>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:3em; flex-wrap:wrap;">
|
||||||
|
<div style="min-width:240px;">
|
||||||
|
<b>Request:</b>
|
||||||
|
<pre style="background:#f4f4f4; border-radius:5px; padding:1em 1.2em; margin:0.5em 0 1em 0; font-size:1em;">{{.Params}}</pre>
|
||||||
|
</div>
|
||||||
|
<div style="min-width:240px;">
|
||||||
|
<b>Response:</b>
|
||||||
|
<pre style="background:#f4f4f4; border-radius:5px; padding:1em 1.2em; margin:0.5em 0 1em 0; font-size:1em;">{{.Response}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<p style="color:#888;">No endpoints</p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
15
cmd/micro/web/templates/auth_login.html
Normal file
15
cmd/micro/web/templates/auth_login.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Login</h2>
|
||||||
|
<form method="POST" action="/auth/login" style="max-width:340px; margin:2em 0;">
|
||||||
|
<div style="margin-bottom:1.2em;">
|
||||||
|
<input name="id" placeholder="Username" required style="width:100%; padding:0.7em;">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:1.2em;">
|
||||||
|
<input name="password" type="password" placeholder="Password" required style="width:100%; padding:0.7em;">
|
||||||
|
</div>
|
||||||
|
<button type="submit" style="width:100%; padding:0.7em;">Login</button>
|
||||||
|
</form>
|
||||||
|
{{if .Error}}
|
||||||
|
<div style="color:#c00; margin-top:1em;">{{.Error}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
63
cmd/micro/web/templates/auth_tokens.html
Normal file
63
cmd/micro/web/templates/auth_tokens.html
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Auth Tokens</h2>
|
||||||
|
<table style="margin-bottom:2em;">
|
||||||
|
<thead>
|
||||||
|
<tr><th>ID</th><th>Type</th><th>Scopes</th><th>Metadata</th><th>Token</th><th>Delete</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Tokens}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.ID}}</td>
|
||||||
|
<td>{{.Type}}</td>
|
||||||
|
<td>{{range .Scopes}}<code>{{.}}</code> {{end}}</td>
|
||||||
|
<td>
|
||||||
|
{{range $k, $v := .Metadata}}
|
||||||
|
{{if and (ne $k "password_hash") (ne $k "token")}}
|
||||||
|
<b>{{$k}}</b>: {{$v}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td style="max-width:320px; word-break:break-all;">
|
||||||
|
{{if .Token}}
|
||||||
|
<span class="obfuscated-token" data-token="{{.Token}}">
|
||||||
|
{{if .TokenSuffix}}
|
||||||
|
{{.TokenPrefix}}...{{.TokenSuffix}}
|
||||||
|
{{else}}
|
||||||
|
{{.Token}}
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
<button onclick="copyToken(this)" data-token="{{.Token}}" style="margin-left:0.5em;">Copy</button>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" action="/auth/tokens" style="display:inline; padding: 0; border: 0">
|
||||||
|
<input type="hidden" name="delete" value="{{.ID}}">
|
||||||
|
<button type="submit" onclick="return confirm('Delete token {{.ID}}?')">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 style="margin-bottom:1em;">Create New Token</h3>
|
||||||
|
<form method="POST" action="/auth/tokens">
|
||||||
|
<input name="id" placeholder="Name/ID" required style="margin-right:1em;">
|
||||||
|
<select name="type" style="margin-right:1em;">
|
||||||
|
<option value="user">User</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
<option value="service">Service</option>
|
||||||
|
</select>
|
||||||
|
<input name="scopes" placeholder="Scopes (comma separated)" style="margin-right:1em;">
|
||||||
|
<button type="submit">Create</button>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
function copyToken(btn) {
|
||||||
|
const token = btn.getAttribute('data-token');
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(token);
|
||||||
|
btn.textContent = 'Copied!';
|
||||||
|
setTimeout(() => { btn.textContent = 'Copy'; }, 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
40
cmd/micro/web/templates/auth_users.html
Normal file
40
cmd/micro/web/templates/auth_users.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">User Accounts</h2>
|
||||||
|
<table style="margin-bottom:2em;">
|
||||||
|
<thead>
|
||||||
|
<tr><th>ID</th><th>Type</th><th>Scopes</th><th>Metadata</th><th>Delete</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Users}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.ID}}</td>
|
||||||
|
<td>{{.Type}}</td>
|
||||||
|
<td>{{range .Scopes}}<code>{{.}}</code> {{end}}</td>
|
||||||
|
<td>
|
||||||
|
{{range $k, $v := .Metadata}}
|
||||||
|
{{if ne $k "password_hash"}}
|
||||||
|
<b>{{$k}}</b>: {{$v}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" action="/auth/users" style="display:inline; padding: 0; border: 0">
|
||||||
|
<input type="hidden" name="delete" value="{{.ID}}">
|
||||||
|
<button type="submit" onclick="return confirm('Delete user {{.ID}}?')">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 style="margin-bottom:1em;">Create New User</h3>
|
||||||
|
<form method="POST" action="/auth/users">
|
||||||
|
<input name="id" placeholder="Username" required style="margin-right:1em;">
|
||||||
|
<input name="password" type="password" placeholder="Password" required style="margin-right:1em;">
|
||||||
|
<select name="type" style="margin-right:1em;">
|
||||||
|
<option value="user">User</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit">Create</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
52
cmd/micro/web/templates/base.html
Normal file
52
cmd/micro/web/templates/base.html
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<link rel="stylesheet" href="/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="layout" style="display:flex; min-height:100vh;">
|
||||||
|
{{if not .HideSidebar}}
|
||||||
|
<nav id="sidebar" style="width:220px; background:#f5f5f5; padding:2em 1.5em 2em 2em; border:1px solid #eee;">
|
||||||
|
<h1 style="margin-bottom:1em;"><a href="/" id="title">Micro</a></h1>
|
||||||
|
{{if .User}}
|
||||||
|
<div style="margin-bottom:1.5em; font-size:1.05em;">
|
||||||
|
<span style="color:#888;">Logged in as</span>
|
||||||
|
<b>{{.User.ID}}</b>
|
||||||
|
<form method="POST" action="/auth/logout" style="margin-top:0.7em; display:block; background:none; box-shadow:none; padding:0; border:none;">
|
||||||
|
<button type="submit" style="padding:0.25em 0.8em; font-size:0.97em; border-radius:4px; margin:0; cursor:pointer;">Logout</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div style="margin-bottom:1.5em;">
|
||||||
|
<a href="/auth/login" class="micro-link">Login</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<ul class="no-bullets" style="padding-left:0;">
|
||||||
|
<li><a href="/" class="micro-link">Home</a></li>
|
||||||
|
<li><a href="/services" class="micro-link">Services</a></li>
|
||||||
|
<li><a href="/logs" class="micro-link">Logs</a></li>
|
||||||
|
<li><a href="/status" class="micro-link">Status</a></li>
|
||||||
|
<li><a href="/api" class="micro-link">API</a></li>
|
||||||
|
<li><a href="/auth/tokens" class="micro-link">Tokens</a></li>
|
||||||
|
<li><a href="/auth/users" class="micro-link">Users</a></li>
|
||||||
|
</ul>
|
||||||
|
{{if and .SidebarEndpoints .SidebarEndpointsEnabled}}
|
||||||
|
<hr style="margin:2em 0 1em 0;">
|
||||||
|
<div style="font-weight:bold; margin-bottom:0.5em;">API Endpoints</div>
|
||||||
|
<div style="max-height:40vh; overflow-y:auto; font-size:0.97em;">
|
||||||
|
{{range .SidebarEndpoints}}
|
||||||
|
<div style="margin-bottom:0.3em;"><a href="#{{.Anchor}}" class="micro-link">{{.Name}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</nav>
|
||||||
|
{{end}}
|
||||||
|
<main class="container" style="flex:1; min-width:0;">
|
||||||
|
{{template "content" .}}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
cmd/micro/web/templates/form.html
Normal file
23
cmd/micro/web/templates/form.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2>{{.ServiceName}}</h2>
|
||||||
|
<form action="/{{.Action}}" method="POST" data-reactive>
|
||||||
|
<h3 class="text-lg font-bold mb-2">{{.EndpointName}}</h3>
|
||||||
|
{{range .Inputs}}
|
||||||
|
<label class="block font-semibold">{{.Label}}</label>
|
||||||
|
<input name="{{.Name}}" placeholder="{{.Placeholder}}" class="border rounded px-2 py-1 mb-2 w-full" value="{{.Value}}">
|
||||||
|
{{end}}
|
||||||
|
<button class="micro-link mt-2" type="submit">Submit</button>
|
||||||
|
<div class="js-response"></div>
|
||||||
|
</form>
|
||||||
|
{{if .Error}}
|
||||||
|
<div class="mt-4 text-red-600 font-bold">Error: {{.Error}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Response}}
|
||||||
|
<div class="mt-4">
|
||||||
|
<h4 class="font-bold mb-2">Response</h4>
|
||||||
|
{{.ResponseTable}}
|
||||||
|
<pre class="bg-gray-100 rounded p-2 mt-2">{{.ResponseJSON}}</pre>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<script src="/main.js"></script>
|
||||||
|
{{end}}
|
||||||
21
cmd/micro/web/templates/home.html
Normal file
21
cmd/micro/web/templates/home.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Dashboard</h2>
|
||||||
|
<div style="display:flex; align-items:center; gap:2em; margin-bottom:2em;">
|
||||||
|
<div style="display:flex; align-items:center; gap:0.5em;">
|
||||||
|
<span style="font-size:2.2em; vertical-align:middle;">
|
||||||
|
{{if eq .StatusDot "green"}}
|
||||||
|
<span style="display:inline-block; width:1em; height:1em; background:#2ecc40; border-radius:50%;"></span>
|
||||||
|
{{else if eq .StatusDot "yellow"}}
|
||||||
|
<span style="display:inline-block; width:1em; height:1em; background:#ffcc00; border-radius:50%;"></span>
|
||||||
|
{{else}}
|
||||||
|
<span style="display:inline-block; width:1em; height:1em; background:#ff4136; border-radius:50%;"></span>
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
<span style="font-size:1.2em; font-weight:bold;">Status</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:1.1em;">Services: <b>{{.ServiceCount}}</b></div>
|
||||||
|
<div style="font-size:1.1em; color:#2ecc40;">Running: <b>{{.RunningCount}}</b></div>
|
||||||
|
<div style="font-size:1.1em; color:#ff4136;">Stopped: <b>{{.StoppedCount}}</b></div>
|
||||||
|
</div>
|
||||||
|
<p>Welcome to the Micro dashboard. Use the sidebar to navigate services, logs, status, and API.</p>
|
||||||
|
{{end}}
|
||||||
5
cmd/micro/web/templates/log.html
Normal file
5
cmd/micro/web/templates/log.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Logs for {{.Service}}</h2>
|
||||||
|
<pre class="bg-gray-100 rounded p-2 mt-2" style="max-height: 60vh; overflow-y: auto;">{{.Log}}</pre>
|
||||||
|
<a href="/logs" class="micro-link">Back to logs</a>
|
||||||
|
{{end}}
|
||||||
8
cmd/micro/web/templates/logs.html
Normal file
8
cmd/micro/web/templates/logs.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Logs</h2>
|
||||||
|
<ul class="no-bullets">
|
||||||
|
{{range .Services}}
|
||||||
|
<li><a href="/logs/{{.}}" class="micro-link">{{.}}</a></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
26
cmd/micro/web/templates/service.html
Normal file
26
cmd/micro/web/templates/service.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
{{if .ServiceName}}
|
||||||
|
<h2 class="text-xl font-bold mb-2">{{.ServiceName}}</h2>
|
||||||
|
<h4 class="font-semibold mb-2">Endpoints</h4>
|
||||||
|
{{if .Endpoints}}
|
||||||
|
{{range .Endpoints}}
|
||||||
|
<div><a href="{{.Path}}" class="micro-link">{{.Name}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<p>No endpoints registered</p>
|
||||||
|
{{end}}
|
||||||
|
<h4 class="font-semibold mt-4 mb-2">Description</h4>
|
||||||
|
<pre class="bg-gray-100 rounded p-2">{{.Description}}</pre>
|
||||||
|
{{else}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Services</h2>
|
||||||
|
{{if .Services}}
|
||||||
|
<ul class="no-bullets">
|
||||||
|
{{range .Services}}
|
||||||
|
<li><a href="/{{.}}" class="micro-link">{{.}}</a></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p>No services registered</p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
29
cmd/micro/web/templates/status.html
Normal file
29
cmd/micro/web/templates/status.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Service Status</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Service</th>
|
||||||
|
<th>Directory</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>PID</th>
|
||||||
|
<th>Uptime</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Logs</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Statuses}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Service}}</td>
|
||||||
|
<td><code>{{.Dir}}</code></td>
|
||||||
|
<td>{{.Status}}</td>
|
||||||
|
<td>{{.PID}}</td>
|
||||||
|
<td>{{.Uptime}}</td>
|
||||||
|
<td style="font-size:0.9em; color:#888;">{{.ID}}</td>
|
||||||
|
<td><a href="/logs/{{.ID}}" class="log-link">View logs</a></td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{end}}
|
||||||
144
cmd/protoc-gen-micro/README.md
Normal file
144
cmd/protoc-gen-micro/README.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# protoc-gen-micro
|
||||||
|
|
||||||
|
This is protobuf code generation for go-micro. We use protoc-gen-micro to reduce boilerplate code.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
go install go-micro.dev/v5/cmd/protoc-gen-micro@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Also required:
|
||||||
|
|
||||||
|
- [protoc](https://github.com/google/protobuf)
|
||||||
|
- [protoc-gen-go](https://google.golang.org/protobuf)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Define your service as `greeter.proto`
|
||||||
|
|
||||||
|
```
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package greeter;
|
||||||
|
option go_package = "/proto;greeter";
|
||||||
|
|
||||||
|
service Greeter {
|
||||||
|
rpc Hello(Request) returns (Response) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the code
|
||||||
|
|
||||||
|
```
|
||||||
|
protoc --proto_path=. --micro_out=. --go_out=. greeter.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
Your output result should be:
|
||||||
|
|
||||||
|
```
|
||||||
|
./
|
||||||
|
greeter.proto # original protobuf file
|
||||||
|
greeter.pb.go # auto-generated by protoc-gen-go
|
||||||
|
greeter.micro.go # auto-generated by protoc-gen-micro
|
||||||
|
```
|
||||||
|
|
||||||
|
The micro generated code includes clients and handlers which reduce boiler plate code
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
Register the handler with your micro server
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Greeter struct{}
|
||||||
|
|
||||||
|
func (g *Greeter) Hello(ctx context.Context, req *proto.Request, rsp *proto.Response) error {
|
||||||
|
rsp.Msg = "Hello " + req.Name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.RegisterGreeterHandler(service.Server(), &Greeter{})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
Create a service client with your micro client
|
||||||
|
|
||||||
|
```go
|
||||||
|
client := proto.NewGreeterService("greeter", service.Client())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
If you see an error about `protoc-gen-micro` not being found or executable, it's likely your environment may not be configured correctly. If you've already installed `protoc`, `protoc-gen-go`, and `protoc-gen-micro` ensure you've included `$GOPATH/bin` in your `PATH`.
|
||||||
|
|
||||||
|
Alternative specify the Go plugin paths as arguments to the `protoc` command
|
||||||
|
|
||||||
|
```
|
||||||
|
protoc --plugin=protoc-gen-go=$GOPATH/bin/protoc-gen-go --plugin=protoc-gen-micro=$GOPATH/bin/protoc-gen-micro --proto_path=. --micro_out=. --go_out=. greeter.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
|
||||||
|
Add a micro API endpoint which routes directly to an RPC method
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
1. Clone `github.com/googleapis/googleapis` to use this feature as it requires http annotations.
|
||||||
|
2. The protoc command must include `-I$GOPATH/src/github.com/googleapis/googleapis` for the annotations import.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package greeter;
|
||||||
|
option go_package = "/proto;greeter";
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
|
||||||
|
service Greeter {
|
||||||
|
rpc Hello(Request) returns (Response) {
|
||||||
|
option (google.api.http) = { post: "/hello"; body: "*"; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The proto generates a `RegisterGreeterHandler` function with a [api.Endpoint](https://godoc.org/go-micro.dev/v3/api#Endpoint).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error {
|
||||||
|
type greeter interface {
|
||||||
|
Hello(ctx context.Context, in *Request, out *Response) error
|
||||||
|
}
|
||||||
|
type Greeter struct {
|
||||||
|
greeter
|
||||||
|
}
|
||||||
|
h := &greeterHandler{hdlr}
|
||||||
|
opts = append(opts, api.WithEndpoint(&api.Endpoint{
|
||||||
|
Name: "Greeter.Hello",
|
||||||
|
Path: []string{"/hello"},
|
||||||
|
Method: []string{"POST"},
|
||||||
|
Handler: "rpc",
|
||||||
|
}))
|
||||||
|
return s.Handle(s.NewHandler(&Greeter{h}, opts...))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
protoc-gen-micro is a liberal reuse of protoc-gen-go hence we maintain the original license
|
||||||
223
cmd/protoc-gen-micro/examples/greeter/greeter.pb.go
Normal file
223
cmd/protoc-gen-micro/examples/greeter/greeter.pb.go
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.32.0
|
||||||
|
// protoc v4.25.3
|
||||||
|
// source: greeter.proto
|
||||||
|
|
||||||
|
package greeter
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Msg *string `protobuf:"bytes,2,opt,name=msg,proto3,oneof" json:"msg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) Reset() {
|
||||||
|
*x = Request{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_greeter_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Request) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Request) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_greeter_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Request) Descriptor() ([]byte, []int) {
|
||||||
|
return file_greeter_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetMsg() string {
|
||||||
|
if x != nil && x.Msg != nil {
|
||||||
|
return *x.Msg
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) Reset() {
|
||||||
|
*x = Response{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_greeter_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Response) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_greeter_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Response) Descriptor() ([]byte, []int) {
|
||||||
|
return file_greeter_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) GetMsg() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Msg
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_greeter_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_greeter_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0d, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||||
|
0x3c, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
|
||||||
|
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15,
|
||||||
|
0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x6d,
|
||||||
|
0x73, 0x67, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, 0x73, 0x67, 0x22, 0x1c, 0x0a,
|
||||||
|
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, 0x4e, 0x0a, 0x07, 0x47,
|
||||||
|
0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12,
|
||||||
|
0x08, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x23, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||||
|
0x12, 0x08, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x0c, 0x5a, 0x0a, 0x2e,
|
||||||
|
0x2e, 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_greeter_proto_rawDescOnce sync.Once
|
||||||
|
file_greeter_proto_rawDescData = file_greeter_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_greeter_proto_rawDescGZIP() []byte {
|
||||||
|
file_greeter_proto_rawDescOnce.Do(func() {
|
||||||
|
file_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(file_greeter_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_greeter_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_greeter_proto_goTypes = []interface{}{
|
||||||
|
(*Request)(nil), // 0: Request
|
||||||
|
(*Response)(nil), // 1: Response
|
||||||
|
}
|
||||||
|
var file_greeter_proto_depIdxs = []int32{
|
||||||
|
0, // 0: Greeter.Hello:input_type -> Request
|
||||||
|
0, // 1: Greeter.Stream:input_type -> Request
|
||||||
|
1, // 2: Greeter.Hello:output_type -> Response
|
||||||
|
1, // 3: Greeter.Stream:output_type -> Response
|
||||||
|
2, // [2:4] is the sub-list for method output_type
|
||||||
|
0, // [0:2] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_greeter_proto_init() }
|
||||||
|
func file_greeter_proto_init() {
|
||||||
|
if File_greeter_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_greeter_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Request); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_greeter_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Response); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_greeter_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_greeter_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_greeter_proto_goTypes,
|
||||||
|
DependencyIndexes: file_greeter_proto_depIdxs,
|
||||||
|
MessageInfos: file_greeter_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_greeter_proto = out.File
|
||||||
|
file_greeter_proto_rawDesc = nil
|
||||||
|
file_greeter_proto_goTypes = nil
|
||||||
|
file_greeter_proto_depIdxs = nil
|
||||||
|
}
|
||||||
183
cmd/protoc-gen-micro/examples/greeter/greeter.pb.micro.go
Normal file
183
cmd/protoc-gen-micro/examples/greeter/greeter.pb.micro.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||||
|
// source: greeter.proto
|
||||||
|
|
||||||
|
package greeter
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
proto "google.golang.org/protobuf/proto"
|
||||||
|
math "math"
|
||||||
|
)
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
client "go-micro.dev/v5/client"
|
||||||
|
server "go-micro.dev/v5/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ context.Context
|
||||||
|
var _ client.Option
|
||||||
|
var _ server.Option
|
||||||
|
|
||||||
|
// Client API for Greeter service
|
||||||
|
|
||||||
|
type GreeterService interface {
|
||||||
|
Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error)
|
||||||
|
Stream(ctx context.Context, opts ...client.CallOption) (Greeter_StreamService, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type greeterService struct {
|
||||||
|
c client.Client
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGreeterService(name string, c client.Client) GreeterService {
|
||||||
|
return &greeterService{
|
||||||
|
c: c,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *greeterService) Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) {
|
||||||
|
req := c.c.NewRequest(c.name, "Greeter.Hello", in)
|
||||||
|
out := new(Response)
|
||||||
|
err := c.c.Call(ctx, req, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *greeterService) Stream(ctx context.Context, opts ...client.CallOption) (Greeter_StreamService, error) {
|
||||||
|
req := c.c.NewRequest(c.name, "Greeter.Stream", &Request{})
|
||||||
|
stream, err := c.c.Stream(ctx, req, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &greeterServiceStream{stream}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Greeter_StreamService interface {
|
||||||
|
Context() context.Context
|
||||||
|
SendMsg(interface{}) error
|
||||||
|
RecvMsg(interface{}) error
|
||||||
|
CloseSend() error
|
||||||
|
Close() error
|
||||||
|
Send(*Request) error
|
||||||
|
Recv() (*Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type greeterServiceStream struct {
|
||||||
|
stream client.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) CloseSend() error {
|
||||||
|
return x.stream.CloseSend()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) Close() error {
|
||||||
|
return x.stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) Context() context.Context {
|
||||||
|
return x.stream.Context()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) SendMsg(m interface{}) error {
|
||||||
|
return x.stream.Send(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) RecvMsg(m interface{}) error {
|
||||||
|
return x.stream.Recv(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) Send(m *Request) error {
|
||||||
|
return x.stream.Send(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterServiceStream) Recv() (*Response, error) {
|
||||||
|
m := new(Response)
|
||||||
|
err := x.stream.Recv(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server API for Greeter service
|
||||||
|
|
||||||
|
type GreeterHandler interface {
|
||||||
|
Hello(context.Context, *Request, *Response) error
|
||||||
|
Stream(context.Context, Greeter_StreamStream) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error {
|
||||||
|
type greeter interface {
|
||||||
|
Hello(ctx context.Context, in *Request, out *Response) error
|
||||||
|
Stream(ctx context.Context, stream server.Stream) error
|
||||||
|
}
|
||||||
|
type Greeter struct {
|
||||||
|
greeter
|
||||||
|
}
|
||||||
|
h := &greeterHandler{hdlr}
|
||||||
|
return s.Handle(s.NewHandler(&Greeter{h}, opts...))
|
||||||
|
}
|
||||||
|
|
||||||
|
type greeterHandler struct {
|
||||||
|
GreeterHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *greeterHandler) Hello(ctx context.Context, in *Request, out *Response) error {
|
||||||
|
return h.GreeterHandler.Hello(ctx, in, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *greeterHandler) Stream(ctx context.Context, stream server.Stream) error {
|
||||||
|
return h.GreeterHandler.Stream(ctx, &greeterStreamStream{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Greeter_StreamStream interface {
|
||||||
|
Context() context.Context
|
||||||
|
SendMsg(interface{}) error
|
||||||
|
RecvMsg(interface{}) error
|
||||||
|
Close() error
|
||||||
|
Send(*Response) error
|
||||||
|
Recv() (*Request, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type greeterStreamStream struct {
|
||||||
|
stream server.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterStreamStream) Close() error {
|
||||||
|
return x.stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterStreamStream) Context() context.Context {
|
||||||
|
return x.stream.Context()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterStreamStream) SendMsg(m interface{}) error {
|
||||||
|
return x.stream.Send(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterStreamStream) RecvMsg(m interface{}) error {
|
||||||
|
return x.stream.Recv(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterStreamStream) Send(m *Response) error {
|
||||||
|
return x.stream.Send(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *greeterStreamStream) Recv() (*Request, error) {
|
||||||
|
m := new(Request)
|
||||||
|
if err := x.stream.Recv(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
17
cmd/protoc-gen-micro/examples/greeter/greeter.proto
Normal file
17
cmd/protoc-gen-micro/examples/greeter/greeter.proto
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "../greeter";
|
||||||
|
|
||||||
|
service Greeter {
|
||||||
|
rpc Hello(Request) returns (Response) {}
|
||||||
|
rpc Stream(stream Request) returns (stream Response) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
string name = 1;
|
||||||
|
optional string msg = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
40
cmd/protoc-gen-micro/generator/Makefile
Normal file
40
cmd/protoc-gen-micro/generator/Makefile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following disclaimer
|
||||||
|
# in the documentation and/or other materials provided with the
|
||||||
|
# distribution.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.inc
|
||||||
|
|
||||||
|
TARG=github.com/golang/protobuf/protoc-gen-go/generator
|
||||||
|
GOFILES=\
|
||||||
|
generator.go\
|
||||||
|
|
||||||
|
DEPS=../descriptor ../plugin ../../proto
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.pkg
|
||||||
2748
cmd/protoc-gen-micro/generator/generator.go
Normal file
2748
cmd/protoc-gen-micro/generator/generator.go
Normal file
File diff suppressed because it is too large
Load Diff
135
cmd/protoc-gen-micro/generator/name_test.go
Normal file
135
cmd/protoc-gen-micro/generator/name_test.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
descriptor "google.golang.org/protobuf/types/descriptorpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCamelCase(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in, want string
|
||||||
|
}{
|
||||||
|
{"one", "One"},
|
||||||
|
{"one_two", "OneTwo"},
|
||||||
|
{"_my_field_name_2", "XMyFieldName_2"},
|
||||||
|
{"Something_Capped", "Something_Capped"},
|
||||||
|
{"my_Name", "My_Name"},
|
||||||
|
{"OneTwo", "OneTwo"},
|
||||||
|
{"_", "X"},
|
||||||
|
{"_a_", "XA_"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
if got := CamelCase(tc.in); got != tc.want {
|
||||||
|
t.Errorf("CamelCase(%q) = %q, want %q", tc.in, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoPackageOption(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
impPath GoImportPath
|
||||||
|
pkg GoPackageName
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"", "", "", false},
|
||||||
|
{"foo", "", "foo", true},
|
||||||
|
{"github.com/golang/bar", "github.com/golang/bar", "bar", true},
|
||||||
|
{"github.com/golang/bar;baz", "github.com/golang/bar", "baz", true},
|
||||||
|
{"github.com/golang/string", "github.com/golang/string", "string", true},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
d := &FileDescriptor{
|
||||||
|
FileDescriptorProto: &descriptor.FileDescriptorProto{
|
||||||
|
Options: &descriptor.FileOptions{
|
||||||
|
GoPackage: &tc.in,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
impPath, pkg, ok := d.goPackageOption()
|
||||||
|
if impPath != tc.impPath || pkg != tc.pkg || ok != tc.ok {
|
||||||
|
t.Errorf("go_package = %q => (%q, %q, %t), want (%q, %q, %t)", tc.in,
|
||||||
|
impPath, pkg, ok, tc.impPath, tc.pkg, tc.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackageNames(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
g.packageNames = make(map[GoImportPath]GoPackageName)
|
||||||
|
g.usedPackageNames = make(map[GoPackageName]bool)
|
||||||
|
for _, test := range []struct {
|
||||||
|
importPath GoImportPath
|
||||||
|
want GoPackageName
|
||||||
|
}{
|
||||||
|
{"github.com/golang/foo", "foo"},
|
||||||
|
{"github.com/golang/second/package/named/foo", "foo1"},
|
||||||
|
{"github.com/golang/third/package/named/foo", "foo2"},
|
||||||
|
{"github.com/golang/conflicts/with/predeclared/ident/string", "string1"},
|
||||||
|
} {
|
||||||
|
if got := g.GoPackageName(test.importPath); got != test.want {
|
||||||
|
t.Errorf("GoPackageName(%v) = %v, want %v", test.importPath, got, test.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnescape(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
// successful cases, including all kinds of escapes
|
||||||
|
{"", ""},
|
||||||
|
{"foo bar baz frob nitz", "foo bar baz frob nitz"},
|
||||||
|
{`\000\001\002\003\004\005\006\007`, string([]byte{0, 1, 2, 3, 4, 5, 6, 7})},
|
||||||
|
{`\a\b\f\n\r\t\v\\\?\'\"`, string([]byte{'\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\', '?', '\'', '"'})},
|
||||||
|
{`\x10\x20\x30\x40\x50\x60\x70\x80`, string([]byte{16, 32, 48, 64, 80, 96, 112, 128})},
|
||||||
|
// variable length octal escapes
|
||||||
|
{`\0\018\222\377\3\04\005\6\07`, string([]byte{0, 1, '8', 0222, 255, 3, 4, 5, 6, 7})},
|
||||||
|
// malformed escape sequences left as is
|
||||||
|
{"foo \\g bar", "foo \\g bar"},
|
||||||
|
{"foo \\xg0 bar", "foo \\xg0 bar"},
|
||||||
|
{"\\", "\\"},
|
||||||
|
{"\\x", "\\x"},
|
||||||
|
{"\\xf", "\\xf"},
|
||||||
|
{"\\777", "\\777"}, // overflows byte
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
s := unescape(tc.in)
|
||||||
|
if s != tc.out {
|
||||||
|
t.Errorf("doUnescape(%q) = %q; should have been %q", tc.in, s, tc.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
cmd/protoc-gen-micro/main.go
Normal file
105
cmd/protoc-gen-micro/main.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// protoc-gen-micro is a plugin for the Google protocol buffer compiler to generate
|
||||||
|
// Go code. Run it by building this program and putting it in your path with
|
||||||
|
// the name
|
||||||
|
//
|
||||||
|
// protoc-gen-micro
|
||||||
|
//
|
||||||
|
// That word 'micro' at the end becomes part of the option string set for the
|
||||||
|
// protocol compiler, so once the protocol compiler (protoc) is installed
|
||||||
|
// you can run
|
||||||
|
//
|
||||||
|
// protoc --micro_out=output_directory --go_out=output_directory input_directory/file.proto
|
||||||
|
//
|
||||||
|
// to generate go-micro code for the protocol defined by file.proto.
|
||||||
|
// With that input, the output will be written to
|
||||||
|
//
|
||||||
|
// output_directory/file.micro.go
|
||||||
|
//
|
||||||
|
// The generated code is documented in the package comment for
|
||||||
|
// the library.
|
||||||
|
//
|
||||||
|
// See the README and documentation for protocol buffers to learn more:
|
||||||
|
//
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go-micro.dev/v5/cmd/protoc-gen-micro/generator"
|
||||||
|
_ "go-micro.dev/v5/cmd/protoc-gen-micro/plugin/micro"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Begin by allocating a generator. The request and response structures are stored there
|
||||||
|
// so we can do error handling easily - the response structure contains the field to
|
||||||
|
// report failure.
|
||||||
|
g := generator.New()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
g.Error(err, "reading input")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := proto.Unmarshal(data, g.Request); err != nil {
|
||||||
|
g.Error(err, "parsing input proto")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Request.FileToGenerate) == 0 {
|
||||||
|
g.Fail("no files to generate")
|
||||||
|
}
|
||||||
|
|
||||||
|
g.CommandLineParameters(g.Request.GetParameter())
|
||||||
|
|
||||||
|
// Create a wrapped version of the Descriptors and EnumDescriptors that
|
||||||
|
// point to the file that defines them.
|
||||||
|
g.WrapTypes()
|
||||||
|
|
||||||
|
g.SetPackageNames()
|
||||||
|
g.BuildTypeNameMap()
|
||||||
|
|
||||||
|
g.GenerateAllFiles()
|
||||||
|
|
||||||
|
// Send back the results.
|
||||||
|
data, err = proto.Marshal(g.Response)
|
||||||
|
if err != nil {
|
||||||
|
g.Error(err, "failed to marshal output proto")
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
g.Error(err, "failed to write output proto")
|
||||||
|
}
|
||||||
|
}
|
||||||
531
cmd/protoc-gen-micro/plugin/micro/micro.go
Normal file
531
cmd/protoc-gen-micro/plugin/micro/micro.go
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
package micro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go-micro.dev/v5/cmd/protoc-gen-micro/generator"
|
||||||
|
options "google.golang.org/genproto/googleapis/api/annotations"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
pb "google.golang.org/protobuf/types/descriptorpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Paths for packages used by code generated in this file,
|
||||||
|
// relative to the import_prefix of the generator.Generator.
|
||||||
|
const (
|
||||||
|
contextPkgPath = "context"
|
||||||
|
clientPkgPath = "go-micro.dev/v5/client"
|
||||||
|
serverPkgPath = "go-micro.dev/v5/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
generator.RegisterPlugin(new(micro))
|
||||||
|
}
|
||||||
|
|
||||||
|
// micro is an implementation of the Go protocol buffer compiler's
|
||||||
|
// plugin architecture. It generates bindings for go-micro support.
|
||||||
|
type micro struct {
|
||||||
|
gen *generator.Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of this plugin, "micro".
|
||||||
|
func (g *micro) Name() string {
|
||||||
|
return "micro"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The names for packages imported in the generated code.
|
||||||
|
// They may vary from the final path component of the import path
|
||||||
|
// if the name is used by other packages.
|
||||||
|
var (
|
||||||
|
contextPkg string
|
||||||
|
clientPkg string
|
||||||
|
serverPkg string
|
||||||
|
pkgImports map[generator.GoPackageName]bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init initializes the plugin.
|
||||||
|
func (g *micro) Init(gen *generator.Generator) {
|
||||||
|
g.gen = gen
|
||||||
|
contextPkg = generator.RegisterUniquePackageName("context", nil)
|
||||||
|
clientPkg = generator.RegisterUniquePackageName("client", nil)
|
||||||
|
serverPkg = generator.RegisterUniquePackageName("server", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a type name defined in a .proto, return its object.
|
||||||
|
// Also record that we're using it, to guarantee the associated import.
|
||||||
|
func (g *micro) objectNamed(name string) generator.Object {
|
||||||
|
g.gen.RecordTypeUse(name)
|
||||||
|
return g.gen.ObjectNamed(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a type name defined in a .proto, return its name as we will print it.
|
||||||
|
func (g *micro) typeName(str string) string {
|
||||||
|
return g.gen.TypeName(g.objectNamed(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// P forwards to g.gen.P.
|
||||||
|
func (g *micro) P(args ...interface{}) { g.gen.P(args...) }
|
||||||
|
|
||||||
|
// Generate generates code for the services in the given file.
|
||||||
|
func (g *micro) Generate(file *generator.FileDescriptor) {
|
||||||
|
if len(file.FileDescriptorProto.Service) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.P("// Reference imports to suppress errors if they are not otherwise used.")
|
||||||
|
g.P("var _ ", contextPkg, ".Context")
|
||||||
|
g.P("var _ ", clientPkg, ".Option")
|
||||||
|
g.P("var _ ", serverPkg, ".Option")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
for i, service := range file.FileDescriptorProto.Service {
|
||||||
|
g.generateService(file, service, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateImports generates the import declaration for this file.
|
||||||
|
func (g *micro) GenerateImports(file *generator.FileDescriptor, imports map[generator.GoImportPath]generator.GoPackageName) {
|
||||||
|
if len(file.FileDescriptorProto.Service) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.P("import (")
|
||||||
|
g.P(contextPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, contextPkgPath)))
|
||||||
|
g.P(clientPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, clientPkgPath)))
|
||||||
|
g.P(serverPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, serverPkgPath)))
|
||||||
|
g.P(")")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
// We need to keep track of imported packages to make sure we don't produce
|
||||||
|
// a name collision when generating types.
|
||||||
|
pkgImports = make(map[generator.GoPackageName]bool)
|
||||||
|
for _, name := range imports {
|
||||||
|
pkgImports[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reservedClientName records whether a client name is reserved on the client side.
|
||||||
|
var reservedClientName = map[string]bool{
|
||||||
|
// TODO: do we need any in go-micro?
|
||||||
|
}
|
||||||
|
|
||||||
|
func unexport(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
name := strings.ToLower(s[:1]) + s[1:]
|
||||||
|
if pkgImports[generator.GoPackageName(name)] {
|
||||||
|
return name + "_"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateService generates all the code for the named service.
|
||||||
|
func (g *micro) generateService(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int) {
|
||||||
|
path := fmt.Sprintf("6,%d", index) // 6 means service.
|
||||||
|
|
||||||
|
origServName := service.GetName()
|
||||||
|
serviceName := strings.ToLower(service.GetName())
|
||||||
|
pkg := file.GetPackage()
|
||||||
|
if pkg != "" {
|
||||||
|
serviceName = pkg
|
||||||
|
}
|
||||||
|
servName := generator.CamelCase(origServName)
|
||||||
|
servAlias := servName + "Service"
|
||||||
|
|
||||||
|
// strip suffix
|
||||||
|
if strings.HasSuffix(servAlias, "ServiceService") {
|
||||||
|
servAlias = strings.TrimSuffix(servAlias, "Service")
|
||||||
|
}
|
||||||
|
|
||||||
|
g.P()
|
||||||
|
g.P("// Client API for ", servName, " service")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
// Client interface.
|
||||||
|
g.P("type ", servAlias, " interface {")
|
||||||
|
for i, method := range service.Method {
|
||||||
|
g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service.
|
||||||
|
g.P(g.generateClientSignature(servName, method))
|
||||||
|
}
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
// Client structure.
|
||||||
|
g.P("type ", unexport(servAlias), " struct {")
|
||||||
|
g.P("c ", clientPkg, ".Client")
|
||||||
|
g.P("name string")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
// NewClient factory.
|
||||||
|
g.P("func New", servAlias, " (name string, c ", clientPkg, ".Client) ", servAlias, " {")
|
||||||
|
/*
|
||||||
|
g.P("if c == nil {")
|
||||||
|
g.P("c = ", clientPkg, ".NewClient()")
|
||||||
|
g.P("}")
|
||||||
|
g.P("if len(name) == 0 {")
|
||||||
|
g.P(`name = "`, serviceName, `"`)
|
||||||
|
g.P("}")
|
||||||
|
*/
|
||||||
|
g.P("return &", unexport(servAlias), "{")
|
||||||
|
g.P("c: c,")
|
||||||
|
g.P("name: name,")
|
||||||
|
g.P("}")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
var methodIndex, streamIndex int
|
||||||
|
serviceDescVar := "_" + servName + "_serviceDesc"
|
||||||
|
// Client method implementations.
|
||||||
|
for _, method := range service.Method {
|
||||||
|
var descExpr string
|
||||||
|
if !method.GetServerStreaming() {
|
||||||
|
// Unary RPC method
|
||||||
|
descExpr = fmt.Sprintf("&%s.Methods[%d]", serviceDescVar, methodIndex)
|
||||||
|
methodIndex++
|
||||||
|
} else {
|
||||||
|
// Streaming RPC method
|
||||||
|
descExpr = fmt.Sprintf("&%s.Streams[%d]", serviceDescVar, streamIndex)
|
||||||
|
streamIndex++
|
||||||
|
}
|
||||||
|
g.generateClientMethod(pkg, serviceName, servName, serviceDescVar, method, descExpr)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.P("// Server API for ", servName, " service")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
// Server interface.
|
||||||
|
serverType := servName + "Handler"
|
||||||
|
g.P("type ", serverType, " interface {")
|
||||||
|
for i, method := range service.Method {
|
||||||
|
g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service.
|
||||||
|
g.P(g.generateServerSignature(servName, method))
|
||||||
|
}
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
// Server registration.
|
||||||
|
g.P("func Register", servName, "Handler(s ", serverPkg, ".Server, hdlr ", serverType, ", opts ...", serverPkg, ".HandlerOption) error {")
|
||||||
|
g.P("type ", unexport(servName), " interface {")
|
||||||
|
|
||||||
|
// generate interface methods
|
||||||
|
for _, method := range service.Method {
|
||||||
|
methName := generator.CamelCase(method.GetName())
|
||||||
|
inType := g.typeName(method.GetInputType())
|
||||||
|
outType := g.typeName(method.GetOutputType())
|
||||||
|
|
||||||
|
if !method.GetServerStreaming() && !method.GetClientStreaming() {
|
||||||
|
g.P(methName, "(ctx ", contextPkg, ".Context, in *", inType, ", out *", outType, ") error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
g.P(methName, "(ctx ", contextPkg, ".Context, stream server.Stream) error")
|
||||||
|
}
|
||||||
|
g.P("}")
|
||||||
|
g.P("type ", servName, " struct {")
|
||||||
|
g.P(unexport(servName))
|
||||||
|
g.P("}")
|
||||||
|
g.P("h := &", unexport(servName), "Handler{hdlr}")
|
||||||
|
g.P("return s.Handle(s.NewHandler(&", servName, "{h}, opts...))")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("type ", unexport(servName), "Handler struct {")
|
||||||
|
g.P(serverType)
|
||||||
|
g.P("}")
|
||||||
|
|
||||||
|
// Server handler implementations.
|
||||||
|
var handlerNames []string
|
||||||
|
for _, method := range service.Method {
|
||||||
|
hname := g.generateServerMethod(servName, method)
|
||||||
|
handlerNames = append(handlerNames, hname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateEndpoint creates the api endpoint
|
||||||
|
func (g *micro) generateEndpoint(servName string, method *pb.MethodDescriptorProto) {
|
||||||
|
if method.Options == nil || !proto.HasExtension(method.Options, options.E_Http) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// http rules
|
||||||
|
r := proto.GetExtension(method.Options, options.E_Http)
|
||||||
|
rule := r.(*options.HttpRule)
|
||||||
|
var meth string
|
||||||
|
var path string
|
||||||
|
switch {
|
||||||
|
case len(rule.GetDelete()) > 0:
|
||||||
|
meth = "DELETE"
|
||||||
|
path = rule.GetDelete()
|
||||||
|
case len(rule.GetGet()) > 0:
|
||||||
|
meth = "GET"
|
||||||
|
path = rule.GetGet()
|
||||||
|
case len(rule.GetPatch()) > 0:
|
||||||
|
meth = "PATCH"
|
||||||
|
path = rule.GetPatch()
|
||||||
|
case len(rule.GetPost()) > 0:
|
||||||
|
meth = "POST"
|
||||||
|
path = rule.GetPost()
|
||||||
|
case len(rule.GetPut()) > 0:
|
||||||
|
meth = "PUT"
|
||||||
|
path = rule.GetPut()
|
||||||
|
}
|
||||||
|
if len(meth) == 0 || len(path) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: process additional bindings
|
||||||
|
g.P("Name:", fmt.Sprintf(`"%s.%s",`, servName, method.GetName()))
|
||||||
|
g.P("Path:", fmt.Sprintf(`[]string{"%s"},`, path))
|
||||||
|
g.P("Method:", fmt.Sprintf(`[]string{"%s"},`, meth))
|
||||||
|
if method.GetServerStreaming() || method.GetClientStreaming() {
|
||||||
|
g.P("Stream: true,")
|
||||||
|
}
|
||||||
|
g.P(`Handler: "rpc",`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateClientSignature returns the client-side signature for a method.
|
||||||
|
func (g *micro) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string {
|
||||||
|
origMethName := method.GetName()
|
||||||
|
methName := generator.CamelCase(origMethName)
|
||||||
|
if reservedClientName[methName] {
|
||||||
|
methName += "_"
|
||||||
|
}
|
||||||
|
reqArg := ", in *" + g.typeName(method.GetInputType())
|
||||||
|
if method.GetClientStreaming() {
|
||||||
|
reqArg = ""
|
||||||
|
}
|
||||||
|
respName := "*" + g.typeName(method.GetOutputType())
|
||||||
|
if method.GetServerStreaming() || method.GetClientStreaming() {
|
||||||
|
respName = servName + "_" + generator.CamelCase(origMethName) + "Service"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s(ctx %s.Context%s, opts ...%s.CallOption) (%s, error)", methName, contextPkg, reqArg, clientPkg, respName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *micro) generateClientMethod(pkg, reqServ, servName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) {
|
||||||
|
reqMethod := fmt.Sprintf("%s.%s", servName, method.GetName())
|
||||||
|
useGrpc := g.gen.Param["use_grpc"]
|
||||||
|
if useGrpc != "" {
|
||||||
|
reqMethod = fmt.Sprintf("/%s.%s/%s", pkg, servName, method.GetName())
|
||||||
|
}
|
||||||
|
methName := generator.CamelCase(method.GetName())
|
||||||
|
inType := g.typeName(method.GetInputType())
|
||||||
|
outType := g.typeName(method.GetOutputType())
|
||||||
|
|
||||||
|
servAlias := servName + "Service"
|
||||||
|
|
||||||
|
// strip suffix
|
||||||
|
if strings.HasSuffix(servAlias, "ServiceService") {
|
||||||
|
servAlias = strings.TrimSuffix(servAlias, "Service")
|
||||||
|
}
|
||||||
|
|
||||||
|
g.P("func (c *", unexport(servAlias), ") ", g.generateClientSignature(servName, method), "{")
|
||||||
|
if !method.GetServerStreaming() && !method.GetClientStreaming() {
|
||||||
|
g.P(`req := c.c.NewRequest(c.name, "`, reqMethod, `", in)`)
|
||||||
|
g.P("out := new(", outType, ")")
|
||||||
|
// TODO: Pass descExpr to Invoke.
|
||||||
|
g.P("err := ", `c.c.Call(ctx, req, out, opts...)`)
|
||||||
|
g.P("if err != nil { return nil, err }")
|
||||||
|
g.P("return out, nil")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
streamType := unexport(servAlias) + methName
|
||||||
|
g.P(`req := c.c.NewRequest(c.name, "`, reqMethod, `", &`, inType, `{})`)
|
||||||
|
g.P("stream, err := c.c.Stream(ctx, req, opts...)")
|
||||||
|
g.P("if err != nil { return nil, err }")
|
||||||
|
|
||||||
|
if !method.GetClientStreaming() {
|
||||||
|
g.P("if err := stream.Send(in); err != nil { return nil, err }")
|
||||||
|
// TODO: currently only grpc support CloseSend
|
||||||
|
// g.P("if err := stream.CloseSend(); err != nil { return nil, err }")
|
||||||
|
}
|
||||||
|
|
||||||
|
g.P("return &", streamType, "{stream}, nil")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
genSend := method.GetClientStreaming()
|
||||||
|
genRecv := method.GetServerStreaming()
|
||||||
|
|
||||||
|
// Stream auxiliary types and methods.
|
||||||
|
g.P("type ", servName, "_", methName, "Service interface {")
|
||||||
|
g.P("Context() context.Context")
|
||||||
|
g.P("SendMsg(interface{}) error")
|
||||||
|
g.P("RecvMsg(interface{}) error")
|
||||||
|
g.P("CloseSend() error")
|
||||||
|
g.P("Close() error")
|
||||||
|
|
||||||
|
if genSend {
|
||||||
|
g.P("Send(*", inType, ") error")
|
||||||
|
}
|
||||||
|
if genRecv {
|
||||||
|
g.P("Recv() (*", outType, ", error)")
|
||||||
|
}
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("type ", streamType, " struct {")
|
||||||
|
g.P("stream ", clientPkg, ".Stream")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") CloseSend() error {")
|
||||||
|
g.P("return x.stream.CloseSend()")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") Close() error {")
|
||||||
|
g.P("return x.stream.Close()")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") Context() context.Context {")
|
||||||
|
g.P("return x.stream.Context()")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") SendMsg(m interface{}) error {")
|
||||||
|
g.P("return x.stream.Send(m)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") RecvMsg(m interface{}) error {")
|
||||||
|
g.P("return x.stream.Recv(m)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
if genSend {
|
||||||
|
g.P("func (x *", streamType, ") Send(m *", inType, ") error {")
|
||||||
|
g.P("return x.stream.Send(m)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if genRecv {
|
||||||
|
g.P("func (x *", streamType, ") Recv() (*", outType, ", error) {")
|
||||||
|
g.P("m := new(", outType, ")")
|
||||||
|
g.P("err := x.stream.Recv(m)")
|
||||||
|
g.P("if err != nil {")
|
||||||
|
g.P("return nil, err")
|
||||||
|
g.P("}")
|
||||||
|
g.P("return m, nil")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateServerSignature returns the server-side signature for a method.
|
||||||
|
func (g *micro) generateServerSignature(servName string, method *pb.MethodDescriptorProto) string {
|
||||||
|
origMethName := method.GetName()
|
||||||
|
methName := generator.CamelCase(origMethName)
|
||||||
|
if reservedClientName[methName] {
|
||||||
|
methName += "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqArgs []string
|
||||||
|
ret := "error"
|
||||||
|
reqArgs = append(reqArgs, contextPkg+".Context")
|
||||||
|
|
||||||
|
if !method.GetClientStreaming() {
|
||||||
|
reqArgs = append(reqArgs, "*"+g.typeName(method.GetInputType()))
|
||||||
|
}
|
||||||
|
if method.GetServerStreaming() || method.GetClientStreaming() {
|
||||||
|
reqArgs = append(reqArgs, servName+"_"+generator.CamelCase(origMethName)+"Stream")
|
||||||
|
}
|
||||||
|
if !method.GetClientStreaming() && !method.GetServerStreaming() {
|
||||||
|
reqArgs = append(reqArgs, "*"+g.typeName(method.GetOutputType()))
|
||||||
|
}
|
||||||
|
return methName + "(" + strings.Join(reqArgs, ", ") + ") " + ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *micro) generateServerMethod(servName string, method *pb.MethodDescriptorProto) string {
|
||||||
|
methName := generator.CamelCase(method.GetName())
|
||||||
|
hname := fmt.Sprintf("_%s_%s_Handler", servName, methName)
|
||||||
|
serveType := servName + "Handler"
|
||||||
|
inType := g.typeName(method.GetInputType())
|
||||||
|
outType := g.typeName(method.GetOutputType())
|
||||||
|
|
||||||
|
if !method.GetServerStreaming() && !method.GetClientStreaming() {
|
||||||
|
g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, in *", inType, ", out *", outType, ") error {")
|
||||||
|
g.P("return h.", serveType, ".", methName, "(ctx, in, out)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
return hname
|
||||||
|
}
|
||||||
|
streamType := unexport(servName) + methName + "Stream"
|
||||||
|
g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, stream server.Stream) error {")
|
||||||
|
if !method.GetClientStreaming() {
|
||||||
|
g.P("m := new(", inType, ")")
|
||||||
|
g.P("if err := stream.Recv(m); err != nil { return err }")
|
||||||
|
g.P("return h.", serveType, ".", methName, "(ctx, m, &", streamType, "{stream})")
|
||||||
|
} else {
|
||||||
|
g.P("return h.", serveType, ".", methName, "(ctx, &", streamType, "{stream})")
|
||||||
|
}
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
genSend := method.GetServerStreaming()
|
||||||
|
genRecv := method.GetClientStreaming()
|
||||||
|
|
||||||
|
// Stream auxiliary types and methods.
|
||||||
|
g.P("type ", servName, "_", methName, "Stream interface {")
|
||||||
|
g.P("Context() context.Context")
|
||||||
|
g.P("SendMsg(interface{}) error")
|
||||||
|
g.P("RecvMsg(interface{}) error")
|
||||||
|
g.P("Close() error")
|
||||||
|
|
||||||
|
if genSend {
|
||||||
|
g.P("Send(*", outType, ") error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if genRecv {
|
||||||
|
g.P("Recv() (*", inType, ", error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("type ", streamType, " struct {")
|
||||||
|
g.P("stream ", serverPkg, ".Stream")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") Close() error {")
|
||||||
|
g.P("return x.stream.Close()")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") Context() context.Context {")
|
||||||
|
g.P("return x.stream.Context()")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") SendMsg(m interface{}) error {")
|
||||||
|
g.P("return x.stream.Send(m)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
g.P("func (x *", streamType, ") RecvMsg(m interface{}) error {")
|
||||||
|
g.P("return x.stream.Recv(m)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
|
||||||
|
if genSend {
|
||||||
|
g.P("func (x *", streamType, ") Send(m *", outType, ") error {")
|
||||||
|
g.P("return x.stream.Send(m)")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
if genRecv {
|
||||||
|
g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {")
|
||||||
|
g.P("m := new(", inType, ")")
|
||||||
|
g.P("if err := x.stream.Recv(m); err != nil { return nil, err }")
|
||||||
|
g.P("return m, nil")
|
||||||
|
g.P("}")
|
||||||
|
g.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
return hname
|
||||||
|
}
|
||||||
16
go.mod
16
go.mod
@@ -12,6 +12,7 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/go-sql-driver/mysql v1.9.2
|
github.com/go-sql-driver/mysql v1.9.2
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/consul/api v1.32.1
|
github.com/hashicorp/consul/api v1.32.1
|
||||||
@@ -28,9 +29,11 @@ require (
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/streadway/amqp v1.1.0
|
github.com/streadway/amqp v1.1.0
|
||||||
|
github.com/stretchr/objx v0.5.2
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/test-go/testify v1.1.4
|
github.com/test-go/testify v1.1.4
|
||||||
github.com/urfave/cli/v2 v2.27.6
|
github.com/urfave/cli/v2 v2.27.6
|
||||||
|
github.com/xlab/treeprint v1.2.0
|
||||||
go.etcd.io/bbolt v1.4.0
|
go.etcd.io/bbolt v1.4.0
|
||||||
go.etcd.io/etcd/api/v3 v3.5.21
|
go.etcd.io/etcd/api/v3 v3.5.21
|
||||||
go.etcd.io/etcd/client/v3 v3.5.21
|
go.etcd.io/etcd/client/v3 v3.5.21
|
||||||
@@ -40,16 +43,13 @@ require (
|
|||||||
golang.org/x/crypto v0.37.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/net v0.38.0
|
golang.org/x/net v0.38.0
|
||||||
golang.org/x/sync v0.13.0
|
golang.org/x/sync v0.13.0
|
||||||
google.golang.org/genai v1.12.0
|
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463
|
||||||
google.golang.org/grpc v1.71.1
|
google.golang.org/grpc v1.71.1
|
||||||
google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057
|
google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.120.0 // indirect
|
|
||||||
cloud.google.com/go/auth v0.15.0 // indirect
|
|
||||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
@@ -59,16 +59,10 @@ require (
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/fatih/color v1.16.0 // indirect
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/google/go-tpm v0.9.3 // indirect
|
github.com/google/go-tpm v0.9.3 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
@@ -101,7 +95,6 @@ require (
|
|||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
@@ -110,7 +103,6 @@ require (
|
|||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
golang.org/x/tools v0.31.0 // indirect
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
24
go.sum
24
go.sum
@@ -1,9 +1,3 @@
|
|||||||
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
|
|
||||||
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
|
||||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
|
||||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
|
||||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
|
||||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
@@ -61,8 +55,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
|||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
@@ -87,6 +79,8 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
|
|||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@@ -103,16 +97,8 @@ github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
|
|||||||
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
|
||||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
|
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
|
||||||
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
|
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
|
||||||
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
|
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
|
||||||
@@ -355,6 +341,8 @@ github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmN
|
|||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
|
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
||||||
|
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -371,8 +359,6 @@ go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY
|
|||||||
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
|
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
@@ -510,8 +496,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genai v1.12.0 h1:0JjAdwvEAha9ZpPH5hL6dVG8bpMnRbAMCgv2f2LDnz4=
|
|
||||||
google.golang.org/genai v1.12.0/go.mod h1:HFXR1zT3LCdLxd/NW6IOSCczOYyRAxwaShvYbgPSeVw=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||||
|
|||||||
@@ -327,49 +327,49 @@ func (s *rpcServer) ServeConn(sock transport.Socket) {
|
|||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
|
||||||
// Process the outbound messages from the socket
|
// Process the outbound messages from the socket
|
||||||
go func(psock *socket.Socket) {
|
go func(psock *socket.Socket) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
logger.Log(log.ErrorLevel, "panic recovered in outbound goroutine: ", r)
|
logger.Log(log.ErrorLevel, "panic recovered in outbound goroutine: ", r)
|
||||||
logger.Log(log.ErrorLevel, string(debug.Stack()))
|
logger.Log(log.ErrorLevel, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
// TODO: don't hack this but if its grpc just break out of the stream
|
// TODO: don't hack this but if its grpc just break out of the stream
|
||||||
// We do this because the underlying connection is h2 and its a stream
|
// We do this because the underlying connection is h2 and its a stream
|
||||||
if protocol == "grpc" {
|
if protocol == "grpc" {
|
||||||
if err := sock.Close(); err != nil {
|
if err := sock.Close(); err != nil {
|
||||||
logger.Logf(log.ErrorLevel, "Failed to close socket: %v", err)
|
logger.Logf(log.ErrorLevel, "Failed to close socket: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.deferer(pool, psock, wg)
|
s.deferer(pool, psock, wg)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Get the message from our internal handler/stream
|
// Get the message from our internal handler/stream
|
||||||
m := new(transport.Message)
|
m := new(transport.Message)
|
||||||
if err := psock.Process(m); err != nil {
|
if err := psock.Process(m); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message back over the socket
|
// Send the message back over the socket
|
||||||
if err := sock.Send(m); err != nil {
|
if err := sock.Send(m); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(psock)
|
}(psock)
|
||||||
|
|
||||||
// Serve the request in a go routine as this may be a stream
|
// Serve the request in a go routine as this may be a stream
|
||||||
go func(psock *socket.Socket) {
|
go func(psock *socket.Socket) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
logger.Log(log.ErrorLevel, "panic recovered in serveReq goroutine: ", r)
|
logger.Log(log.ErrorLevel, "panic recovered in serveReq goroutine: ", r)
|
||||||
logger.Log(log.ErrorLevel, string(debug.Stack()))
|
logger.Log(log.ErrorLevel, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
s.deferer(pool, psock, wg)
|
s.deferer(pool, psock, wg)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.serveReq(ctx, msg, &request, &response, rcodec)
|
s.serveReq(ctx, msg, &request, &response, rcodec)
|
||||||
}(psock)
|
}(psock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package transport
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
var http2BufPool = sync.Pool{
|
var http2BufPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return make([]byte, DefaultBufSizeH2)
|
return make([]byte, DefaultBufSizeH2)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHTTP2BufPool() *sync.Pool {
|
func getHTTP2BufPool() *sync.Pool {
|
||||||
return &http2BufPool
|
return &http2BufPool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,51 +165,51 @@ func (h *httpTransportSocket) recvHTTP1(msg *Message) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpTransportSocket) recvHTTP2(msg *Message) error {
|
func (h *httpTransportSocket) recvHTTP2(msg *Message) error {
|
||||||
// only process if the socket is open
|
// only process if the socket is open
|
||||||
select {
|
select {
|
||||||
case <-h.closed:
|
case <-h.closed:
|
||||||
return io.EOF
|
return io.EOF
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer pool for reuse
|
// buffer pool for reuse
|
||||||
var bufPool = getHTTP2BufPool()
|
var bufPool = getHTTP2BufPool()
|
||||||
|
|
||||||
// set max buffer size
|
// set max buffer size
|
||||||
s := h.ht.opts.BuffSizeH2
|
s := h.ht.opts.BuffSizeH2
|
||||||
if s == 0 {
|
if s == 0 {
|
||||||
s = DefaultBufSizeH2
|
s = DefaultBufSizeH2
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bufPool.Get().([]byte)
|
buf := bufPool.Get().([]byte)
|
||||||
if cap(buf) < s {
|
if cap(buf) < s {
|
||||||
buf = make([]byte, s)
|
buf = make([]byte, s)
|
||||||
}
|
}
|
||||||
buf = buf[:s]
|
buf = buf[:s]
|
||||||
|
|
||||||
n, err := h.buf.Read(buf)
|
n, err := h.buf.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bufPool.Put(buf)
|
bufPool.Put(buf)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
msg.Body = make([]byte, n)
|
msg.Body = make([]byte, n)
|
||||||
copy(msg.Body, buf[:n])
|
copy(msg.Body, buf[:n])
|
||||||
}
|
}
|
||||||
bufPool.Put(buf)
|
bufPool.Put(buf)
|
||||||
|
|
||||||
for k, v := range h.r.Header {
|
for k, v := range h.r.Header {
|
||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
msg.Header[k] = v[0]
|
msg.Header[k] = v[0]
|
||||||
} else {
|
} else {
|
||||||
msg.Header[k] = ""
|
msg.Header[k] = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Header[":path"] = h.r.URL.Path
|
msg.Header[":path"] = h.r.URL.Path
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpTransportSocket) sendHTTP1(msg *Message) error {
|
func (h *httpTransportSocket) sendHTTP1(msg *Message) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user