go-ayo/common을 gocommon으로 분리
This commit is contained in:
535
server.go
Normal file
535
server.go
Normal file
@ -0,0 +1,535 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
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 RpcReturnTypeInterface interface {
|
||||
Value() any
|
||||
Error() error
|
||||
Serialize(http.ResponseWriter) error
|
||||
}
|
||||
|
||||
type functionCallContext struct {
|
||||
Method string
|
||||
Args []interface{}
|
||||
}
|
||||
|
||||
// Server :
|
||||
type Server struct {
|
||||
httpserver *http.Server
|
||||
interrupt chan os.Signal
|
||||
}
|
||||
|
||||
var PrefixPtr = flag.String("prefix", "", "'")
|
||||
var portptr = flag.Int("port", 80, "")
|
||||
var tls = flag.String("tls", "", "")
|
||||
var NoSessionFlag = flag.Bool("nosession", false, "nosession=[true|false]")
|
||||
var healthcheckcounter = int64(0)
|
||||
|
||||
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}()
|
||||
|
||||
// 한번이라도 들어오면 lb에 붙어있다는 뜻
|
||||
if t := atomic.AddInt64(&healthcheckcounter, 1); t < 0 {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
func welcomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}()
|
||||
|
||||
w.Write([]byte("welcome"))
|
||||
}
|
||||
|
||||
// NewHTTPServer :
|
||||
func NewHTTPServerWithPort(serveMux *http.ServeMux, port int) *Server {
|
||||
if len(*tls) > 0 && port == 80 {
|
||||
port = 443
|
||||
}
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
serveMux.HandleFunc(MakeHttpHandlerPattern("welcome"), welcomeHandler)
|
||||
serveMux.HandleFunc(MakeHttpHandlerPattern("lb_health_chceck"), healthCheckHandler)
|
||||
|
||||
server := &Server{
|
||||
httpserver: &http.Server{Addr: addr, Handler: serveMux},
|
||||
interrupt: make(chan os.Signal, 1),
|
||||
}
|
||||
server.httpserver.SetKeepAlivesEnabled(true)
|
||||
|
||||
signal.Notify(server.interrupt, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
go func() {
|
||||
c := <-server.interrupt
|
||||
logger.Println("interrupt!!!!!!!! :", c.String())
|
||||
server.shutdown()
|
||||
}()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func NewHTTPServer(serveMux *http.ServeMux) *Server {
|
||||
// 시작시 자동으로 enable됨
|
||||
if len(*tls) > 0 && *portptr == 80 {
|
||||
*portptr = 443
|
||||
}
|
||||
return NewHTTPServerWithPort(serveMux, *portptr)
|
||||
}
|
||||
|
||||
// Shutdown :
|
||||
func (server *Server) shutdown() {
|
||||
logger.Println("server.shutdown()")
|
||||
|
||||
signal.Stop(server.interrupt)
|
||||
|
||||
if atomic.LoadInt64(&healthcheckcounter) > 0 {
|
||||
atomic.StoreInt64(&healthcheckcounter, math.MinInt64)
|
||||
for atomic.LoadInt64(&healthcheckcounter)-math.MinInt64 > 10 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
if server.httpserver != nil {
|
||||
server.httpserver.Shutdown(context.Background())
|
||||
server.httpserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Start :
|
||||
func (server *Server) Start() error {
|
||||
if server.httpserver != nil {
|
||||
ln, r := net.Listen("tcp", server.httpserver.Addr)
|
||||
if r != nil {
|
||||
return r
|
||||
}
|
||||
proxyListener := &proxyproto.Listener{
|
||||
Listener: ln,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
defer proxyListener.Close()
|
||||
|
||||
var err error
|
||||
if len(*tls) > 0 {
|
||||
crtfile := *tls + ".crt"
|
||||
keyfile := *tls + ".key"
|
||||
logger.Println("tls enabled :", crtfile, keyfile)
|
||||
err = server.httpserver.ServeTLS(proxyListener, crtfile, keyfile)
|
||||
} else {
|
||||
err = server.httpserver.Serve(proxyListener)
|
||||
}
|
||||
|
||||
logger.Error("server.httpserver.Serve err :", err)
|
||||
if err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertInterface :
|
||||
func ConvertInterface(from interface{}, toType reflect.Type) reflect.Value {
|
||||
fromrv := reflect.ValueOf(from)
|
||||
fromtype := reflect.TypeOf(from)
|
||||
|
||||
if fromtype == toType {
|
||||
return fromrv
|
||||
}
|
||||
|
||||
switch fromtype.Kind() {
|
||||
case reflect.Map:
|
||||
frommap := from.(map[string]interface{})
|
||||
if frommap == nil {
|
||||
// map[string]interface{}가 아니고 뭐란말인가
|
||||
return reflect.Zero(toType)
|
||||
}
|
||||
|
||||
needAddr := false
|
||||
if toType.Kind() == reflect.Pointer {
|
||||
toType = toType.Elem()
|
||||
needAddr = true
|
||||
}
|
||||
|
||||
out := reflect.New(toType)
|
||||
switch toType.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < toType.NumField(); i++ {
|
||||
field := toType.FieldByIndex([]int{i})
|
||||
if field.Anonymous {
|
||||
fieldval := out.Elem().FieldByIndex([]int{i})
|
||||
fieldval.Set(ConvertInterface(from, field.Type))
|
||||
} else if v, ok := frommap[field.Name]; ok {
|
||||
fieldval := out.Elem().FieldByIndex([]int{i})
|
||||
fieldval.Set(ConvertInterface(v, fieldval.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
out.Elem().Set(reflect.MakeMap(toType))
|
||||
|
||||
if toType.Key().Kind() != reflect.String {
|
||||
for k, v := range frommap {
|
||||
tok := reflect.ValueOf(k).Convert(toType.Key())
|
||||
tov := ConvertInterface(v, toType.Elem())
|
||||
out.Elem().SetMapIndex(tok, tov)
|
||||
}
|
||||
} else {
|
||||
for k, v := range frommap {
|
||||
tov := ConvertInterface(v, toType.Elem())
|
||||
out.Elem().SetMapIndex(reflect.ValueOf(k), tov)
|
||||
}
|
||||
}
|
||||
default:
|
||||
logger.Println("ConvertInterface failed : map ->", toType.Kind().String())
|
||||
}
|
||||
|
||||
if needAddr {
|
||||
return out
|
||||
}
|
||||
|
||||
return out.Elem()
|
||||
|
||||
case reflect.Slice:
|
||||
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
|
||||
|
||||
case 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 ErrWithStatusCode = errors.New("custom error with status code")
|
||||
var ErrSecondReturnShouldBeErrorType = errors.New("second return type should be error")
|
||||
|
||||
type methodCacheType struct {
|
||||
sync.Mutex
|
||||
toIndex map[reflect.Type]map[string]int
|
||||
}
|
||||
|
||||
var methodCache = methodCacheType{
|
||||
toIndex: make(map[reflect.Type]map[string]int),
|
||||
}
|
||||
|
||||
func (mc *methodCacheType) method(receiver any, method string) (reflect.Value, bool) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
t := reflect.TypeOf(receiver)
|
||||
table, ok := mc.toIndex[t]
|
||||
if !ok {
|
||||
table = make(map[string]int)
|
||||
mc.toIndex[t] = table
|
||||
}
|
||||
|
||||
idx, ok := table[method]
|
||||
if !ok {
|
||||
m, ok := t.MethodByName(method)
|
||||
if !ok {
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
table[method] = m.Index
|
||||
idx = m.Index
|
||||
}
|
||||
|
||||
return reflect.ValueOf(receiver).Method(idx), true
|
||||
}
|
||||
|
||||
// CallMethodInternal :
|
||||
func CallMethodInternal(receiver interface{}, context functionCallContext) (result RpcReturnTypeInterface, err error) {
|
||||
fn, ok := methodCache.method(receiver, context.Method)
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprint("method is missing :", receiver, context.Method))
|
||||
}
|
||||
if len(context.Args) != fn.Type().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], fn.Type().In(i)))
|
||||
}
|
||||
returnVals := fn.Call(argv)
|
||||
|
||||
if len(returnVals) == 0 {
|
||||
return nil, ErrSecondReturnShouldBeErrorType
|
||||
}
|
||||
|
||||
result = nil
|
||||
err = nil
|
||||
if result, ok = returnVals[0].Interface().(RpcReturnTypeInterface); !ok {
|
||||
return nil, ErrSecondReturnShouldBeErrorType
|
||||
}
|
||||
|
||||
if len(returnVals) > 1 {
|
||||
if err, ok = returnVals[1].Interface().(error); !ok {
|
||||
return nil, ErrSecondReturnShouldBeErrorType
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CallMethod :
|
||||
func CallMethod(receiver interface{}, context []byte) (RpcReturnTypeInterface, error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
logger.Error(r)
|
||||
}
|
||||
}()
|
||||
|
||||
var meta functionCallContext
|
||||
if err := json.Unmarshal(context, &meta); err != nil {
|
||||
return nil, ErrUnmarshalRequestFailed
|
||||
}
|
||||
|
||||
return CallMethodInternal(receiver, meta)
|
||||
}
|
||||
|
||||
func ReadIntegerFormValue(r url.Values, key string) (int64, bool) {
|
||||
strval := r.Get(key)
|
||||
if len(strval) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
temp, err := strconv.ParseInt(strval, 10, 0)
|
||||
if err != nil {
|
||||
logger.Error("common.ReadFloatFormValue failed :", key, err)
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return temp, true
|
||||
|
||||
}
|
||||
|
||||
func ReadFloatFormValue(r url.Values, key string) (float64, bool) {
|
||||
var inst float64
|
||||
strval := r.Get(key)
|
||||
if len(strval) == 0 {
|
||||
return inst, false
|
||||
}
|
||||
temp, err := strconv.ParseFloat(strval, 64)
|
||||
if err != nil {
|
||||
logger.Error("common.ReadFloatFormValue failed :", key, err)
|
||||
return inst, false
|
||||
}
|
||||
|
||||
return temp, true
|
||||
}
|
||||
|
||||
func ReadObjectIDFormValue(r url.Values, key string) (primitive.ObjectID, bool) {
|
||||
strval := r.Get(key)
|
||||
if len(strval) == 0 {
|
||||
return primitive.NilObjectID, false
|
||||
}
|
||||
id, err := primitive.ObjectIDFromHex(strval)
|
||||
if err != nil {
|
||||
logger.Error("common.ReadObjectIDFormValue failed :", key, err)
|
||||
return primitive.NilObjectID, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
func ReadBoolFormValue(r url.Values, key string) (bool, bool) {
|
||||
strval := r.Get(key)
|
||||
if len(strval) == 0 {
|
||||
return false, false
|
||||
}
|
||||
ret, err := strconv.ParseBool(strval)
|
||||
if err != nil {
|
||||
logger.Error("common.ReadBoolFormValue failed :", key, err)
|
||||
return false, false
|
||||
}
|
||||
return ret, true
|
||||
}
|
||||
|
||||
func ReadStringFormValue(r url.Values, key string) (string, bool) {
|
||||
strval := r.Get(key)
|
||||
return strval, len(strval) > 0
|
||||
}
|
||||
|
||||
func DotStringToTimestamp(tv string) primitive.Timestamp {
|
||||
if len(tv) == 0 {
|
||||
return primitive.Timestamp{T: 0, I: 0}
|
||||
}
|
||||
|
||||
ti := strings.Split(tv, ".")
|
||||
t, _ := strconv.ParseUint(ti[0], 10, 0)
|
||||
if len(ti) > 1 {
|
||||
i, _ := strconv.ParseUint(ti[1], 10, 0)
|
||||
return primitive.Timestamp{T: uint32(t), I: uint32(i)}
|
||||
}
|
||||
|
||||
return primitive.Timestamp{T: uint32(t), I: 0}
|
||||
}
|
||||
|
||||
type RpcReturnSimple struct {
|
||||
value any
|
||||
}
|
||||
|
||||
func (rt *RpcReturnSimple) Value() any {
|
||||
return rt.value
|
||||
}
|
||||
|
||||
func (rt *RpcReturnSimple) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rt *RpcReturnSimple) Serialize(w http.ResponseWriter) error {
|
||||
err := SerializeInterface(w, rt.value)
|
||||
if err == nil {
|
||||
w.Write([]byte{0, 0})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type RpcReturnError struct {
|
||||
err error
|
||||
code int
|
||||
h map[string]any
|
||||
}
|
||||
|
||||
func (rt *RpcReturnError) Value() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errDefaultError = errors.New("unknown error")
|
||||
|
||||
func (rt *RpcReturnError) Error() error {
|
||||
if rt.err != nil {
|
||||
return rt.err
|
||||
}
|
||||
|
||||
if rt.code != 0 {
|
||||
if rt.h == nil {
|
||||
return fmt.Errorf("http status code %d error", rt.code)
|
||||
}
|
||||
return fmt.Errorf("http status code %d error with header %v", rt.code, rt.h)
|
||||
}
|
||||
|
||||
return errDefaultError
|
||||
}
|
||||
|
||||
func (rt *RpcReturnError) Serialize(w http.ResponseWriter) error {
|
||||
if rt.h != nil {
|
||||
bt, _ := json.Marshal(rt.h)
|
||||
w.Header().Add("As-X-Err", string(bt))
|
||||
}
|
||||
|
||||
w.WriteHeader(rt.code)
|
||||
if rt.err != nil {
|
||||
logger.Error(rt.err)
|
||||
}
|
||||
|
||||
if rt.code >= http.StatusInternalServerError {
|
||||
logger.Println("rpc return error :", rt.code, rt.h)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rt *RpcReturnError) WithCode(code int) *RpcReturnError {
|
||||
rt.code = code
|
||||
return rt
|
||||
}
|
||||
|
||||
func (rt *RpcReturnError) WithError(err error) *RpcReturnError {
|
||||
rt.err = err
|
||||
return rt
|
||||
}
|
||||
|
||||
func (rt *RpcReturnError) WithHeader(k string, v any) *RpcReturnError {
|
||||
if rt.h == nil {
|
||||
rt.h = make(map[string]any)
|
||||
}
|
||||
rt.h[k] = v
|
||||
return rt
|
||||
}
|
||||
|
||||
func (rt *RpcReturnError) WithHeaders(h map[string]any) *RpcReturnError {
|
||||
if rt.h == nil {
|
||||
rt.h = h
|
||||
} else {
|
||||
for k, v := range h {
|
||||
rt.h[k] = v
|
||||
}
|
||||
}
|
||||
return rt
|
||||
}
|
||||
|
||||
// MakeRPCReturn :
|
||||
func MakeRPCReturn(value interface{}) *RpcReturnSimple {
|
||||
return &RpcReturnSimple{
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func MakeRPCError() *RpcReturnError {
|
||||
pc, _, _, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
frames := runtime.CallersFrames([]uintptr{pc})
|
||||
frame, _ := frames.Next()
|
||||
logger.Printf("rpc error. func=%s, file=%s, line=%d", frame.Function, frame.File, frame.Line)
|
||||
}
|
||||
|
||||
return &RpcReturnError{
|
||||
err: nil,
|
||||
code: http.StatusInternalServerError,
|
||||
h: nil,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user