github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/vm/wasmapi/async/processor.go (about) 1 package async 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "net/http/httptest" 12 13 "github.com/gin-gonic/gin" 14 "github.com/hibiken/asynq" 15 "github.com/pkg/errors" 16 17 "github.com/machinefi/w3bstream/pkg/depends/conf/logger" 18 confmq "github.com/machinefi/w3bstream/pkg/depends/conf/mq" 19 confredis "github.com/machinefi/w3bstream/pkg/depends/conf/redis" 20 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx" 21 "github.com/machinefi/w3bstream/pkg/depends/x/contextx" 22 "github.com/machinefi/w3bstream/pkg/models" 23 "github.com/machinefi/w3bstream/pkg/modules/event" 24 apitypes "github.com/machinefi/w3bstream/pkg/modules/vm/wasmapi/types" 25 "github.com/machinefi/w3bstream/pkg/types" 26 "github.com/machinefi/w3bstream/pkg/types/wasm" 27 "github.com/machinefi/w3bstream/pkg/types/wasm/kvdb" 28 ) 29 30 type ApiCallProcessor struct { 31 router *gin.Engine 32 cli *asynq.Client 33 } 34 35 func NewApiCallProcessor(router *gin.Engine, cli *asynq.Client) *ApiCallProcessor { 36 return &ApiCallProcessor{ 37 router: router, 38 cli: cli, 39 } 40 } 41 42 func (p *ApiCallProcessor) ProcessTask(ctx context.Context, t *asynq.Task) error { 43 ctx, l := logger.NewSpanContext(ctx, "vm.ApiCall.ProcessTask") 44 defer l.End() 45 46 payload := apiCallPayload{} 47 if err := json.Unmarshal(t.Payload(), &payload); err != nil { 48 return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) 49 } 50 51 req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(payload.Data))) 52 if err != nil { 53 return fmt.Errorf("http.ReadRequest failed: %v: %w", err, asynq.SkipRetry) 54 } 55 req = req.WithContext(contextx.WithContextCompose( 56 types.WithProjectContext(payload.Project), 57 wasm.WithChainClientContext(payload.ChainClient), 58 )(context.Background())) 59 60 respRecorder := httptest.NewRecorder() 61 p.router.ServeHTTP(respRecorder, req) 62 63 prjName := payload.Project.ProjectName.Name 64 l = l.WithValues("prj", prjName) 65 66 apiResp, err := ConvHttpResponse(req.Header, respRecorder.Result()) 67 if err != nil { 68 l.Error(errors.Wrap(err, "conv http response failed")) 69 return fmt.Errorf("conv http response failed: %v: %w", err, asynq.SkipRetry) 70 } 71 72 // no content need return to caller 73 if apiResp.StatusCode == http.StatusNoContent { 74 return nil 75 } 76 77 apiRespJson, err := json.Marshal(apiResp) 78 if err != nil { 79 l.Error(errors.Wrap(err, "encode http response failed")) 80 return fmt.Errorf("encode http response failed: %v: %w", err, asynq.SkipRetry) 81 } 82 83 eventType := req.Header.Get("eventType") 84 85 task, err := newApiResultTask(prjName, eventType, apiRespJson) 86 if err != nil { 87 l.Error(errors.Wrap(err, "new api result task failed")) 88 return fmt.Errorf("new api result task failed: %v: %w", err, asynq.SkipRetry) 89 } 90 if _, err := p.cli.Enqueue(task); err != nil { 91 l.Error(errors.Wrap(err, "could not enqueue task")) 92 return fmt.Errorf("could not enqueue task: %v: %w", err, asynq.SkipRetry) 93 } 94 95 return nil 96 } 97 98 type ApiResultProcessor struct { 99 mgrDB sqlx.DBExecutor 100 kv *kvdb.RedisDB 101 redis *confredis.Redis 102 tasks *confmq.Config 103 } 104 105 func NewApiResultProcessor(mgrDB sqlx.DBExecutor, kv *kvdb.RedisDB, tasks *confmq.Config, redis *confredis.Redis) *ApiResultProcessor { 106 return &ApiResultProcessor{ 107 kv: kv, 108 mgrDB: mgrDB, 109 tasks: tasks, 110 redis: redis, 111 } 112 } 113 114 func (p *ApiResultProcessor) ProcessTask(ctx context.Context, t *asynq.Task) error { 115 ctx, l := logger.NewSpanContext(ctx, "vm.ApiResult.ProcessTask") 116 defer l.End() 117 118 payload := apiResultPayload{} 119 if err := json.Unmarshal(t.Payload(), &payload); err != nil { 120 return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) 121 } 122 123 ctx = contextx.WithContextCompose( 124 confmq.WithMqContext(p.tasks), 125 types.WithMgrDBExecutorContext(p.mgrDB), 126 kvdb.WithRedisDBKeyContext(p.kv), 127 types.WithProjectContext(&models.Project{ 128 ProjectName: models.ProjectName{Name: payload.ProjectName}}, 129 ), 130 types.WithRedisEndpointContext(p.redis), 131 )(ctx) 132 133 if _, err := event.HandleEvent(ctx, payload.EventType, payload.Data); err != nil { 134 l.Error(errors.Wrap(err, "send event failed")) 135 return err 136 } 137 138 return nil 139 } 140 141 func ConvHttpResponse(reqHeader http.Header, resp *http.Response) (*apitypes.HttpResponse, error) { 142 var body []byte 143 var err error 144 if resp.Body != nil { 145 body, err = io.ReadAll(resp.Body) 146 if err != nil { 147 return nil, err 148 } 149 } 150 151 respHeader := resp.Header 152 for k, v := range reqHeader { 153 if k == "Content-Type" { 154 continue 155 } 156 respHeader[k] = v 157 } 158 159 return &apitypes.HttpResponse{ 160 Status: resp.Status, 161 StatusCode: resp.StatusCode, 162 Proto: resp.Proto, 163 Header: respHeader, 164 Body: body, 165 }, nil 166 }