原文链接: gRPC,爆赞

gRPC 这项技术真是太棒了,接口约束严格,性能还高,在 k8s 和很多微服务框架中都有应用。

作为一名程序员,学就对了。

之前用 Python 写过一些 gRPC 服务,现在准备用 Go 来感受一下原汁原味的 gRPC 程序开发。

本文的特点是直接用代码说话,通过开箱即用的完整代码,来介绍 gRPC 的各种使用方法。

代码已经上传到 GitHub,下面正式开始。

介绍

gRPC 是 Google 公司基于 Protobuf 开发的跨语言的开源 RPC 框架。gRPC 基于 HTTP/2 协议设计,可以基于一个 HTTP/2 链接提供多个服务,对于移动设备更加友好。

入门

首先来看一个最简单的 gRPC 服务,第一步是定义 proto 文件,因为 gRPC 也是 C/S 架构,这一步相当于明确接口规范。

proto

syntax = "proto3";

package proto;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
} // The request message containing the user's name.
message HelloRequest {
string name = 1;
} // The response message containing the greetings
message HelloReply {
string message = 1;
}

使用 protoc-gen-go 内置的 gRPC 插件生成 gRPC 代码:

protoc --go_out=plugins=grpc:. helloworld.proto

执行完这个命令之后,会在当前目录生成一个 helloworld.pb.go 文件,文件中分别定义了服务端和客户端的接口:

// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
} // GreeterServer is the server API for Greeter service.
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

接下来就是写服务端和客户端的代码,分别实现对应的接口。

server

package main

import (
"context"
"fmt"
"grpc-server/proto"
"log"
"net" "google.golang.org/grpc"
"google.golang.org/grpc/reflection"
) type greeter struct {
} func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
fmt.Println(req)
reply := &proto.HelloReply{Message: "hello"}
return reply, nil
} func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
} server := grpc.NewServer()
// 注册 grpcurl 所需的 reflection 服务
reflection.Register(server)
// 注册业务服务
proto.RegisterGreeterServer(server, &greeter{}) fmt.Println("grpc server start ...")
if err := server.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

client

package main

import (
"context"
"fmt"
"grpc-client/proto"
"log" "google.golang.org/grpc"
) func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer conn.Close() client := proto.NewGreeterClient(conn)
reply, err := client.SayHello(context.Background(), &proto.HelloRequest{Name: "zhangsan"})
if err != nil {
log.Fatal(err)
}
fmt.Println(reply.Message)
}

这样就完成了最基础的 gRPC 服务的开发,接下来我们就在这个「基础模板」上不断丰富,学习更多特性。

流方式

接下来看看流的方式,顾名思义,数据可以源源不断的发送和接收。

流的话分单向流和双向流,这里我们直接通过双向流来举例。

proto

service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends stream message
rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {}
}

增加一个流函数 SayHelloStream,通过 stream 关键词来指定流特性。

需要重新生成 helloworld.pb.go 文件,这里不再多说。

server

func (*greeter) SayHelloStream(stream proto.Greeter_SayHelloStreamServer) error {
for {
args, err := stream.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return err
} fmt.Println("Recv: " + args.Name)
reply := &proto.HelloReply{Message: "hi " + args.Name} err = stream.Send(reply)
if err != nil {
return err
}
}
}

在「基础模板」上增加 SayHelloStream 函数,其他都不需要变。

client

client := proto.NewGreeterClient(conn)

// 流处理
stream, err := client.SayHelloStream(context.Background())
if err != nil {
log.Fatal(err)
} // 发送消息
go func() {
for {
if err := stream.Send(&proto.HelloRequest{Name: "zhangsan"}); err != nil {
log.Fatal(err)
}
time.Sleep(time.Second)
}
}() // 接收消息
for {
reply, err := stream.Recv()
if err != nil {
if err == io.EOF {
break
}
log.Fatal(err)
}
fmt.Println(reply.Message)
}

通过一个 goroutine 发送消息,主程序的 for 循环接收消息。

执行程序会发现,服务端和客户端都不断有打印输出。

验证器

接下来是验证器,这个需求是很自然会想到的,因为涉及到接口之间的请求,那么对参数进行适当的校验是很有必要的。

在这里我们使用 protoc-gen-govalidators 和 go-grpc-middleware 来实现。

先安装:

go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators

go get github.com/grpc-ecosystem/go-grpc-middleware

接下来修改 proto 文件:

proto

import "github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto";

message HelloRequest {
string name = 1 [
(validator.field) = {regex: "^[z]{2,5}$"}
];
}

在这里对 name 参数进行校验,需要符合正则的要求才可以正常请求。

还有其他验证规则,比如对数字大小进行验证等,这里不做过多介绍。

接下来生成 *.pb.go 文件:

protoc  \
--proto_path=${GOPATH}/pkg/mod \
--proto_path=${GOPATH}/pkg/mod/github.com/gogo/protobuf@v1.3.2 \
--proto_path=. \
--govalidators_out=. --go_out=plugins=grpc:.\
*.proto

执行成功之后,目录下会多一个 helloworld.validator.pb.go 文件。

这里需要特别注意一下,使用之前的简单命令是不行的,需要使用多个 proto_path 参数指定导入 proto 文件的目录。

官方给了两种依赖情况,一个是 google protobuf,一个是 gogo protobuf。我这里使用的是第二种。

即使使用上面的命令,也有可能会遇到这个报错:

Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors

但不要慌,大概率是引用路径的问题,一定要看好自己的安装版本,以及在 GOPATH 中的具体路径。

最后是服务端代码改造:

引入包:

grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"

然后在初始化的时候增加验证器功能:

server := grpc.NewServer(
grpc.UnaryInterceptor(
grpc_middleware.ChainUnaryServer(
grpc_validator.UnaryServerInterceptor(),
),
),
grpc.StreamInterceptor(
grpc_middleware.ChainStreamServer(
grpc_validator.StreamServerInterceptor(),
),
),
)

启动程序之后,我们再用之前的客户端代码来请求,会收到报错:

2021/10/11 18:32:59 rpc error: code = InvalidArgument desc = invalid field Name: value 'zhangsan' must be a string conforming to regex "^[z]{2,5}$"
exit status 1

因为 name: zhangsan 是不符合服务端正则要求的,但是如果传参 name: zzz,就可以正常返回了。

Token 认证

终于到认证环节了,先看 Token 认证方式,然后再介绍证书认证。

先改造服务端,有了上文验证器的经验,那么可以采用同样的方式,写一个拦截器,然后在初始化 server 时候注入。

认证函数:

func Auth(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return fmt.Errorf("missing credentials")
} var user string
var password string if val, ok := md["user"]; ok {
user = val[0]
}
if val, ok := md["password"]; ok {
password = val[0]
} if user != "admin" || password != "admin" {
return grpc.Errorf(codes.Unauthenticated, "invalid token")
} return nil
}

metadata.FromIncomingContext 从上下文读取用户名和密码,然后和实际数据进行比较,判断是否通过认证。

拦截器:

var authInterceptor grpc.UnaryServerInterceptor
authInterceptor = func(
ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (resp interface{}, err error) {
//拦截普通方法请求,验证 Token
err = Auth(ctx)
if err != nil {
return
}
// 继续处理请求
return handler(ctx, req)
}

初始化:

server := grpc.NewServer(
grpc.UnaryInterceptor(
grpc_middleware.ChainUnaryServer(
authInterceptor,
grpc_validator.UnaryServerInterceptor(),
),
),
grpc.StreamInterceptor(
grpc_middleware.ChainStreamServer(
grpc_validator.StreamServerInterceptor(),
),
),
)

除了上文的验证器,又多了 Token 认证拦截器 authInterceptor

最后是客户端改造,客户端需要实现 PerRPCCredentials 接口。

type PerRPCCredentials interface {
// GetRequestMetadata gets the current request metadata, refreshing
// tokens if required. This should be called by the transport layer on
// each request, and the data should be populated in headers or other
// context. If a status code is returned, it will be used as the status
// for the RPC. uri is the URI of the entry point for the request.
// When supported by the underlying implementation, ctx can be used for
// timeout and cancellation.
// TODO(zhaoq): Define the set of the qualified keys instead of leaving
// it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (
map[string]string, error,
)
// RequireTransportSecurity indicates whether the credentials requires
// transport security.
RequireTransportSecurity() bool
}

GetRequestMetadata 方法返回认证需要的必要信息,RequireTransportSecurity 方法表示是否启用安全链接,在生产环境中,一般都是启用的,但为了测试方便,暂时这里不启用了。

实现接口:

type Authentication struct {
User string
Password string
} func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
map[string]string, error,
) {
return map[string]string{"user": a.User, "password": a.Password}, nil
} func (a *Authentication) RequireTransportSecurity() bool {
return false
}

连接:

conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))

好了,现在我们的服务就有 Token 认证功能了。如果用户名或密码错误,客户端就会收到:

2021/10/11 20:39:35 rpc error: code = Unauthenticated desc = invalid token
exit status 1

如果用户名和密码正确,则可以正常返回。

单向证书认证

证书认证分两种方式:

  1. 单向认证
  2. 双向认证

先看一下单向认证方式:

生成证书

首先通过 openssl 工具生成自签名的 SSL 证书。

1、生成私钥:

openssl genrsa -des3 -out server.pass.key 2048

2、去除私钥中密码:

openssl rsa -in server.pass.key -out server.key

3、生成 csr 文件:

openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=beijing/L=beijing/O=grpcdev/OU=grpcdev/CN=example.grpcdev.cn"

4、生成证书:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

再多说一句,分别介绍一下 X.509 证书包含的三个文件:key,csr 和 crt。

  • key: 服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
  • csr: 证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
  • crt: 由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。

gRPC 代码

证书有了之后,剩下的就是改造程序了,首先是服务端代码。

// 证书认证-单向认证
creds, err := credentials.NewServerTLSFromFile("keys/server.crt", "keys/server.key")
if err != nil {
log.Fatal(err)
return
} server := grpc.NewServer(grpc.Creds(creds))

只有几行代码需要修改,很简单,接下来是客户端。

由于是单向认证,不需要为客户端单独生成证书,只需要把服务端的 crt 文件拷贝到客户端对应目录下即可。

// 证书认证-单向认证
creds, err := credentials.NewClientTLSFromFile("keys/server.crt", "example.grpcdev.cn")
if err != nil {
log.Fatal(err)
return
}
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))

好了,现在我们的服务就支持单向证书认证了。

但是还没完,这里可能会遇到一个问题:

2021/10/11 21:32:37 rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
exit status 1

原因是 Go 1.15 开始废弃了 CommonName,推荐使用 SAN 证书。如果想要兼容之前的方式,可以通过设置环境变量的方式支持,如下:

export GODEBUG="x509ignoreCN=0"

但是需要注意,从 Go 1.17 开始,环境变量就不再生效了,必须通过 SAN 方式才行。所以,为了后续的 Go 版本升级,还是早日支持为好。

双向证书认证

最后来看看双向证书认证。

生成带 SAN 的证书

还是先生成证书,但这次有一点不一样,我们需要生成带 SAN 扩展的证书。

什么是 SAN?

SAN(Subject Alternative Name)是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

将默认的 OpenSSL 配置文件拷贝到当前目录。

Linux 系统在:

/etc/pki/tls/openssl.cnf

Mac 系统在:

/System/Library/OpenSSL/openssl.cnf

修改临时配置文件,找到 [ req ] 段落,然后将下面语句的注释去掉。

req_extensions = v3_req # The extensions to add to a certificate request

接着添加以下配置:

[ v3_req ]
# Extensions to add to a certificate request basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names [ alt_names ]
DNS.1 = www.example.grpcdev.cn

[ alt_names ] 位置可以配置多个域名,比如:

[ alt_names ]
DNS.1 = www.example.grpcdev.cn
DNS.2 = www.test.grpcdev.cn

为了测试方便,这里只配置一个域名。

1、生成 ca 证书:

openssl genrsa -out ca.key 2048

openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.grpcdev.com" -days 5000 -out ca.pem

2、生成服务端证书:

# 生成证书
openssl req -new -nodes \
-subj "/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn" \
-config <(cat openssl.cnf \
<(printf "[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn")) \
-keyout server.key \
-out server.csr # 签名证书
openssl x509 -req -days 365000 \
-in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:www.example.grpcdev.cn") \
-out server.pem

3、生成客户端证书:

# 生成证书
openssl req -new -nodes \
-subj "/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn" \
-config <(cat openssl.cnf \
<(printf "[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn")) \
-keyout client.key \
-out client.csr # 签名证书
openssl x509 -req -days 365000 \
-in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:www.example.grpcdev.cn") \
-out client.pem

gRPC 代码

接下来开始修改代码,先看服务端:

// 证书认证-双向认证
// 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
cert, _ := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
// 创建一个新的、空的 CertPool
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("cert/ca.pem")
// 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
// 构建基于 TLS 的 TransportCredentials 选项
creds := credentials.NewTLS(&tls.Config{
// 设置证书链,允许包含一个或多个
Certificates: []tls.Certificate{cert},
// 要求必须校验客户端的证书。可以根据实际情况选用以下参数
ClientAuth: tls.RequireAndVerifyClientCert,
// 设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
ClientCAs: certPool,
})

再看客户端:

// 证书认证-双向认证
// 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
cert, _ := tls.LoadX509KeyPair("cert/client.pem", "cert/client.key")
// 创建一个新的、空的 CertPool
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("cert/ca.pem")
// 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
// 构建基于 TLS 的 TransportCredentials 选项
creds := credentials.NewTLS(&tls.Config{
// 设置证书链,允许包含一个或多个
Certificates: []tls.Certificate{cert},
// 要求必须校验客户端的证书。可以根据实际情况选用以下参数
ServerName: "www.example.grpcdev.cn",
RootCAs: certPool,
})

大功告成。

Python 客户端

前面已经说了,gRPC 是跨语言的,那么,本文最后我们用 Python 写一个客户端,来请求 Go 服务端。

使用最简单的方式来实现:

proto 文件就使用最开始的「基础模板」的 proto 文件:

syntax = "proto3";

package proto;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends stream message
rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {}
} // The request message containing the user's name.
message HelloRequest {
string name = 1;
} // The response message containing the greetings
message HelloReply {
string message = 1;
}

同样的,也需要通过命令行的方式生成 pb.py 文件:

python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ./*.proto

执行成功之后会在目录下生成 helloworld_pb2.py 和 helloworld_pb2_grpc.py 两个文件。

这个过程也可能会报错:

ModuleNotFoundError: No module named 'grpc_tools'

别慌,是缺少包,安装就好:

pip3 install grpcio
pip3 install grpcio-tools

最后看一下 Python 客户端代码:

import grpc

import helloworld_pb2
import helloworld_pb2_grpc def main():
channel = grpc.insecure_channel("127.0.0.1:50051")
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name="zhangsan"))
print(response.message) if __name__ == '__main__':
main()

这样,就可以通过 Python 客户端请求 Go 启的服务端服务了。

总结

本文通过实战角度出发,直接用代码说话,来说明 gRPC 的一些应用。

内容包括简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证。

除此之外,还有其他值得研究的内容,比如超时控制,REST 接口和负载均衡等。以后还会抽时间继续完善剩下这部分内容。

本文中的代码都经过测试验证,可以直接执行,并且已经上传到 GitHub,小伙伴们可以一遍看源码,一遍对照文章内容来学习。


源码地址:

往期文章:

参考文章:

gRPC,爆赞的更多相关文章

  1. gRPC四种模式、认证和授权实战演示,必赞~~~

    前言 上一篇对gRPC进行简单介绍,并通过示例体验了一下开发过程.接下来说说实际开发常用功能,如:gRPC的四种模式.gRPC集成JWT做认证和授权等. 正文 1. gRPC四种模式服务 以下案例演示 ...

  2. 赞!这款国产的SSH工具,好用到爆!

    之前分享 MobaXterm 时 有小伙伴留言说到了 FinalShell 我本来是一脸蒙蔽的 网上搜了一下 它们家的官网 打开官网 我一度怀疑自己进错了网站 简单(jianlou)的排版 像极了上个 ...

  3. 帝吧fb出征是什么原因?帝吧fb出征事情始末 帝吧出征FB打“台独” 台湾网民崩溃:巨人之墙爆了

    帝吧出征FB打"台独" 台湾网民崩溃:巨人之墙爆了 发表时间:2016-01-20 21:08:10 字号:A-AA+ 关键字: 帝吧帝吧出征FB帝吧出征FB打台独台独脸书巨人之墙 ...

  4. GRpc添加客户端的四种方式

    随着微服务的发展,相信越来越多的.net人员也开始接触GRpc这门技术,大家生成GRpc客户端的方式也各不相同,今天给大家介绍一下依据Proto文件生成Rpc客户端的四种方式 前提:需要安装4个Nug ...

  5. 让 gRPC 提供 REST 服务

    让 gRPC 提供 REST 服务 Intro gRPC 是一个高性能.开源和通用的 RPC 框架,面向移动和 HTTP/2 设计. gRPC 基于 HTTP/2 标准设计,带来诸如双向流.流控.头部 ...

  6. gRPC源码分析1-SSL/TLS

    引子 前几天看到微信后台团队分享了TLS相关文章,正好gRPC里TLS数据加密是很重要的一块,于是整理出了这篇文章. 在gRPC里,如果仅仅是用来做后端微服务,可以考虑不加密.本文太长,先给个大纲. ...

  7. 你真的会玩SQL吗?玩爆你的数据报表之存储过程编写(上)

    你真的会玩SQL吗?系列目录 你真的会玩SQL吗?之逻辑查询处理阶段 你真的会玩SQL吗?和平大使 内连接.外连接 你真的会玩SQL吗?三范式.数据完整性 你真的会玩SQL吗?查询指定节点及其所有父节 ...

  8. gRPC源码分析2-Server的建立

    gRPC中,Server.Client共享的Class不是很多,所以我们可以单独的分别讲解Server和Client的源码. 通过第一篇,我们知道对于gRPC来说,建立Server是非常简单的,还记得 ...

  9. gRPC源码分析0-导读

    gRPC是Google开源的新一代RPC框架,官网是http://www.grpc.io.正式发布于2016年8月,技术栈非常的新,基于HTTP/2,netty4.1,proto3.虽然目前在工程化方 ...

  10. POJ 2965. The Pilots Brothers&#39; refrigerator 枚举or爆搜or分治

    The Pilots Brothers' refrigerator Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 22286 ...

随机推荐

  1. pwnable.kr-random

    题目 首先我们要对rand&srand有个总体的看法:srand初始化随机种子,rand产生随机数,下面将详细说明. rand(产生随机数) 表头文件: #include 定义函数 :int ...

  2. 九、Java基础---------面向对象封装、继承、多态

    封装 1.1 基本概念  封装(encapsulation)是面向对象三大特征之一,它是指将对象的状态信心隐藏在对象的内部,不允许外部直接进行访问,而是通过该类提供的方法来实现对内部信息的操作和访问. ...

  3. 内容输出Linux文件系统的的实现:创建一个文件的过程

    题记:写这篇博客要主是加深自己对内容输出的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 考虑上面这个命令: who > userlist 当这个命令完成后,文件系统增加l一 ...

  4. vs2010:【“System.Data.OracleClient.OracleConnection”已过时】警告

    在oracle 安装目录下 找到 Oracle.DataAccess.dll添加引用,然后 using Oracle.DataAccess.Client;其他的都不用动,即可.连接字符串中 如有 用的 ...

  5. unset与unlink

    unset() -- 释放给定的变量 详见->http://www.kuqin.com/php5_doc/function.unset.html unlink() --删除文件    常用于用户 ...

  6. QQ登录的那些坑

    这几天在项目上面实现qq登录的功能,当功能做好后发现,同一个qq号登录之后腾讯返回的openid并不一样....(天啦噜啊~)然后查询文档以及咨询客服才知道注册申请时是有一个固定的套路的(不得不说,如 ...

  7. spring扩展点总结

    NamespaceHandler 通过自定义的NamespaceHandler,配合BeanDefinitionParser,可以完成自定义Bean的组装操作,对于BeanDefinition的数据结 ...

  8. 浅谈CSRF(Cross-site request forgery)跨站请求伪造(写的非常好)

    一 CSRF是什么 CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者X ...

  9. 2017-12-15python全栈9期第二天第七节之练习题

    #!/user/bin/python# -*- coding:utf-8 -*-print(6 or 2 > 1)print(3 or 2 >1 )print(0 or 5 <4)p ...

  10. mongo连接拒绝10061原因

    首先检查Mongo是否启动: 启动 再次检查mongo配置文件是否允许其他人访问,默认路由是否加上 .进入mongodb安装目录的bin目录,新增mongodb.conf文件,输入 bind_ip=0 ...