Skip to content

Commit

Permalink
feat: clamd simple and easy-to-use REST API
Browse files Browse the repository at this point in the history
  • Loading branch information
hq0101 committed Aug 6, 2024
1 parent 6e4d6a4 commit b11a7a8
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 0 deletions.
27 changes: 27 additions & 0 deletions cmd/clamd-api/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"github.com/hq0101/go-clamav/internal/api"
"github.com/spf13/cobra"
"log"
)

func main() {
var cfgFilePath string
var rootCmd = &cobra.Command{
Use: "clamd",
Short: "clamd",
Run: func(cmd *cobra.Command, args []string) {
if _err := api.Init(cfgFilePath); _err != nil {
log.Fatalln(_err)
}
if err := api.Run(); err != nil {
log.Fatalln(err)
}
},
}
rootCmd.PersistentFlags().StringVarP(&cfgFilePath, "config", "c", "./configs/clamav-api.yaml", "config file path")
if err := rootCmd.Execute(); err != nil {
log.Fatalln(err)
}
}
5 changes: 5 additions & 0 deletions configs/clamav-api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
clamd_network_type: "tcp"
clamd_address: "192.168.127.131:3310"
clamd_conn_timeout: "10s"
clamd_read_timeout: "180s"
listen: "0.0.0.0:8080"
43 changes: 43 additions & 0 deletions internal/api/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package api

import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"time"
)

var (
cfg *Config
)

type Config struct {
Listen string `mapstructure:"listen"`
ClamdAddress string `mapstructure:"clamd_address"`
ClamdNetworkType string `mapstructure:"clamd_network_type"`
ClamdConnTimeout time.Duration `mapstructure:"clamd_conn_timeout"`
ClamdReadTimeout time.Duration `mapstructure:"clamd_read_timeout"`
}

func Init(configPath string) error {
viper.SetConfigFile(configPath)
if _err := viper.ReadInConfig(); _err != nil {
return fmt.Errorf("failed to read the configuration file: %w", _err)
}
if _err := viper.Unmarshal(&cfg); _err != nil {
return fmt.Errorf("failed to parse the configuration file: %w", _err)
}
viper.OnConfigChange(func(e fsnotify.Event) {
if _err := viper.Unmarshal(&cfg); _err != nil {
fmt.Println("Failed to reload the configuration file:", _err)
} else {
fmt.Println("reload config file")
}
})
viper.WatchConfig()
return nil
}

func GetCfg() *Config {
return cfg
}
62 changes: 62 additions & 0 deletions internal/api/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/hq0101/go-clamav/docs"
"github.com/prometheus/client_golang/prometheus/promhttp"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"
)

func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")

if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}

c.Next()
}
}

func Run() error {
docs.SwaggerInfo.Title = "ClamAV API"
docs.SwaggerInfo.Description = "This is a sample server for ClamAV"
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "localhost:8080"
docs.SwaggerInfo.BasePath = "/"
docs.SwaggerInfo.Schemes = []string{"http", "https"}

r := gin.Default()
// CORS configuration
r.Use(CORSMiddleware())
r.Use(gin.Recovery())

r.GET("/healthz", func(c *gin.Context) { c.Status(http.StatusOK) })
r.GET("/metrics", gin.WrapH(promhttp.Handler()))

clamd := NewClamd()

r.GET("/ping", clamd.Ping)
r.GET("/version", clamd.Version)
r.GET("/versioncommands", clamd.Version)
r.GET("/stats", clamd.Stats)
r.POST("/reload", clamd.Reload)
r.POST("/shutdown", clamd.Shutdown)
r.GET("/scan", clamd.Scan)
r.GET("/contscan", clamd.Contscan)
r.GET("/multiscan", clamd.MultiScan)
r.GET("/allmatchscan", clamd.MultiScan)
r.POST("/instream", clamd.Instream)

// Use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

return r.Run(GetCfg().Listen)
}

0 comments on commit b11a7a8

Please sign in to comment.