Files
ciimsproxy/cmd/main/main.go
T

207 lines
5.0 KiB
Go

package main
import (
"errors"
"fmt"
"log"
"net/http"
"os"
"strconv"
"github.com/gin-gonic/gin"
"gzzn.com/mini/ciimsproxy/internal"
)
const (
servicePrefix string = "/services/ExchangeService"
defaultTimeout = 240
maxMsgLen = 1048576 // 1MB
maxCount = 1000
)
type Config struct {
ServerURL string
Listen string
Timeout int
Client *internal.Client
}
type App struct {
config Config
}
func main() {
config, err := loadConfig()
if err != nil {
log.Printf("[ERROR] configuration error: %v", err)
os.Exit(1)
}
log.Printf("use ciims: %s", config.ServerURL)
r := newRouter(config)
log.Printf("Starting ciims proxy for %s", config.ServerURL)
log.Printf("timeout: %d", config.Timeout)
if err := r.Run(config.Listen); err != nil {
log.Printf("[ERROR] server failed: %v", err)
os.Exit(1)
}
}
func loadConfig() (Config, error) {
config := Config{
ServerURL: os.Getenv("CIIMS_SERVER"),
Listen: os.Getenv("PROXY_LISTEN"),
Timeout: defaultTimeout,
}
t, err := getIntEnv("CIIMS_TIMEOUT")
if err != nil {
return Config{}, err
}
if t > 0 {
config.Timeout = t
}
if config.Listen == "" {
config.Listen = ":9090"
}
config.Client, err = internal.NewClient(config.Timeout)
if err != nil {
return Config{}, err
}
return config, nil
}
func newRouter(config Config) *gin.Engine {
app := &App{config: config}
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.POST("/send", app.sendMessage)
r.POST("/receive", app.receiveMessage)
return r
}
func (a *App) sendMessage(c *gin.Context) {
// Limit request body size
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxMsgLen)
var message struct {
URL string `json:"url"`
User string `json:"user" binding:"required"`
Pass string `json:"pass" binding:"required"`
Event string `json:"event" binding:"required"`
Priority int `json:"priority"`
Val bool `json:"val"`
Msg string `json:"msg" binding:"required"`
}
if err := c.ShouldBind(&message); err != nil {
a.handleBindError(c, err)
return
}
msg := internal.CreateSend(message.User, message.Pass,
message.Priority, message.Event, message.Val, message.Msg)
url := a.getURL(message.URL)
resp, err := a.config.Client.Send(url, msg)
if err != nil {
a.handleSendError(c, "send", resp, err)
return
}
errMsg := resp.ErrorMessage()
if len(errMsg) > 0 {
log.Printf("[ERROR] SOAP fault: %s", errMsg)
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
c.JSON(http.StatusOK, gin.H{"error": ""})
}
func (a *App) receiveMessage(c *gin.Context) {
// Limit request body size
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxMsgLen)
var message struct {
URL string `json:"url"`
User string `json:"user" binding:"required"`
Pass string `json:"pass" binding:"required"`
Count int `json:"count" binding:"required"`
}
if err := c.ShouldBind(&message); err != nil {
a.handleBindError(c, err)
return
}
if message.Count < 1 || message.Count > maxCount {
c.JSON(http.StatusBadRequest, gin.H{"error": "count must be between 1 and " + strconv.Itoa(maxCount)})
return
}
msg := internal.CreateReceive(message.User, message.Pass, message.Count)
url := a.getURL(message.URL)
resp, err := a.config.Client.Send(url, msg)
if err != nil {
a.handleSendError(c, "receive", resp, err)
return
}
if errMsg := resp.ErrorMessage(); errMsg != "" {
log.Printf("[ERROR] SOAP fault: %s", errMsg)
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
c.JSON(http.StatusOK, gin.H{"msgs": resp.Messages()})
}
func (a *App) getURL(url string) string {
var result string
if len(url) > 0 {
result = url + servicePrefix
} else {
result = a.config.ServerURL + servicePrefix
}
return result
}
func (a *App) handleBindError(c *gin.Context, err error) {
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": err.Error()})
return
}
log.Print(err.Error())
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
func (a *App) handleSendError(c *gin.Context, operation string, resp *internal.CIIMSResponse, err error) {
if resp != nil {
if errMsg := resp.ErrorMessage(); errMsg != "" {
log.Printf("[ERROR] SOAP fault: %s", errMsg)
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
}
var statusErr *internal.HTTPStatusError
if errors.As(err, &statusErr) {
if errMsg := internal.GetErrMsg(statusErr.Body); errMsg != "" {
log.Printf("[ERROR] SOAP fault: %s", errMsg)
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
}
log.Printf("[ERROR] %s failed: %v", operation, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
func getIntEnv(key string) (int, error) {
val := os.Getenv(key)
if val == "" {
return 0, nil
}
ret, err := strconv.Atoi(val)
if err != nil {
return 0, fmt.Errorf("invalid %s value %q: must be an integer", key, val)
}
return ret, nil
}