382 lines
8.9 KiB
Go
382 lines
8.9 KiB
Go
|
|
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
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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")
|
||
|
|
|
||
|
|
// 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))
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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 meta functionCallContext
|
||
|
|
if err := json.Unmarshal(context, &meta); err != nil {
|
||
|
|
fmt.Println(string(context))
|
||
|
|
return nil, ErrUnmarshalRequestFailed
|
||
|
|
} else {
|
||
|
|
fmt.Printf("%+v\n", meta)
|
||
|
|
}
|
||
|
|
|
||
|
|
var v interface{}
|
||
|
|
v, err = CallMethodInternal(receiver, meta)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// v는 RPCReturnType이어야 한다.
|
||
|
|
if cast, ok := v.(RPCReturnType); ok {
|
||
|
|
if cast.Error() != nil {
|
||
|
|
return nil, cast.Error()
|
||
|
|
}
|
||
|
|
return cast, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil, errors.New(fmt.Sprint("method should return only RPCReturnType", meta))
|
||
|
|
}
|