本文从Fabric-ca源码入手,以newRegisterCommand()
函数为例,简单分析client启动时的过程。Fabric-ca源码可以从github.com下载,本文以v1.4.6为例进行简单分析。
与server相似,本文也是从main.go开始:
// fabric-ca/cmd/fabric-ca-client/main.go
package main
import (
...
)
// The fabric-ca client main
func main() {
if err := command.RunMain(os.Args); err != nil {
os.Exit(1)
}
}
main()
函数只是调用了package command
中的RunMain()
函数,所以接下来我们以RunMain()
函数为起点来简析client端的启动过程,其中包括client端的初始化以及跟server端的初次交互:
// fabric-ca/cmd/fabric-ca-client/command/root.go
package command
import "os"
// RunMain is the fabric-ca client main
func RunMain(args []string) error {
// Save the os.Args
saveOsArgs := os.Args
os.Args = args
// Execute the command
cmdName := ""
if len(args) > 1 {
cmdName = args[1]
}
ccmd := NewCommand(cmdName)
err := ccmd.Execute()
// Restore original os.Args
os.Args = saveOsArgs
return err
}
不难看出,与server类似,都是先通过NewCommand()
函数来添加命令,之后再通过Execute()
来执行操作:
// fabric-ca/cmd/fabric-ca-client/command/clientcmd.go
// ClientCmd encapsulates cobra command that provides command line interface
// for the Fabric CA client and the configuration used by the Fabric CA client
type ClientCmd struct {
// name of the sub command
name string
// rootCmd is the base command for the Hyerledger Fabric CA client
rootCmd *cobra.Command
// My viper instance
myViper *viper.Viper
// cfgFileName is the name of the configuration file
cfgFileName string
// homeDirectory is the location of the client's home directory
homeDirectory string
// clientCfg is the client's configuration
clientCfg *lib.ClientConfig
// cfgAttrs are the attributes specified via flags or env variables
// and translated to Attributes field in registration
cfgAttrs []string
// cfgAttrReqs are the attribute requests specified via flags or env variables
// and translated to the AttrReqs field in enrollment
cfgAttrReqs []string
// cfgCsrNames are the certificate signing request names specified via flags
// or env variables
cfgCsrNames []string
// csrCommonName is the certificate signing request common name specified via the flag
csrCommonName string
// gencrl command argument values
crlParams crlArgs
// revoke command argument values
revokeParams revokeArgs
// profileMode is the profiling mode, cpu or mem or empty
profileMode string
// profileInst is the profiling instance object
profileInst interface {
Stop()
}
// Dynamically configuring identities
dynamicIdentity identityArgs
// Dynamically configuring affiliations
dynamicAffiliation affiliationArgs
// Set to log level
logLevel string
}
// NewCommand returns new ClientCmd ready for running
func NewCommand(name string) *ClientCmd {
c := &ClientCmd{
myViper: viper.New(),
}
c.name = strings.ToLower(name)
c.init()
return c
}
...
// init initializes the ClientCmd instance
// It intializes the cobra root and sub commands and
// registers command flgs with viper
func (c *ClientCmd) init() {
...
c.rootCmd.AddCommand(c.newRegisterCommand(),
newEnrollCmd(c).getCommand(),
c.newReenrollCommand(),
c.newRevokeCommand(),
newGetCAInfoCmd(c).getCommand(),
c.newGenCsrCommand(),
c.newGenCRLCommand(),
c.newIdentityCommand(),
c.newAffiliationCommand(),
createCertificateCommand(c))
c.rootCmd.AddCommand(&cobra.Command{
Use: "version",
Short: "Prints Fabric CA Client version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Print(metadata.GetVersionInfo(cmdName))
},
})
c.registerFlags()
...
}
在NewCommand()
函数中,创建了一个*ClientCmd
的对象,之后,调用该对象的init()
方法。在init()
方法中,首先实例化了*ClientCmd.rootCmd
,其中会要执行checkAndEnableProfiling()
来检查运行环境:
// checkAndEnableProfiling checks for the FABRIC_CA_CLIENT_PROFILE_MODE
// env variable, if it is set to "cpu", cpu profiling is enbled;
// if it is set to "heap", heap profiling is enabled
func (c *ClientCmd) checkAndEnableProfiling() error {
...
}
// registerFlags registers command flags with viper
func (c *ClientCmd) registerFlags() {
...
}
之后会调用AddCommand()
函数来添加newRegisterCommand()
、newEnrollCmd(c).getCommand()
、newReenrollCommand()
、newRevokeCommand()
、newGetCAInfoCmd(c).getCommand()
、newGenCsrCommand()
、newGenCRLCommand()
、newIdentityCommand()
、newAffiliationCommand()
、createCertificateCommand()
和获取client版本的命令。随后执行c.registerFlags()
操作。registerFlags()
函数中主要是注册一些命令行参数,这里就不细究了。
newRegisterCommand()
// fabric-ca/cmd/fabric-ca-client/command/register.go
func (c *ClientCmd) newRegisterCommand() *cobra.Command {
...
}
// The client register main logic
func (c *ClientCmd) runRegister() error {
...
}
在newRegisterCommand()
函数中,实例化了一个*cobra.Command
命令对象,该对象中包含两个命令:ConfigInit()
和runRegister()
。ConfigInit()
命令是为fabric-ca-client命令初始化一些配置,这里不做说明,而runRegister()
中包含客户端注册的主要逻辑:首先实例化一个lib.Client
结构体,之后导入client端的身份凭证client.LoadMyIdentity()
,随后发起注册*Identity.Register()
。
// fabric-ca/lib/client.go
// Client is the fabric-ca client object
type Client struct {
// The client's home directory
HomeDir string `json:"homeDir,omitempty"`
// The client's configuration
Config *ClientConfig
// Denotes if the client object is already initialized
initialized bool
// File and directory paths
keyFile, certFile, idemixCredFile, idemixCredsDir, ipkFile, caCertsDir string
// The crypto service provider (BCCSP)
csp bccsp.BCCSP
// HTTP client associated with this Fabric CA client
httpClient *http.Client
// Public key of Idemix issuer
issuerPublicKey *idemix.IssuerPublicKey
}
// LoadMyIdentity loads the client's identity from disk
func (c *Client) LoadMyIdentity() (*Identity, error) {
...
return c.LoadIdentity(c.keyFile, c.certFile, c.idemixCredFile)
}
// LoadIdentity loads an identity from disk
func (c *Client) LoadIdentity(keyFile, certFile, idemixCredFile string) (*Identity, error) {
...
return c.NewIdentity(creds)
}
// NewIdentity creates a new identity
func (c *Client) NewIdentity(creds []credential.Credential) (*Identity, error) {
...
return NewIdentity(c, name, creds), nil
}
如上,在LoadMyIdentity()
中会先调用Init()
来初始化client,然后再调用LoadIdentity()
函数,从硬盘中导入身份凭证。在LoadIdentity()
中同样会调用Init()
来初始化client,之后会调用NewCredential()
接口来导入x509和idemin格式的证书。证书读取完成后,调用NewIdentity()
来创建新的身份认证。至此,身份导入过程就完成了,接下来就是注册了。
//fabric-ca/lib/identity.go
// Register registers a new identity
// @param req The registration request
func (i *Identity) Register(req *api.RegistrationRequest) (rr *api.RegistrationResponse, err error) {
...
}
在Register()
,会将认证请求序列后,通过Post
请求发送给服务端,并将服务端的应答返回给调用者。
// Post sends arbitrary request body (reqBody) to an endpoint.
// This adds an authorization header which contains the signature
// of this identity over the body and non-signature part of the authorization header.
// The return value is the body of the response.
func (i *Identity) Post(endpoint string, reqBody []byte, result interface{}, queryParam map[string]string) error {
...
}
至此,注册命令就结束了。