Frequently Asked Questions
Tips
If you have any questions or suggestions regarding sponge, feel free to provide feedback through the following channels:
- Submit an issue on GitHub Issues.
- Leave a comment in the section below.
How to set request parameter validation rules?
Creating Web Service Code based on SQL
Answer:
You can specify which parameters are required by manually adding tags. For example, in the
internal/types/xxx_types.go
file. For specific validation rules, see https://github.com/go-playground/validator.
type CreateUserRequest struct {
Name string `json:"name" binding:"min=2"` // User name
Email string `json:"email" binding:"email"` // Email
Password string `json:"password" binding:"md5"` // Password
Gender int `json:"password" binding:""` // Gender, optional
}
binding
specifies the validation rule for that field. If the rule isrequired
, it means the field is mandatory. If thebinding
tag is empty or removed, the field is optional.
Services created using other methods
Answer:
Add annotations in the proto file to set validation rules. For example, in the
api/user/v1/user.proto
file. For specific validation rules, see https://github.com/bufbuild/protoc-gen-validate.
import "validate/validate.proto";
message CreateUserRequest {
string name = 1 [(validate.rules).string.min_len = 2]; // User name
string email = 2 [(validate.rules).string.email = true]; // Email
string password = 3 [(validate.rules).string.min_len = 10]; // Password
int32 gender = 4; // Gender, optional
}
How to use JWT authentication middleware?
Using JWT authentication in Gin
Answer:
Click to view the example of using JWT authentication in Gin.
Using JWT authentication in gRPC
Answer:
Click to view the example of using JWT authentication in gRPC.
Running make proto
command results in an error
Error message type 1
api/types/types.proto: File not found.
api/xxx/v1/xxx.proto:5:1: Import "api/types/types.proto" was not found or had errors.
api/xxx/v1/xxx.proto:246:3: "types.Params" is not defined.
make: *** [Makefile:81: proto] Error 1
Answer:
Switch to the project code directory and run the command
make patch TYPE=types-pb
in the terminal.
Error message type 2
# xxx/internal/cache
internal/cache/xxx.go:40:38: undefined: model.CacheType
make: *** [Makefile:103: run] Error 1
Answer:
Switch to the project code directory and run the command
make patch TYPE=init-mysql
. If you are using a different database type, replacemysql
in the command with the name of the corresponding database type.
Error message type 3
api/xxx/v1/xxx.proto:68:24: Option "(tagger.tags)" unknown. Ensure that your proto definition file imports the proto which defines the option.
make: *** [Makefile:81: proto] Error 1
Answer:
Import the dependency
import "tagger/tagger.proto";
in theapi/xxx/v1/xxx.proto
file.
Error message type 4
api/xxx/v1/xxx.proto:232:24: Option "(validate.rules)" unknown ......
Answer:
Import the dependency
import "validate/validate.proto";
in theapi/xxx/v1/xxx.proto
file.
Error message type 5
a.proto: File not found.
api/demo/v1/a.proto:8:1: Import "a.proto" was not found or had errors.
Reason: Issue with relative path in protoc parameter proto_path
.
Answer:
In b.proto
, change import "a.proto"
to the specific path import "api/demo/v1/a.proto"
.
Error message type 6
protoc-gen-go: unable to determine Go import path for "api/demo/v1/a.proto"
Please specify either:
• a "go_package" option in the .proto source file, or
• a "M" argument on the command line.
Reason: The proto file lacks go_package
.
Answer:
Add option go_package = "demo/api/demo/v1;v1";
in the a.proto
file.
CORS error occurs when requesting API from Swagger UI
Error message:
Failed to fetch.
Possible Reasons:
CORS
Network Failure
URL scheme must be "http" or "https" for CORS request.
Reason:
The default port in the configuration file was modified, or the request is made from a Swagger UI on a different host, while the server's default swagger base URL is still localhost:8080, causing this CORS error.
Answer:
- For
web services created based on sql
, modifylocalhost:8080
in themain.go
file, then run the commandmake docs
.- For
web services created based on protobuf
, modifylocalhost:8080
in each proto file, then run the commandmake proto
.- For temporary testing, modify the
host
field in thedocs/swagger.json
file to the actual service address, then restart the service and refresh the swagger page. Note: Themake docs
ormake proto
commands will replacehost
with the default value.
Running command to generate code fails in zsh
Example error message:
zsh: no matches found: --db-dsn=root:123456@(127.0.0.1:3306)/school
Answer:
If you use the
zsh
shell interpreter to execute the sponge command, the value of the--db-dsn
parameter needs to be enclosed in double quotes. Example:--db-dsn="root:123456@(127.0.0.1:3306)/school"
.
When requesting API from the frontend, the request parameter field values received by the backend are empty
In Create web server based on protobuf
and Create gRPC gateway server Based on Protobuf
, the following are three common scenarios:
Unable to get query parameters from the request
For example, requesting the address http://localhost:8080/api/v1/getUser?name=Tom, the backend gets an empty value for the request parameter name
.
Answer:
Add a tag to the field defined in the corresponding message in the proto file, for example
string name = 1 [(tagger.tags) = "form:/"name/"];
, then execute the commandmake proto
.
Unable to get path parameters
For example, requesting the address http://localhost:8080/api/v1/user/1, where 1 is the value of the path parameter id, the backend cannot get the id value.
Answer:
Define the path parameter in the proto file, for example
/api/v1/user/{id}
, and the defined message must contain the name of the path parameterid
. Thisid
name must have a tag explaining it, for exampleint64 id = 1 [(tagger.tags) = "uri:/"id/""];
, then execute the commandmake proto
.
JSON names of request parameters are inconsistent
The fields of the request parameters use snake_case naming, while the JSON tags of the fields in the xxx.pb.go file use Camel Case.
Request example:
curl -X POST http://localhost:8080/api/v1/user/register -H 'Content-Type: application/json'-d \
'{
"my_email": "string",
}'
The JSON tag value for the field in the xxx.pb.go file is myEmail
, which is inconsistent with the request parameter name.
Answer:
Change the request parameter
my_email
tomyEmail
for normal access. If you must use snake_casemy_email
, you need to add a JSON tag to the corresponding field in the message of the proto file, for examplestring my_email = 1 [json_name ="my_email"]
, to ensure consistency between frontend and backend JSON names, then execute the commandmake proto
.
How to connect to multiple MySQL databases and achieve read-write separation
Answer:
Open the configuration file configs/xxx.yml
(1) Example of one master, multiple replicas:
mysql:
dsn: "root:123456@(127.0.0.1:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true
maxIdleConns: 3
maxOpenConns: 100
connMaxLifetime: 30
slavesDsn:
- "root:123456@(192.168.3.38:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
- "root:123456@(192.168.3.39:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
(2) Example of multiple masters, multiple replicas:
mysql:
dsn: "root:123456@(127.0.0.1:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true
maxIdleConns: 3
maxOpenConns: 100
connMaxLifetime: 30
slavesDsn:
- "root:123456@(192.168.3.38:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
- "root:123456@(192.168.3.39:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
mastersDsn:
- "root:123456@(127.0.0.1:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
- "root:123456@(192.168.3.40:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
ID field name inconsistency issue
Reason:
The ID field name generated by sponge is ID or xxxID, while the field name in the code generated by the protoc plugin is Id or xxxId. Although they represent the same field, there is an inconsistency.
Answer:
Note that you cannot use
copier
for copying; you must assign this field manually and individually.
How to use Service Registration and Discovery
Notes:
- When testing API in the goland IDE, stop the proxy configured in the golang IDE.
- When starting the service in the terminal, stop using network proxy.
Service configuration:
app:
# Do not write 127.0.0.1, write the server's real IP or domain name. If the service registration and discovery platform (etcd, consul, nacos) is not deployed locally, the scheduled monitoring check host will fail.
host: "192.168.3.79"
registryDiscoveryType: "consul" # Can fill in etcd, consul, nacos. Must fill in configuration, e.g., consul
# If connecting to other gRPC services in this service and using service discovery, configure as follows:
grpcClient:
- name: "user" # Required, used for service discovery
host: "127.0.0.1" # Optional
port: 36790 # Optional
registryDiscoveryType: "consul" # Can fill in etcd, consul, nacos. Must fill in configuration, e.g., consul
If using DTM with service registration and discovery, the real IP or domain name should also be written.
MicroService:
EndPoint: 'grpc://192.168.3.79:36790'
When using nacos for service registration and discovery, the namespaceID
value must be consistent between the registration server and the service discovery client configurations.
For example, if gRPC service A wants to register its real address with nacos, the nacos configuration under service A's configuration file is:
nacosRd:
ipAddr: "127.0.0.1"
port: 8848
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
Another gRPC service B needs to obtain service A's real address by name. The nacos configuration under service B's configuration file is:
nacosRd:
ipAddr: "127.0.0.1"
port: 8848
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
The namespaceID
values in the nacos configurations for service A and service B must be the same. They can both be empty simultaneously.
How to get profiles from a service running online to analyze performance locally
The services generated by sponge support collecting profiles through http api and system signals.
Collect profile via HTTP API
The http api function needs to be enabled. Set
enableHTTPProfile: true
in the yaml file in theconfigs
directory. The default route is/debug/pprof
.
Collect profile via system signals
# View service pid by name (second column)
ps aux | grep service_name
# Send signal to service
kill -trap pid_value
After the service receives the system signal notification, it starts collecting profiles and saves them to the /tmp/service_name_profile
directory. The default collection duration is 60 seconds, after which profiling automatically stops. If you only want to collect for 30 seconds, send the first signal to start collection, wait approximately 30 seconds, and then send the second signal to stop profiling (similar to a switch). By default, six types of profiles are collected: cpu, memory, goroutine, block, mutex, and threadcreate. The file format is datetime_pid_service_name_profiletype.out
.
Adaptive profile collection
Resource statistics need to be enabled. Set
enableStat: true
in the yaml file in theconfigs
directory.
This is achieved by combining profile collection via system signals with the resource statistics alerting function. Alert conditions:
- Record the program's CPU usage rate 3 consecutive times (default once per minute). An alert is triggered when the average usage rate for these 3 times exceeds 80%.
- Record the program's physical memory usage rate 3 times (default once per minute). An alert is triggered when the average usage rate for these 3 times exceeds 80% of the system memory.
- If the alert threshold is continuously exceeded, an alert is issued every 15 minutes by default.
The collected profiles are saved to the /tmp/service_name_profile
directory.
In a Web Service created based on Protobuf, how to get gin.Context
in the handler?
Set the selector:
"[ctx]"
flag in the proto file, indicating that this route will pass throughgin.Context
to the handler. ParameterShouldBinding
will still be executed internally. Proto file example:// Login rpc Login(LoginRequest) returns (LoginReply) { option (google.api.http) = { post: "/api/v1/auth/login" body: "*" selector: "[ctx]" }; }
Execute the command
make proto
to generate code, then getgin.Context
in the handler. Example:// Login login func (h *userHandler) Login(ctx context.Context, req *userV1.LoginRequest) (*userV1.LoginReply, error) { c, ctx := middleware.AdaptCtx(ctx) // Variable c is gin.Context // ...... }
In a Web Service created based on Protobuf, how to upload files?
Set the selector:
"[no_bind]"
flag in the proto file, indicating that this route is set to pass throughgin.Context
and skip theShouldBinding
parameters. Proto file example:// Upload file rpc Upload(UploadRequest) returns (UploadReply) { option (google.api.http) = { post: "/api/v1/upload" body: "*" selector: "[no_bind]" }; }
Execute the command
make proto
to generate code, then getgin.Context
in the handler. Example:// Upload upload file func (h *userHandler) Upload(ctx context.Context, req *userV1.UploadRequest) (*userV1.UploadReply, error) { c, ctx := middleware.AdaptCtx(ctx) // Variable c is gin.Context // Handle file upload logic }
In a Web Service created based on Protobuf, how to return non-JSON structured data, such as strings, images, HTTP redirects?
Set the
selector: "[no_bind]"
flag in the proto file, indicating that this route is set to pass throughgin.Context
and skip theShouldBinding
parameters. Proto file example:rpc Foobar(FoobarRequest) returns (FoobarReply) { option (google.api.http) = { post: "/api/v1/foobar" body: "*" selector: "[no_bind]" }; }
Execute the command
make proto
to generate code, getgin.Context
in the handler, and then handle the file upload logic. Example:func (h *userHandler) Foobar(ctx context.Context, req *userV1.FoobarRequest) (*userV1.FoobarReply, error) { c, ctx := middleware.AdaptCtx(ctx) // Variable c is gin.Context // 1. Bind parameters (if req data is needed, bind parameters here) // 2. Handle logic // 3. If returning non-JSON structured data or redirecting, you must skip returning structured data // c.String(http.StatusOK, "hello world") // Return string return nil, ecode.SkipResponse // Skip returning structured data }