1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-26 02:51:28 +00:00

Merge pull request #402 from fuxiaohei/master

add api comments in file memzipfile.go,reload.go,router.go,template.go and templatefunc.go
This commit is contained in:
astaxie 2013-12-21 03:55:10 -08:00
commit 8ba6dbb9a0
6 changed files with 105 additions and 52 deletions

4
app.go
View File

@ -61,7 +61,7 @@ func (app *App) Run() {
if nil != err { if nil != err {
BeeLogger.Critical("ResolveTCPAddr:", err) BeeLogger.Critical("ResolveTCPAddr:", err)
} }
l, err = GetInitListner(laddr) l, err = GetInitListener(laddr)
theStoppable = newStoppable(l) theStoppable = newStoppable(l)
err = server.Serve(theStoppable) err = server.Serve(theStoppable)
theStoppable.wg.Wait() theStoppable.wg.Wait()
@ -118,7 +118,7 @@ func (app *App) AutoRouter(c ControllerInterface) *App {
return app return app
} }
// UrlFor does another controller handler with params in current context. // UrlFor creates a url with another registered controller handler with params.
// The endpoint is formed as path.controller.name to defined the controller method which will run. // The endpoint is formed as path.controller.name to defined the controller method which will run.
// The values need key-pair data to assign into controller method. // The values need key-pair data to assign into controller method.
func (app *App) UrlFor(endpoint string, values ...string) string { func (app *App) UrlFor(endpoint string, values ...string) string {

View File

@ -16,7 +16,8 @@ import (
var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo) var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo)
//TODO: 加锁保证数据完整性 // OpenMemZipFile returns MemFile object with a compressed static file.
// it's used for serve static file if gzip enable.
func OpenMemZipFile(path string, zip string) (*MemFile, error) { func OpenMemZipFile(path string, zip string) (*MemFile, error) {
osfile, e := os.Open(path) osfile, e := os.Open(path)
if e != nil { if e != nil {
@ -86,6 +87,8 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) {
return &MemFile{fi: cfi, offset: 0}, nil return &MemFile{fi: cfi, offset: 0}, nil
} }
// MemFileInfo contains a compressed file bytes and file information.
// it implements os.FileInfo interface.
type MemFileInfo struct { type MemFileInfo struct {
os.FileInfo os.FileInfo
modTime time.Time modTime time.Time
@ -94,49 +97,62 @@ type MemFileInfo struct {
fileSize int64 fileSize int64
} }
// Name returns the compressed filename.
func (fi *MemFileInfo) Name() string { func (fi *MemFileInfo) Name() string {
return fi.Name() return fi.Name()
} }
// Size returns the raw file content size, not compressed size.
func (fi *MemFileInfo) Size() int64 { func (fi *MemFileInfo) Size() int64 {
return fi.contentSize return fi.contentSize
} }
// Mode returns file mode.
func (fi *MemFileInfo) Mode() os.FileMode { func (fi *MemFileInfo) Mode() os.FileMode {
return fi.Mode() return fi.Mode()
} }
// ModTime returns the last modified time of raw file.
func (fi *MemFileInfo) ModTime() time.Time { func (fi *MemFileInfo) ModTime() time.Time {
return fi.modTime return fi.modTime
} }
// IsDir returns the compressing file is a directory or not.
func (fi *MemFileInfo) IsDir() bool { func (fi *MemFileInfo) IsDir() bool {
return fi.IsDir() return fi.IsDir()
} }
// return nil. implement the os.FileInfo interface method.
func (fi *MemFileInfo) Sys() interface{} { func (fi *MemFileInfo) Sys() interface{} {
return nil return nil
} }
// MemFile contains MemFileInfo and bytes offset when reading.
// it implements io.Reader,io.ReadCloser and io.Seeker.
type MemFile struct { type MemFile struct {
fi *MemFileInfo fi *MemFileInfo
offset int64 offset int64
} }
// Close memfile.
func (f *MemFile) Close() error { func (f *MemFile) Close() error {
return nil return nil
} }
// Get os.FileInfo of memfile.
func (f *MemFile) Stat() (os.FileInfo, error) { func (f *MemFile) Stat() (os.FileInfo, error) {
return f.fi, nil return f.fi, nil
} }
// read os.FileInfo of files in directory of memfile.
// it returns empty slice.
func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) { func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) {
infos := []os.FileInfo{} infos := []os.FileInfo{}
return infos, nil return infos, nil
} }
// Read bytes from the compressed file bytes.
func (f *MemFile) Read(p []byte) (n int, err error) { func (f *MemFile) Read(p []byte) (n int, err error) {
if len(f.fi.content)-int(f.offset) >= len(p) { if len(f.fi.content)-int(f.offset) >= len(p) {
n = len(p) n = len(p)
@ -152,6 +168,7 @@ func (f *MemFile) Read(p []byte) (n int, err error) {
var errWhence = errors.New("Seek: invalid whence") var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset") var errOffset = errors.New("Seek: invalid offset")
// Read bytes from the compressed file bytes by seeker.
func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) { func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
switch whence { switch whence {
default: default:
@ -169,8 +186,9 @@ func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
return f.offset, nil return f.offset, nil
} }
//返回: gzip, deflate, 优先gzip // GetAcceptEncodingZip returns accept encoding format in http header.
//返回空, 表示不zip // zip is first, then deflate if both accepted.
// If no accepted, return empty string.
func GetAcceptEncodingZip(r *http.Request) string { func GetAcceptEncodingZip(r *http.Request) string {
ss := r.Header.Get("Accept-Encoding") ss := r.Header.Get("Accept-Encoding")
ss = strings.ToLower(ss) ss = strings.ToLower(ss)
@ -181,8 +199,10 @@ func GetAcceptEncodingZip(r *http.Request) string {
} else { } else {
return "" return ""
} }
return ""
} }
// CloseZWriter closes the io.Writer after compressing static file.
func CloseZWriter(zwriter io.Writer) { func CloseZWriter(zwriter io.Writer) {
if zwriter == nil { if zwriter == nil {
return return

View File

@ -16,6 +16,7 @@ import (
) )
const ( const (
// An environment variable when restarting application http listener.
FDKey = "BEEGO_HOT_FD" FDKey = "BEEGO_HOT_FD"
) )
@ -32,6 +33,7 @@ type conn struct {
lock sync.Mutex lock sync.Mutex
} }
// Close current processing connection.
func (c conn) Close() error { func (c conn) Close() error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
@ -67,6 +69,8 @@ func newStoppable(l net.Listener) (sl *stoppableListener) {
return return
} }
// Set stopped Listener to accept requests again.
// it returns the accepted and closable connection or error.
func (sl *stoppableListener) Accept() (c net.Conn, err error) { func (sl *stoppableListener) Accept() (c net.Conn, err error) {
c, err = sl.Listener.Accept() c, err = sl.Listener.Accept()
if err != nil { if err != nil {
@ -80,6 +84,7 @@ func (sl *stoppableListener) Accept() (c net.Conn, err error) {
return return
} }
// Listener waits signal to kill or interrupt then restart.
func WaitSignal(l net.Listener) error { func WaitSignal(l net.Listener) error {
ch := make(chan os.Signal, 1) ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, os.Kill) signal.Notify(ch, os.Interrupt, os.Kill)
@ -101,6 +106,7 @@ func WaitSignal(l net.Listener) error {
return nil // It'll never get here. return nil // It'll never get here.
} }
// Kill current running os process.
func CloseSelf() error { func CloseSelf() error {
ppid := os.Getpid() ppid := os.Getpid()
if ppid == 1 { // init provided sockets, for example systemd if ppid == 1 { // init provided sockets, for example systemd
@ -140,7 +146,8 @@ func Restart(l net.Listener) error {
return nil return nil
} }
func GetInitListner(tcpaddr *net.TCPAddr) (l net.Listener, err error) { // Get current net.Listen in running process.
func GetInitListener(tcpaddr *net.TCPAddr) (l net.Listener, err error) {
countStr := os.Getenv(FDKey) countStr := os.Getenv(FDKey)
if countStr == "" { if countStr == "" {
return net.ListenTCP("tcp", tcpaddr) return net.ListenTCP("tcp", tcpaddr)

View File

@ -19,6 +19,7 @@ import (
) )
const ( const (
// default filter execution points
BeforeRouter = iota BeforeRouter = iota
AfterStatic AfterStatic
BeforeExec BeforeExec
@ -27,6 +28,7 @@ const (
) )
var ( var (
// supported http methods.
HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head"} HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head"}
) )
@ -39,6 +41,7 @@ type controllerInfo struct {
hasMethod bool hasMethod bool
} }
// ControllerRegistor containers registered router rules, controller handlers and filters.
type ControllerRegistor struct { type ControllerRegistor struct {
routers []*controllerInfo // regexp router storage routers []*controllerInfo // regexp router storage
fixrouters []*controllerInfo // fixed router storage fixrouters []*controllerInfo // fixed router storage
@ -48,6 +51,7 @@ type ControllerRegistor struct {
autoRouter map[string]map[string]reflect.Type //key:controller key:method value:reflect.type autoRouter map[string]map[string]reflect.Type //key:controller key:method value:reflect.type
} }
// NewControllerRegistor returns a new ControllerRegistor.
func NewControllerRegistor() *ControllerRegistor { func NewControllerRegistor() *ControllerRegistor {
return &ControllerRegistor{ return &ControllerRegistor{
routers: make([]*controllerInfo, 0), routers: make([]*controllerInfo, 0),
@ -56,15 +60,16 @@ func NewControllerRegistor() *ControllerRegistor {
} }
} }
//methods support like this: // Add controller handler and pattern rules to ControllerRegistor.
//default methods is the same name as method // usage:
//Add("/user",&UserController{}) // default methods is the same name as method
//Add("/api/list",&RestController{},"*:ListFood") // Add("/user",&UserController{})
//Add("/api/create",&RestController{},"post:CreateFood") // Add("/api/list",&RestController{},"*:ListFood")
//Add("/api/update",&RestController{},"put:UpdateFood") // Add("/api/create",&RestController{},"post:CreateFood")
//Add("/api/delete",&RestController{},"delete:DeleteFood") // Add("/api/update",&RestController{},"put:UpdateFood")
//Add("/api",&RestController{},"get,post:ApiFunc") // Add("/api/delete",&RestController{},"delete:DeleteFood")
//Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") // Add("/api",&RestController{},"get,post:ApiFunc")
// Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) { func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) {
parts := strings.Split(pattern, "/") parts := strings.Split(pattern, "/")
@ -210,11 +215,11 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM
} }
} }
// add auto router to controller // Add auto router to ControllerRegistor.
// example beego.AddAuto(&MainContorlller{}) // example beego.AddAuto(&MainContorlller{}),
// MainController has method List and Page // MainController has method List and Page.
// you can visit the url /main/list to exec List function // visit the url /main/list to exec List function
// /main/page to exec Page function // /main/page to exec Page function.
func (p *ControllerRegistor) AddAuto(c ControllerInterface) { func (p *ControllerRegistor) AddAuto(c ControllerInterface) {
p.enableAuto = true p.enableAuto = true
reflectVal := reflect.ValueOf(c) reflectVal := reflect.ValueOf(c)
@ -231,6 +236,8 @@ func (p *ControllerRegistor) AddAuto(c ControllerInterface) {
} }
} }
// [Deprecated] use InsertFilter.
// Add FilterFunc with pattern for action.
func (p *ControllerRegistor) AddFilter(pattern, action string, filter FilterFunc) { func (p *ControllerRegistor) AddFilter(pattern, action string, filter FilterFunc) {
mr := buildFilter(pattern, filter) mr := buildFilter(pattern, filter)
switch action { switch action {
@ -248,12 +255,15 @@ func (p *ControllerRegistor) AddFilter(pattern, action string, filter FilterFunc
p.enableFilter = true p.enableFilter = true
} }
// Add a FilterFunc with pattern rule and action constant.
func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc) { func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc) {
mr := buildFilter(pattern, filter) mr := buildFilter(pattern, filter)
p.filters[pos] = append(p.filters[pos], mr) p.filters[pos] = append(p.filters[pos], mr)
p.enableFilter = true p.enableFilter = true
} }
// UrlFor does another controller handler in this request function.
// it can access any controller method.
func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
paths := strings.Split(endpoint, ".") paths := strings.Split(endpoint, ".")
if len(paths) <= 1 { if len(paths) <= 1 {
@ -369,7 +379,7 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
return "" return ""
} }
// main function to serveHTTP // Implement http.Handler interface.
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
@ -753,8 +763,8 @@ Admin:
} }
} }
// there always should be error handler that sets error code accordingly for all unhandled errors // there always should be error handler that sets error code accordingly for all unhandled errors.
// in order to have custom UI for error page it's necessary to override "500" error // in order to have custom UI for error page it's necessary to override "500" error.
func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.ResponseWriter, r *http.Request) { func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.ResponseWriter, r *http.Request) {
handler := middleware.SimpleServerError handler := middleware.SimpleServerError
ok := true ok := true
@ -771,6 +781,9 @@ func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.Resp
return handler return handler
} }
// returns method name from request header or form field.
// sometimes browsers can't create PUT and DELETE request.
// set a form field "_method" instead.
func (p *ControllerRegistor) getRunMethod(method string, context *beecontext.Context, router *controllerInfo) string { func (p *ControllerRegistor) getRunMethod(method string, context *beecontext.Context, router *controllerInfo) string {
method = strings.ToLower(method) method = strings.ToLower(method)
if method == "post" && strings.ToLower(context.Input.Query("_method")) == "put" { if method == "post" && strings.ToLower(context.Input.Query("_method")) == "put" {
@ -806,6 +819,7 @@ func (w *responseWriter) Header() http.Header {
return w.writer.Header() return w.writer.Header()
} }
// Init content-length header.
func (w *responseWriter) InitHeadContent(contentlength int64) { func (w *responseWriter) InitHeadContent(contentlength int64) {
if w.contentEncoding == "gzip" { if w.contentEncoding == "gzip" {
w.Header().Set("Content-Encoding", "gzip") w.Header().Set("Content-Encoding", "gzip")
@ -817,14 +831,15 @@ func (w *responseWriter) InitHeadContent(contentlength int64) {
} }
// Write writes the data to the connection as part of an HTTP reply, // Write writes the data to the connection as part of an HTTP reply,
// and sets `started` to true // and sets `started` to true.
// started means the response has sent out.
func (w *responseWriter) Write(p []byte) (int, error) { func (w *responseWriter) Write(p []byte) (int, error) {
w.started = true w.started = true
return w.writer.Write(p) return w.writer.Write(p)
} }
// WriteHeader sends an HTTP response header with status code, // WriteHeader sends an HTTP response header with status code,
// and sets `started` to true // and sets `started` to true.
func (w *responseWriter) WriteHeader(code int) { func (w *responseWriter) WriteHeader(code int) {
w.status = code w.status = code
w.started = true w.started = true

View File

@ -17,6 +17,7 @@ import (
var ( var (
beegoTplFuncMap template.FuncMap beegoTplFuncMap template.FuncMap
// beego template caching map ans supported template file extensions.
BeeTemplates map[string]*template.Template BeeTemplates map[string]*template.Template
BeeTemplateExt []string BeeTemplateExt []string
) )
@ -50,7 +51,7 @@ func init() {
beegoTplFuncMap["urlfor"] = UrlFor // != beegoTplFuncMap["urlfor"] = UrlFor // !=
} }
// AddFuncMap let user to register a func in the template // AddFuncMap let user to register a func in the template.
func AddFuncMap(key string, funname interface{}) error { func AddFuncMap(key string, funname interface{}) error {
beegoTplFuncMap[key] = funname beegoTplFuncMap[key] = funname
return nil return nil
@ -88,6 +89,7 @@ func (self *templatefile) visit(paths string, f os.FileInfo, err error) error {
return nil return nil
} }
// return this path has supported template extension of beego or not.
func HasTemplateExt(paths string) bool { func HasTemplateExt(paths string) bool {
for _, v := range BeeTemplateExt { for _, v := range BeeTemplateExt {
if strings.HasSuffix(paths, "."+v) { if strings.HasSuffix(paths, "."+v) {
@ -97,6 +99,7 @@ func HasTemplateExt(paths string) bool {
return false return false
} }
// add new extension for template.
func AddTemplateExt(ext string) { func AddTemplateExt(ext string) {
for _, v := range BeeTemplateExt { for _, v := range BeeTemplateExt {
if v == ext { if v == ext {
@ -106,6 +109,8 @@ func AddTemplateExt(ext string) {
BeeTemplateExt = append(BeeTemplateExt, ext) BeeTemplateExt = append(BeeTemplateExt, ext)
} }
// build all template files in a directory.
// it makes beego can render any template file in view directory.
func BuildTemplate(dir string) error { func BuildTemplate(dir string) error {
if _, err := os.Stat(dir); err != nil { if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {

View File

@ -12,7 +12,7 @@ import (
"time" "time"
) )
// Substr() return the substr from start to length // Substr returns the substr from start to length.
func Substr(s string, start, length int) string { func Substr(s string, start, length int) string {
bt := []rune(s) bt := []rune(s)
if start < 0 { if start < 0 {
@ -27,7 +27,7 @@ func Substr(s string, start, length int) string {
return string(bt[start:end]) return string(bt[start:end])
} }
// Html2str() returns escaping text convert from html // Html2str returns escaping text convert from html.
func Html2str(html string) string { func Html2str(html string) string {
src := string(html) src := string(html)
@ -60,6 +60,7 @@ func DateFormat(t time.Time, layout string) (datestring string) {
return return
} }
// DateFormat pattern rules.
var DatePatterns = []string{ var DatePatterns = []string{
// year // year
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
@ -100,14 +101,14 @@ var DatePatterns = []string{
"r", time.RFC1123Z, "r", time.RFC1123Z,
} }
// Parse Date use PHP time format // Parse Date use PHP time format.
func DateParse(dateString, format string) (time.Time, error) { func DateParse(dateString, format string) (time.Time, error) {
replacer := strings.NewReplacer(DatePatterns...) replacer := strings.NewReplacer(DatePatterns...)
format = replacer.Replace(format) format = replacer.Replace(format)
return time.ParseInLocation(format, dateString, time.Local) return time.ParseInLocation(format, dateString, time.Local)
} }
// Date takes a PHP like date func to Go's time format // Date takes a PHP like date func to Go's time format.
func Date(t time.Time, format string) string { func Date(t time.Time, format string) string {
replacer := strings.NewReplacer(DatePatterns...) replacer := strings.NewReplacer(DatePatterns...)
format = replacer.Replace(format) format = replacer.Replace(format)
@ -115,7 +116,7 @@ func Date(t time.Time, format string) string {
} }
// Compare is a quick and dirty comparison function. It will convert whatever you give it to strings and see if the two values are equal. // Compare is a quick and dirty comparison function. It will convert whatever you give it to strings and see if the two values are equal.
// Whitespace is trimmed. Used by the template parser as "eq" // Whitespace is trimmed. Used by the template parser as "eq".
func Compare(a, b interface{}) (equal bool) { func Compare(a, b interface{}) (equal bool) {
equal = false equal = false
if strings.TrimSpace(fmt.Sprintf("%v", a)) == strings.TrimSpace(fmt.Sprintf("%v", b)) { if strings.TrimSpace(fmt.Sprintf("%v", a)) == strings.TrimSpace(fmt.Sprintf("%v", b)) {
@ -124,10 +125,12 @@ func Compare(a, b interface{}) (equal bool) {
return return
} }
// Convert string to template.HTML type.
func Str2html(raw string) template.HTML { func Str2html(raw string) template.HTML {
return template.HTML(raw) return template.HTML(raw)
} }
// Htmlquote returns quoted html string.
func Htmlquote(src string) string { func Htmlquote(src string) string {
//HTML编码为实体符号 //HTML编码为实体符号
/* /*
@ -150,6 +153,7 @@ func Htmlquote(src string) string {
return strings.TrimSpace(text) return strings.TrimSpace(text)
} }
// Htmlunquote returns unquoted html string.
func Htmlunquote(src string) string { func Htmlunquote(src string) string {
//实体符号解释为HTML //实体符号解释为HTML
/* /*
@ -174,13 +178,14 @@ func Htmlunquote(src string) string {
return strings.TrimSpace(text) return strings.TrimSpace(text)
} }
// This will reference the index function local to the current blueprint: // UrlFor returns url string with another registered controller handler with params.
// usage:
// UrlFor(".index") // UrlFor(".index")
// ... print UrlFor("index") // print UrlFor("index")
// ... print UrlFor("login") // print UrlFor("login")
// ... print UrlFor("login", "next","/"") // print UrlFor("login", "next","/"")
// ... print UrlFor("profile", "username","John Doe") // print UrlFor("profile", "username","John Doe")
// ... // result:
// / // /
// /login // /login
// /login?next=/ // /login?next=/
@ -189,7 +194,7 @@ func UrlFor(endpoint string, values ...string) string {
return BeeApp.UrlFor(endpoint, values...) return BeeApp.UrlFor(endpoint, values...)
} }
//This can be changed to a better name // returns script tag with src string.
func AssetsJs(src string) template.HTML { func AssetsJs(src string) template.HTML {
text := string(src) text := string(src)
@ -198,7 +203,7 @@ func AssetsJs(src string) template.HTML {
return template.HTML(text) return template.HTML(text)
} }
//This can be changed to a better name // returns stylesheet link tag with str string.
func AssetsCss(src string) template.HTML { func AssetsCss(src string) template.HTML {
text := string(src) text := string(src)
@ -207,7 +212,7 @@ func AssetsCss(src string) template.HTML {
return template.HTML(text) return template.HTML(text)
} }
// parse form values to struct via tag // parse form values to struct via tag.
func ParseForm(form url.Values, obj interface{}) error { func ParseForm(form url.Values, obj interface{}) error {
objT := reflect.TypeOf(obj) objT := reflect.TypeOf(obj)
objV := reflect.ValueOf(obj) objV := reflect.ValueOf(obj)
@ -295,7 +300,8 @@ var unKind = map[reflect.Kind]bool{
reflect.UnsafePointer: true, reflect.UnsafePointer: true,
} }
// obj must be a struct pointer // render object to form html.
// obj must be a struct pointer.
func RenderForm(obj interface{}) template.HTML { func RenderForm(obj interface{}) template.HTML {
objT := reflect.TypeOf(obj) objT := reflect.TypeOf(obj)
objV := reflect.ValueOf(obj) objV := reflect.ValueOf(obj)