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  }