http_command.go 3.6 KB
Newer Older
O
ob-robot 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
package http

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"reflect"
	"time"

	log "github.com/sirupsen/logrus"

	errors2 "github.com/oceanbase/obagent/errors"
	"github.com/oceanbase/obagent/lib/command"
	"github.com/oceanbase/obagent/lib/errors"
	"github.com/oceanbase/obagent/lib/system"
	"github.com/oceanbase/obagent/lib/trace"
)

type TaskToken struct {
	Token string `json:"token"`
}

var serverIp, _ = system.SystemImpl{}.GetLocalIpAddress()

type reqKey string

const (
	RequestTimeKey reqKey = "request_time"
)

func NewFuncHandler(fun interface{}) http.HandlerFunc {
	return NewHandler(command.WrapFunc(fun))
}

func NewHandler(cmd command.Command) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer request.Body.Close()
		ctx := context.WithValue(trace.ContextWithTraceId(request), RequestTimeKey, time.Now())
		request = request.WithContext(ctx)

		cmdParam, err := parseJsonParam(request.Body, cmd.DefaultParam())
		if err != nil {
			writeError(err, request, writer)
			return
		}
		log.WithContext(ctx).Infof("handling command request %s %v", request.URL.Path, cmdParam)
		execContext := command.NewInputExecutionContext(ctx, cmdParam)
		err = cmd.Execute(execContext)
		if err != nil {
			writeError(err, request, writer)
			return
		}
		result, err := execContext.Output().Get()
		if err != nil {
			writeError(err, request, writer)
			return
		}
		writeOk(result, request, writer)
	}
}

func parseJsonParam(reader io.Reader, defaultParam interface{}) (interface{}, error) {
	v := reflect.ValueOf(defaultParam)
	if !v.IsValid() {
		return nil, nil
	}
	v1 := reflect.New(v.Type())
	v1.Elem().Set(v)
	err := json.NewDecoder(reader).Decode(v1.Interface())
	if err != nil {
		return nil, err
	}
	return v1.Elem().Interface(), nil
}

func writeData(ctx context.Context, result interface{}, writer http.ResponseWriter) {
	data, _ := json.Marshal(result)
	writer.Header().Set("Connection", "close")
	_, err := writer.Write(data)
	if err != nil {
		log.WithContext(ctx).WithError(err).Error("write data to client got error. maybe client closed connection.")
	}
}

func writeOk(result interface{}, request *http.Request, writer http.ResponseWriter) {
	envelop := wrapEnvelop(result, nil, request)
	ctx := trace.ContextWithTraceId(request)
	writeData(ctx, envelop, writer)
	log.WithContext(ctx).Infof("command request %s succeed", request.URL.Path)
}

func wrapEnvelop(result interface{}, err error, request *http.Request) OcpAgentResponse {
	value := request.Context().Value(RequestTimeKey)
	var requestTime time.Time
	var ok bool
	var duration int
	if requestTime, ok = value.(time.Time); ok {
		duration = int(time.Since(requestTime).Milliseconds())
	}
	var apiErr *ApiError
	success := true
	code := 0
	statusCode := 200
	if err != nil {
		success = false
		statusCode = errors.HttpCode(err, http.StatusInternalServerError)
		if e, ok := err.(*errors2.OcpAgentError); ok {
			code = e.ErrorCode.Code
			statusCode = e.ErrorCode.Kind
		} else if e, ok := err.(*errors.Error); ok {
			code = e.Kind().HttpCode
			statusCode = code
		}
		apiErr = &ApiError{
			Code:    code,
			Message: err.Error(),
		}
	}
	return OcpAgentResponse{
		Successful: success,
		Timestamp:  requestTime,
		Duration:   duration,
		TraceId:    trace.GetTraceId(request),
		Server:     serverIp,
		Data:       result,
		Error:      apiErr,
		Status:     statusCode,
	}
}

func writeError(err error, request *http.Request, writer http.ResponseWriter) {
	envelop := wrapEnvelop(nil, err, request)
	writer.WriteHeader(envelop.Status)
	ctx := trace.ContextWithTraceId(request)
	writeData(ctx, envelop, writer)
	log.WithContext(ctx).Warnf("command request %s got error: %v", request.URL.Path, err)
}