server initial commit
This commit is contained in:
381
shared/server.go
Normal file
381
shared/server.go
Normal file
@ -0,0 +1,381 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user