package shared import ( "context" "encoding/json" "errors" "flag" "fmt" "io" "io/ioutil" "net/http" "os" "os/signal" "reflect" "runtime/debug" "strconv" "strings" "sync/atomic" "syscall" ) const ( // HTTPStatusReloginRequired : http status를 이걸 받으면 클라이언트는 로그아웃하고 로그인 화면으로 돌아가야 한다. HTTPStatusReloginRequired = 599 // HTTPStatusReloginRequiredDupID : HTTPStatusReloginRequiredDupID = 598 // HTTPStatusPlayerBlocked : HTTPStatusPlayerBlocked = 597 ) type ShutdownFlag int32 const ( ShutdownFlagRunning = ShutdownFlag(0) ShutdownFlagTerminating = ShutdownFlag(1) ShutdownFlagRestarting = ShutdownFlag(2) ShutdownFlagIdle = ShutdownFlag(3) ) type functionCallContext struct { Method string Args []interface{} } // RPCReturnType : RPC 호출 가능한 함수가 유일하게 리턴할 수 있는 타입 type RPCReturnType interface { Serialize(io.Writer) error Value() interface{} Error() error } type rpcReturnTypeImpl struct { value reflect.Value err error serialized []byte } // Bytes : RPCReturnType.Serialize 구현 func (r rpcReturnTypeImpl) Serialize(w io.Writer) (err error) { err = r.err if err != nil { return err } if len(r.serialized) > 0 { w.Write(r.serialized) return nil } if !r.value.IsValid() { return nil } switch r.value.Kind() { case reflect.String: _, err = w.Write([]byte(r.value.String())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: _, err = w.Write([]byte(fmt.Sprintf("%d", r.value.Int()))) case reflect.Float32, reflect.Float64: _, err = w.Write([]byte(fmt.Sprintf("%f", r.value.Float()))) case reflect.Slice: switch r.value.Type().Elem().Kind() { case reflect.Uint8: _, err = w.Write(r.value.Bytes()) default: var conv []interface{} for i := 0; i < r.value.Len(); i++ { conv = append(conv, r.value.Index(i).Interface()) } if len(conv) == 0 { _, err = w.Write([]byte("[]")) } else { err = json.NewEncoder(w).Encode(conv) } } case reflect.Interface, reflect.Struct, reflect.Map: err = json.NewEncoder(w).Encode(r.value.Interface()) case reflect.Ptr: if !r.value.IsNil() { err = json.NewEncoder(w).Encode(r.value.Interface()) } } return err } // Error : RPCReturnType.Error 구현 func (r rpcReturnTypeImpl) Error() error { return r.err } // Error : RPCReturnType.Error 구현 func (r rpcReturnTypeImpl) Value() interface{} { if r.value.IsValid() { return r.value.Interface() } return nil } // MakeRPCReturn : func MakeRPCReturn(value interface{}, err error) RPCReturnType { return rpcReturnTypeImpl{ value: reflect.ValueOf(value), err: err, } } // MakeRPCReturnSerialized : 이미 시리얼라이즈 한 Return func MakeRPCReturnSerialized(serialized []byte) RPCReturnType { return rpcReturnTypeImpl{ serialized: serialized, } } // MakeRPCError : func MakeRPCError(args ...interface{}) RPCReturnType { return rpcReturnTypeImpl{ value: reflect.ValueOf(nil), err: errors.New(fmt.Sprint(args...)), } } // MakeRPCErrorf : func MakeRPCErrorf(format string, v ...interface{}) RPCReturnType { return rpcReturnTypeImpl{ value: reflect.ValueOf(nil), err: fmt.Errorf(format, v...), } } // Server : type Server struct { httpserver *http.Server shutdownFlag ShutdownFlag preShutdown func() stopfunc func() } var prefixptr = flag.String("prefix", "", "'") var portptr = flag.Int("port", 80, "") var tls = flag.String("tls", "", "") func welcomeHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("welcome")) // welcome은 accesslog 안 남김 // if atomic.LoadInt32(&serveMux.Shutdowning) == 0 { // w.Write([]byte("welcome")) // } else { // w.WriteHeader(http.StatusServiceUnavailable) // } io.Copy(ioutil.Discard, r.Body) r.Body.Close() } func NewDependentServer(preShutdown func()) *Server { server := &Server{ httpserver: nil, shutdownFlag: ShutdownFlagIdle, preShutdown: preShutdown, } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) go func() { <-c signal.Stop(c) server.Shutdown(ShutdownFlagTerminating) }() return server } // NewHTTPServer : func NewHTTPServer(serveMux *http.ServeMux, preShutdown func()) *Server { server := NewDependentServer(preShutdown) // 시작시 자동으로 enable됨 if len(*tls) > 0 && *portptr == 80 { *portptr = 443 } addr := fmt.Sprintf(":%d", *portptr) serveMux.HandleFunc("/welcome", welcomeHandler) server.httpserver = &http.Server{Addr: addr, Handler: serveMux} server.httpserver.SetKeepAlivesEnabled(true) return server } func (server *Server) ShutdownFlag() ShutdownFlag { return ShutdownFlag(atomic.LoadInt32((*int32)(&server.shutdownFlag))) } // Shutdown : func (server *Server) Shutdown(flag ShutdownFlag) { atomic.StoreInt32((*int32)(&server.shutdownFlag), int32(flag)) if server.preShutdown != nil { server.preShutdown() } if server.httpserver != nil { server.httpserver.Shutdown(context.Background()) server.httpserver = nil } else if server.stopfunc != nil { server.stopfunc() server.stopfunc = nil } } // Start : func (server *Server) Start() { if !flag.Parsed() { flag.Parse() } atomic.StoreInt32((*int32)(&server.shutdownFlag), int32(ShutdownFlagRunning)) if server.httpserver != nil { var err error if len(*tls) > 0 { crtfile := *tls + ".crt" keyfile := *tls + ".key" err = server.httpserver.ListenAndServeTLS(crtfile, keyfile) } else { err = server.httpserver.ListenAndServe() } if err != http.ErrServerClosed { fmt.Println(err) } } else { ctx, stopfunc := context.WithCancel(context.Background()) server.stopfunc = stopfunc <-ctx.Done() } } // ConvertInterface : func ConvertInterface(from interface{}, toType reflect.Type) reflect.Value { if toType.Kind() == reflect.String { return reflect.ValueOf(from.(string)) } fromrv := reflect.ValueOf(from) fromtype := reflect.TypeOf(from) if toType.Kind() == reflect.Struct { if frommap, ok := from.(map[string]interface{}); ok { out := reflect.New(toType) for k, v := range frommap { if fieldval := out.Elem().FieldByName(k); fieldval.IsValid() { fieldval.Set(ConvertInterface(v, fieldval.Type())) } } return out } } if fromtype.Kind() == reflect.Slice { if fromtype.Elem() == toType.Elem() { return fromrv } convslice := reflect.MakeSlice(toType, 0, fromrv.Len()) for i := 0; i < fromrv.Len(); i++ { convslice = reflect.Append(convslice, ConvertInterface(fromrv.Index(i).Interface(), toType.Elem())) } return convslice } if toType.Kind() == reflect.Bool { val, _ := strconv.ParseBool(from.(string)) return reflect.ValueOf(val) } return fromrv.Convert(toType) } // ErrUnmarshalRequestFailed : var ErrUnmarshalRequestFailed = errors.New("Unmarshal failed at rpc handler") var ErrInvalidContext = errors.New("Invalid context") // CallMethodInternal : func CallMethodInternal(receiver interface{}, context functionCallContext) (interface{}, error) { methodTokens := strings.Split(context.Method, ".") context.Method = methodTokens[len(methodTokens)-1] for i := 0; i < len(methodTokens)-1; i++ { token := methodTokens[i] if strings.HasSuffix(token, "()") { token = token[:len(token)-2] } fn := reflect.ValueOf(receiver).MethodByName(token) // 이 fn은 인자를 받지 않고 하나만 리턴하는 함수여야 한다. fnType := fn.Type() if fnType.NumOut() != 1 || fnType.NumIn() != 0 { return nil, errors.New(fmt.Sprint("method tokens are not correct :", token)) } returnVals := fn.Call(nil) if returnVals[0].IsNil() { return nil, errors.New(fmt.Sprint("method token returns nil :", token)) } receiver = returnVals[0].Interface() } fn := reflect.ValueOf(receiver).MethodByName(context.Method) if fn.IsValid() { fnType := fn.Type() if fnType.NumOut() != 1 { return nil, errors.New(fmt.Sprint("method should return only RPCReturnType :", context.Method)) } if len(context.Args) != fnType.NumIn() { return nil, errors.New(fmt.Sprint("argument is not matching :", context.Method)) } var argv []reflect.Value for i := 0; i < len(context.Args); i++ { argv = append(argv, ConvertInterface(context.Args[i], fnType.In(i))) } returnVals := fn.Call(argv) return returnVals[0].Interface(), nil } return nil, errors.New(fmt.Sprint("method is missing :", receiver, context.Method)) } func CallMethodWithInterface(receiver interface{}, context interface{}) (retval RPCReturnType, err error) { if context2, ok := context.(map[string]interface{}); ok { var meta functionCallContext if method, ok2 := context2["Method"].(string); ok2 { meta.Method = method } else { return nil, ErrInvalidContext } if args, ok2 := context2["Args"].([]interface{}); ok2 { meta.Args = args } else { return nil, ErrInvalidContext } if v, err := CallMethodInternal(receiver, meta); err == nil { if cast, ok := v.(RPCReturnType); ok { if cast.Error() != nil { return nil, cast.Error() } return cast, nil } else { return nil, errors.New(fmt.Sprint("method should return only RPCReturnType", meta)) } } } return nil, ErrInvalidContext } // CallMethod : func CallMethod(receiver interface{}, context []byte) (retval RPCReturnType, err error) { defer func() { r := recover() if r != nil && err == nil { debug.PrintStack() err = errors.New(fmt.Sprintf("%v", r)) } }() var raw interface{} if err := json.Unmarshal(context, &raw); err != nil { fmt.Println(string(context)) return nil, ErrUnmarshalRequestFailed } if meta, ok := raw.(map[string]interface{}); ok { if methods, ok := meta["Methods"].([]interface{}); ok { // calling cascaded methods body := make([]interface{}, 0, len(methods)) for _, meta2 := range methods { if cast, err := CallMethodWithInterface(receiver, meta2.(map[string]interface{})); err == nil { body = append(body, cast.Value()) continue } return MakeRPCReturn(nil, err), err } return MakeRPCReturn(body, nil), nil } else { return CallMethodWithInterface(receiver, meta) } } return nil, errors.New("Invalid context") }