├── .env.example ├── .gitignore ├── assets └── background.png ├── images └── 2023-12-13 │ ├── 24546902-dddf-42f6-9661-8ed2c8fd9efe.jpg │ ├── 29351717-fb1c-4fd8-889e-245ca549a94b.jpg │ ├── 5f5ac09a-dfd0-48f4-87f6-7cf4b0accbfd.jpg │ ├── 8cbc153f-73f1-4eea-85e9-582859abfb92.jpg │ ├── 8ee68966-6634-44eb-970d-28fcc5bc8a43.jpg │ ├── a6dc98fa-a221-4369-95be-3df3092eb7b8.jpg │ ├── ae80de12-ad3c-4125-b832-61f5f48cf88f.jpg │ ├── b337014f-f677-447b-8ffc-22a9d653686c.jpg │ ├── d6093e42-bc62-4b26-a286-0556fdd6d1dc.jpg │ └── fae03e07-a553-4836-9966-ea9d2c34b635.jpg ├── routes └── routes.go ├── server └── server.go ├── tasks ├── queue.go └── tasks.go ├── worker └── worker.go ├── docker-compose.yaml ├── README.md ├── go.mod ├── handlers └── handlers.go └── go.sum /.env.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./images 2 | .env 3 | .idea -------------------------------------------------------------------------------- /assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/assets/background.png -------------------------------------------------------------------------------- /images/2023-12-13/24546902-dddf-42f6-9661-8ed2c8fd9efe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/24546902-dddf-42f6-9661-8ed2c8fd9efe.jpg -------------------------------------------------------------------------------- /images/2023-12-13/29351717-fb1c-4fd8-889e-245ca549a94b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/29351717-fb1c-4fd8-889e-245ca549a94b.jpg -------------------------------------------------------------------------------- /images/2023-12-13/5f5ac09a-dfd0-48f4-87f6-7cf4b0accbfd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/5f5ac09a-dfd0-48f4-87f6-7cf4b0accbfd.jpg -------------------------------------------------------------------------------- /images/2023-12-13/8cbc153f-73f1-4eea-85e9-582859abfb92.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/8cbc153f-73f1-4eea-85e9-582859abfb92.jpg -------------------------------------------------------------------------------- /images/2023-12-13/8ee68966-6634-44eb-970d-28fcc5bc8a43.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/8ee68966-6634-44eb-970d-28fcc5bc8a43.jpg -------------------------------------------------------------------------------- /images/2023-12-13/a6dc98fa-a221-4369-95be-3df3092eb7b8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/a6dc98fa-a221-4369-95be-3df3092eb7b8.jpg -------------------------------------------------------------------------------- /images/2023-12-13/ae80de12-ad3c-4125-b832-61f5f48cf88f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/ae80de12-ad3c-4125-b832-61f5f48cf88f.jpg -------------------------------------------------------------------------------- /images/2023-12-13/b337014f-f677-447b-8ffc-22a9d653686c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/b337014f-f677-447b-8ffc-22a9d653686c.jpg -------------------------------------------------------------------------------- /images/2023-12-13/d6093e42-bc62-4b26-a286-0556fdd6d1dc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/d6093e42-bc62-4b26-a286-0556fdd6d1dc.jpg -------------------------------------------------------------------------------- /images/2023-12-13/fae03e07-a553-4836-9966-ea9d2c34b635.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anqorithm/image-processing-service-async/HEAD/images/2023-12-13/fae03e07-a553-4836-9966-ea9d2c34b635.jpg -------------------------------------------------------------------------------- /routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/qahta0/image-processing-service/handlers" 6 | ) 7 | 8 | func Setup(app *fiber.App) { 9 | app.Post("/process-image", handlers.UploadImage) 10 | } 11 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/qahta0/image-processing-service/routes" 6 | "github.com/qahta0/image-processing-service/tasks" 7 | "log" 8 | ) 9 | 10 | const redisAddress = "127.0.0.1:6379" 11 | 12 | func main() { 13 | app := fiber.New() 14 | routes.Setup(app) 15 | tasks.Init(redisAddress) 16 | defer tasks.Close() 17 | log.Fatal(app.Listen(":3000")) 18 | } 19 | -------------------------------------------------------------------------------- /tasks/queue.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "github.com/hibiken/asynq" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | client *asynq.Client 10 | once sync.Once 11 | ) 12 | 13 | func Init(redisAddress string) { 14 | once.Do(func() { 15 | client = asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddress}) 16 | }) 17 | } 18 | 19 | func Close() { 20 | if client != nil { 21 | client.Close() 22 | } 23 | } 24 | 25 | func GetClient() *asynq.Client { 26 | return client 27 | } 28 | -------------------------------------------------------------------------------- /worker/worker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hibiken/asynq" 5 | "github.com/qahta0/image-processing-service/tasks" 6 | "log" 7 | ) 8 | 9 | const redisAddress = "127.0.0.1:6379" 10 | 11 | func main() { 12 | srv := asynq.NewServer( 13 | asynq.RedisClientOpt{Addr: redisAddress}, 14 | asynq.Config{Concurrency: 10}, 15 | ) 16 | mux := asynq.NewServeMux() 17 | mux.HandleFunc(tasks.TypeResizeImage, tasks.HandleResizeImageTask) 18 | if err := srv.Run(mux); err != nil { 19 | log.Fatalf("Could not run asynq server: %v", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | redis: 5 | image: redis:latest 6 | ports: 7 | - "6379:6379" 8 | volumes: 9 | - redis-data:/data 10 | networks: 11 | - image-processing-network 12 | 13 | asynqmon: 14 | image: hibiken/asynqmon:latest 15 | environment: 16 | - REDIS_ADDR=redis:6379 17 | ports: 18 | - "8080:8080" 19 | depends_on: 20 | - redis 21 | networks: 22 | - image-processing-network 23 | 24 | networks: 25 | image-processing-network: 26 | 27 | volumes: 28 | redis-data: 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image-processing-service-async 2 | 3 | ![background](./assets/background.png) 4 | 5 | This repository contains an asynchronous image processing service built using Golang, Asynq, Redis, Fiber and Docker 6 | Compose for easy deployment. To run the service, follow these steps: 7 | 8 | 1. Start the service containers using Docker Compose: 9 | ```bash 10 | docker-compose up -d 11 | ``` 12 | 13 | 2. Run the server component: 14 | ```bash 15 | go run server/server.go 16 | ``` 17 | 18 | 3. Run the worker component: 19 | ```bash 20 | go run worker/worker.go 21 | ``` 22 | 23 | 4. Visit Asynqmon on port 8080 to monitor and manage asynchronous tasks. 24 | 25 | You can read the detailed explanation about efficient image processing with Golang, Asynq, Redis, and Fiber in the article on Medium by clicking [Efficient Image Processing: Golang, Asynq, Redis, and Fiber for Asynchronous Queue Handling](https://medium.com/@anqorithm/efficient-image-processing-golang-asynq-redis-and-fiber-for-asynchronous-queue-handling-77d1cc5e75a1). 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/qahta0/image-processing-service 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gofiber/fiber/v2 v2.51.0 7 | github.com/hibiken/asynq v0.24.1 8 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.0.5 // indirect 13 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 14 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 15 | github.com/golang/protobuf v1.5.2 // indirect 16 | github.com/google/uuid v1.4.0 // indirect 17 | github.com/klauspost/compress v1.16.7 // indirect 18 | github.com/mattn/go-colorable v0.1.13 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/mattn/go-runewidth v0.0.15 // indirect 21 | github.com/redis/go-redis/v9 v9.0.3 // indirect 22 | github.com/rivo/uniseg v0.2.0 // indirect 23 | github.com/robfig/cron/v3 v3.0.1 // indirect 24 | github.com/spf13/cast v1.3.1 // indirect 25 | github.com/valyala/bytebufferpool v1.0.0 // indirect 26 | github.com/valyala/fasthttp v1.50.0 // indirect 27 | github.com/valyala/tcplisten v1.0.0 // indirect 28 | golang.org/x/sys v0.14.0 // indirect 29 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect 30 | google.golang.org/protobuf v1.26.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /handlers/handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gofiber/fiber/v2" 6 | "github.com/qahta0/image-processing-service/tasks" 7 | "io" 8 | ) 9 | 10 | func UploadImage(c *fiber.Ctx) error { 11 | file, err := c.FormFile("image") 12 | if err != nil { 13 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Upload failed"}) 14 | } 15 | fileData, err := file.Open() 16 | if err != nil { 17 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to open the file"}) 18 | } 19 | defer fileData.Close() 20 | data, err := io.ReadAll(fileData) 21 | if err != nil { 22 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to read the file"}) 23 | } 24 | fileName := file.Filename 25 | resizeTasks, err := tasks.NewImageResizeTasks(data, fileName) 26 | if err != nil { 27 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not create image resize tasks"}) 28 | } 29 | client := tasks.GetClient() 30 | for _, task := range resizeTasks { 31 | if _, err := client.Enqueue(task); err != nil { 32 | fmt.Printf("Error enqueuing task: %v\n", err) 33 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not enqueue image resize task"}) 34 | } 35 | } 36 | return c.JSON(fiber.Map{"message": "Image uploaded and resizing tasks started"}) 37 | } 38 | -------------------------------------------------------------------------------- /tasks/tasks.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/google/uuid" 9 | "github.com/hibiken/asynq" 10 | "github.com/nfnt/resize" 11 | "image" 12 | "image/jpeg" 13 | _ "image/jpeg" 14 | _ "image/png" 15 | "os" 16 | "path/filepath" 17 | "time" 18 | ) 19 | 20 | const TypeResizeImage = "image:resize" 21 | 22 | type ResizeImagePayload struct { 23 | ImageData []byte 24 | Width uint 25 | Height uint 26 | FileName string 27 | } 28 | 29 | var StandardWidths = []uint{16, 32, 128, 240, 320, 480, 540, 640, 800, 1024} 30 | 31 | func NewImageResizeTasks(imageData []byte, fileName string) ([]*asynq.Task, error) { 32 | img, _, err := image.Decode(bytes.NewReader(imageData)) 33 | if err != nil { 34 | return nil, err 35 | } 36 | originalBounds := img.Bounds() 37 | originalWidth := uint(originalBounds.Dx()) 38 | originalHeight := uint(originalBounds.Dy()) 39 | var tasks []*asynq.Task 40 | for _, width := range StandardWidths { 41 | height := (width * originalHeight) / originalWidth 42 | payload := ResizeImagePayload{ 43 | ImageData: imageData, 44 | Width: width, 45 | Height: height, 46 | FileName: fileName, 47 | } 48 | payloadBytes, err := json.Marshal(payload) 49 | if err != nil { 50 | return nil, err 51 | } 52 | task := asynq.NewTask(TypeResizeImage, payloadBytes) 53 | tasks = append(tasks, task) 54 | } 55 | return tasks, nil 56 | } 57 | 58 | func HandleResizeImageTask(ctx context.Context, t *asynq.Task) error { 59 | var payload ResizeImagePayload 60 | if err := json.Unmarshal(t.Payload(), &payload); err != nil { 61 | return fmt.Errorf("failed to parse resize image task payload: %v", err) 62 | } 63 | img, _, err := image.Decode(bytes.NewReader(payload.ImageData)) 64 | if err != nil { 65 | return fmt.Errorf("image decode failed: %v", err) 66 | } 67 | resizedImg := resize.Resize(payload.Width, payload.Height, img, resize.Lanczos3) 68 | outputUUID := uuid.New() 69 | outputFileName := fmt.Sprintf("images/%s/%s%s", time.Now().Format("2006-01-02"), outputUUID.String(), filepath.Ext(payload.FileName)) 70 | outputDir := filepath.Dir(outputFileName) 71 | if _, err := os.Stat(outputDir); os.IsNotExist(err) { 72 | if err := os.MkdirAll(outputDir, 0755); err != nil { 73 | return err 74 | } 75 | } 76 | outFile, err := os.Create(outputFileName) 77 | if err != nil { 78 | return err 79 | } 80 | defer outFile.Close() 81 | if err := jpeg.Encode(outFile, resizedImg, nil); err != nil { 82 | return err 83 | } 84 | fmt.Printf("Output UUID for the processed image: %s\n", outputUUID.String()) 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= 4 | github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= 5 | github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= 6 | github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 7 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 8 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 13 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 14 | github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= 15 | github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= 16 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 17 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 18 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 19 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 20 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 21 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 22 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 24 | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 25 | github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw= 26 | github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts= 27 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 28 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 29 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 30 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 31 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 32 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 33 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 34 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 35 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 36 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 37 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 38 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 39 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 40 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= 44 | github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= 45 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 46 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 47 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 48 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 49 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= 50 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 51 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 52 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 53 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 54 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 55 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 56 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 57 | github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= 58 | github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= 59 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 60 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 61 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 62 | go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= 63 | go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 64 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 65 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 66 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 67 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 68 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 69 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 70 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 71 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 72 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 73 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 74 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 75 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 79 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 83 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 84 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 85 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 86 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 87 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 88 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 89 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 90 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 91 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 92 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 93 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 95 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 96 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 97 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 98 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 99 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 100 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 101 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 102 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 103 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 104 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 105 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 106 | --------------------------------------------------------------------------------