Files
gocommon/server.go

1000 lines
22 KiB
Go

package gocommon
import (
"bytes"
"context"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"github.com/pires/go-proxyproto"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func init() {
gob.Register(map[string]any{})
gob.Register(primitive.A{})
gob.Register(primitive.M{})
gob.Register(primitive.D{})
gob.Register(primitive.ObjectID{})
gob.Register([]any{})
}
type ServerMuxInterface interface {
http.Handler
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
Handle(pattern string, handler http.Handler)
}
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 ErrorWithStatus struct {
StatusCode int
}
func (e ErrorWithStatus) Error() string {
return fmt.Sprintf("%d", e.StatusCode)
}
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 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 {
logger.Println("healthCheckHandler return StatusServiceUnavailable :", t)
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"))
}
var tlsflag = flagx.String("tls", "", "")
var portptr = flagx.Int("port", 80, "")
func isTlsEnabled(fileout ...*string) bool {
if len(*tlsflag) == 0 {
return false
}
if strings.HasSuffix(*tlsflag, "/") {
return false
}
crtfile := *tlsflag + ".crt"
if _, err := os.Stat(crtfile); os.IsNotExist(err) {
return false
}
keyfile := *tlsflag + ".key"
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
return false
}
if len(fileout) > 0 {
*fileout[0] = crtfile
}
if len(fileout) > 1 {
*fileout[1] = keyfile
}
return true
}
func registUnhandledPattern(serveMux ServerMuxInterface) {
defer func() {
recover()
}()
serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
logger.Println("page not found :", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
})
}
// NewHTTPServer :
func NewHTTPServerWithPort(serveMux ServerMuxInterface, port int) *Server {
if isTlsEnabled() && port == 80 {
port = 443
}
addr := fmt.Sprintf(":%d", port)
serveMux.HandleFunc(MakeHttpHandlerPattern("welcome"), welcomeHandler)
serveMux.HandleFunc(MakeHttpHandlerPattern("lb_health_chceck"), healthCheckHandler)
serveMux.HandleFunc(MakeHttpHandlerPattern("lb_health_check"), healthCheckHandler)
registUnhandledPattern(serveMux)
server := &Server{
httpserver: &http.Server{
Addr: addr,
Handler: serveMux,
MaxHeaderBytes: 2 << 20, // 2 MB
},
}
server.httpserver.SetKeepAlivesEnabled(true)
return server
}
func NewHTTPServer(serveMux ServerMuxInterface) *Server {
// 시작시 자동으로 enable됨
if isTlsEnabled() && *portptr == 80 {
*portptr = 443
}
return NewHTTPServerWithPort(serveMux, *portptr)
}
// Shutdown :
func (server *Server) shutdown() {
logger.Println("server.shutdown()")
signal.Stop(server.interrupt)
if t := atomic.LoadInt64(&healthcheckcounter); t > 0 {
logger.Println("http server shutdown. healthcheckcounter :", t)
atomic.StoreInt64(&healthcheckcounter, math.MinInt64)
for cnt := 0; cnt < 100; {
next := atomic.LoadInt64(&healthcheckcounter)
if next == t {
cnt++
} else {
t = next
cnt = 0
}
time.Sleep(100 * time.Millisecond)
}
logger.Println("http server shutdown. healthcheck completed")
} else {
logger.Println("http server shutdown. no lb")
}
if server.httpserver != nil {
server.httpserver.Shutdown(context.Background())
server.httpserver = nil
}
}
func (server *Server) Stop() {
if server.interrupt != nil {
server.interrupt <- os.Interrupt
} else {
server.shutdown()
}
}
// Start :
func (server *Server) Start(name ...string) error {
if len(name) == 0 {
exepath, _ := os.Executable()
name = []string{path.Base(exepath)}
}
if server.httpserver != nil {
ln, r := net.Listen("tcp", server.httpserver.Addr)
if r != nil {
return r
}
server.interrupt = make(chan os.Signal, 1)
signal.Notify(server.interrupt, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
c := <-server.interrupt
logger.Println("interrupt!!!!!!!! :", c.String())
server.shutdown()
}()
proxyListener := &proxyproto.Listener{
Listener: ln,
ReadHeaderTimeout: 10 * time.Second,
}
defer proxyListener.Close()
var err error
var crtfile string
var keyfile string
if isTlsEnabled(&crtfile, &keyfile) {
logger.Println("tls enabled :", crtfile, keyfile)
err = server.httpserver.ServeTLS(proxyListener, crtfile, keyfile)
} else {
logger.Println("tls disabled")
logger.Println(strings.Join(name, ", "), "started")
err = server.httpserver.Serve(proxyListener)
}
if err != nil {
if errors.Is(err, http.ErrServerClosed) {
logger.Println("server.httpserver is closed normally")
return nil
}
logger.Error("server.httpserver.Serve err :", err)
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[strings.ToLower(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)
case reflect.String:
if toType == reflect.TypeOf(primitive.ObjectID{}) {
objid, _ := primitive.ObjectIDFromHex(from.(string))
return reflect.ValueOf(objid)
}
}
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 ReadStringsFormValue(r url.Values, key string) ([]string, bool) {
if r.Has(key) {
return (map[string][]string)(r)[key], true
}
return nil, false
}
type encoder interface {
Encode(any) error
}
type nilEncoder struct{}
func (ne *nilEncoder) Encode(any) error { return nil }
type decoder interface {
Decode(any) error
}
type nilDecoder struct{}
func (nd *nilDecoder) Decode(any) error { return nil }
func MakeDecoder(r *http.Request) decoder {
ct := r.Header.Get("Content-Type")
if ct == "application/gob" {
return gob.NewDecoder(r.Body)
} else if ct == "application/json" {
return json.NewDecoder(r.Body)
}
logger.Error("Content-Type is not supported :", ct)
return &nilDecoder{}
}
func MakeEncoder(w http.ResponseWriter, r *http.Request) encoder {
ct := r.Header.Get("Content-Type")
if ct == "application/gob" {
return gob.NewEncoder(w)
} else if ct == "application/json" {
return json.NewEncoder(w)
}
logger.Error("Content-Type is not supported :", ct)
return &nilEncoder{}
}
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 {
if err2, ok := err.(*ErrorWithStatus); ok {
rt.WithCode(err2.StatusCode)
} else {
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 MakeRPCFail() *RpcReturnError {
return &RpcReturnError{
err: nil,
code: http.StatusInternalServerError,
h: nil,
}
}
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,
}
}
type indirectBody struct {
inner io.ReadCloser
dump []byte
closer func()
}
func (ib *indirectBody) Read(p []byte) (n int, err error) {
n, err = ib.inner.Read(p)
if n > 0 {
ib.dump = append(ib.dump, p...)
}
return
}
func (ib *indirectBody) Close() error {
if ib.closer != nil {
ib.closer()
ib.closer = nil
}
return ib.inner.Close()
}
func MakeHttpRequestForLogging(r *http.Request) *http.Request {
ib := &indirectBody{
inner: r.Body,
}
closer := func() {
var uv url.Values
if r.Form != nil {
uv = r.Form
} else {
uv = r.URL.Query()
}
logger.Println("request :")
logger.Println(" header :", r.Header)
logger.Println(" values :", uv)
logger.Println(" body :", string(ib.dump))
}
ib.closer = closer
r.Body = ib
return r
}
type apiFuncType func(http.ResponseWriter, *http.Request)
type HttpApiHandler struct {
methods map[string]apiFuncType
originalReceiverName string
}
func MakeHttpApiHandler[T any](receiver *T, receiverName string) HttpApiHandler {
methods := make(map[string]apiFuncType)
tp := reflect.TypeOf(receiver)
if len(receiverName) == 0 {
receiverName = tp.Elem().Name()
}
writerType := reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
for i := 0; i < tp.NumMethod(); i++ {
method := tp.Method(i)
if method.Type.NumIn() != 3 {
continue
}
if method.Type.In(0) != tp {
continue
}
if !method.Type.In(1).Implements(writerType) {
continue
}
var r http.Request
if method.Type.In(2) != reflect.TypeOf(&r) {
continue
}
if method.Name == "ServeHTTP" {
continue
}
funcptr := method.Func.Pointer()
p1 := unsafe.Pointer(&funcptr)
p2 := unsafe.Pointer(&p1)
testfunc := (*func(*T, http.ResponseWriter, *http.Request))(p2)
methods[receiverName+"."+method.Name] = func(w http.ResponseWriter, r *http.Request) {
(*testfunc)(receiver, w, r)
}
}
return HttpApiHandler{
methods: methods,
originalReceiverName: tp.Elem().Name(),
}
}
type HttpApiBroker struct {
methods map[string]apiFuncType
methods_dup map[string][]apiFuncType
}
type bufferReadCloser struct {
*bytes.Reader
}
func (buff *bufferReadCloser) Close() error { return nil }
type readOnlyResponseWriter struct {
inner http.ResponseWriter
statusCode int
}
func (w *readOnlyResponseWriter) Header() http.Header {
return w.inner.Header()
}
func (w *readOnlyResponseWriter) Write(in []byte) (int, error) {
logger.Println("readOnlyResponseWriter cannot write")
return len(in), nil
}
func (w *readOnlyResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}
func (hc *HttpApiBroker) AddHandler(receiver HttpApiHandler) {
if hc.methods == nil {
hc.methods = make(map[string]apiFuncType)
hc.methods_dup = make(map[string][]apiFuncType)
}
for k, v := range receiver.methods {
ab := strings.Split(k, ".")
logger.Printf("http api registered : %s.%s -> %s\n", receiver.originalReceiverName, ab[1], k)
hc.methods_dup[k] = append(hc.methods_dup[k], v)
if len(hc.methods_dup[k]) > 1 {
chain := hc.methods_dup[k]
hc.methods[k] = func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
defer r.Body.Close()
wrap := &readOnlyResponseWriter{inner: w, statusCode: 200}
for _, f := range chain {
r.Body = &bufferReadCloser{bytes.NewReader(body)}
f(wrap, r)
}
if wrap.statusCode != 200 {
w.WriteHeader(wrap.statusCode)
}
}
} else {
hc.methods[k] = v
}
}
}
func (hc *HttpApiBroker) AllMethodNames() (out []string) {
out = make([]string, 0, len(hc.methods))
for name := range hc.methods {
out = append(out, name)
}
return
}
func (hc *HttpApiBroker) CallByHeader(w http.ResponseWriter, r *http.Request) {
funcname := r.Header.Get("AS-X-CALL")
if len(funcname) == 0 {
logger.Println("as-x-call header is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
hc.call(funcname, w, r)
}
func (hc *HttpApiBroker) Call(w http.ResponseWriter, r *http.Request) {
funcname := r.URL.Query().Get("call")
if len(funcname) == 0 {
logger.Println("query param 'call' is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
hc.call(funcname, w, r)
}
func (hc *HttpApiBroker) call(funcname string, w http.ResponseWriter, r *http.Request) {
if found := hc.methods[funcname]; found != nil {
found(w, r)
} else {
logger.Println("api is not found :", funcname)
}
}
func CallInternalServiceAPI[T any](url string, apitoken string, method string, data T, headers ...string) error {
tempHeader := make(http.Header)
tempHeader.Set("MG-X-API-TOKEN", apitoken)
tempHeader.Set("Content-Type", "application/gob")
for i := 1; i < len(headers); i += 2 {
tempHeader.Set(headers[i-1], headers[i])
}
buff := new(bytes.Buffer)
ct := tempHeader.Get("Content-Type")
if ct == "application/gob" {
enc := gob.NewEncoder(buff)
enc.Encode(data)
} else if ct == "application/json" {
enc := json.NewEncoder(buff)
enc.Encode(data)
}
reqURL := fmt.Sprintf("%s/api?call=%s", url, method)
req, err := http.NewRequest("POST", reqURL, buff)
if err != nil {
return err
}
req.Header = tempHeader
resp, err := http.DefaultClient.Do(req)
defer func() {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
}()
if err == nil {
if resp.StatusCode != http.StatusOK {
return &ErrorWithStatus{StatusCode: resp.StatusCode}
}
}
return err
}
func CallInternalServiceAPIAs[Tin any, Tout any](url string, apitoken string, method string, data Tin, out *Tout, headers ...string) error {
tempHeader := make(http.Header)
tempHeader.Set("MG-X-API-TOKEN", apitoken)
tempHeader.Set("Content-Type", "application/gob")
for i := 1; i < len(headers); i += 2 {
tempHeader.Set(headers[i-1], headers[i])
}
buff := new(bytes.Buffer)
ct := tempHeader.Get("Content-Type")
if ct == "application/gob" {
enc := gob.NewEncoder(buff)
enc.Encode(data)
} else if ct == "application/json" {
enc := json.NewEncoder(buff)
enc.Encode(data)
}
reqURL := fmt.Sprintf("%s/api?call=%s", url, method)
req, err := http.NewRequest("POST", reqURL, buff)
if err != nil {
return err
}
req.Header = tempHeader
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return &ErrorWithStatus{StatusCode: resp.StatusCode}
}
defer func() {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
}()
if out != nil && resp.Body != nil {
dec := gob.NewDecoder(resp.Body)
return dec.Decode(out)
}
return nil
}