-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserver.go
160 lines (134 loc) · 4.05 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// Configuration defines the single config dict from the config.json file.
type Configuration struct {
Host string `json:"host"`
Targets []string `json:"targets"`
}
// MessageResponse defines the output schema for all the endpoints.
type MessageResponse struct {
Message string `json:"message"`
}
/**
This file opens the `config.json` file and reads the contents and returns the data.
This is used by other functions to get the configurations.
*/
func getConfigurations() []Configuration {
configFileName := "config.json"
configFile, err := os.Open(configFileName)
// error while opening the file
if err != nil {
fmt.Println("Error while opening configuration file.", err)
fmt.Printf("Make sure the `%s` file is present in the project root.\n", configFileName)
os.Exit(3)
}
// read the file and get contents
defer configFile.Close()
byteValue, _ := ioutil.ReadAll(configFile)
var configurations []Configuration
json.Unmarshal(byteValue, &configurations)
return configurations
}
func main() {
// check if everything is fine by getting the configurations
getConfigurations()
// server instance
server := echo.New()
// middlewares & config
server.Pre(middleware.AddTrailingSlash())
// url mapping
server.Any("/webhook/", webhookHandler)
server.GET("/ping/", pingHandler)
// running the server
server.Logger.Fatal(server.Start(":8080"))
}
/**
Handler for the `/ping` endpoint. This is used as a status check for the application.
Like, if in case this is needed for anything.
*/
func pingHandler(context echo.Context) error {
return context.JSON(http.StatusOK, MessageResponse{"pong"})
}
/**
Main handler for all the webhooks. This is called using any method, from any where.
Based on the config.json file this endpoint handles and demultiplexes the requests.
*/
func webhookHandler(context echo.Context) error {
request := context.Request()
// getting the response body
inboundData := echo.Map{}
err := context.Bind(&inboundData)
checkAndPanic(err)
// check given config based on inbound data | de-multiplexer operation
for _, config := range getConfigurations() {
if config.Host == request.Host || config.Host == "*" {
for _, target := range config.Targets {
// forward the request
forwardRequest(target, inboundData, request)
}
}
}
return context.JSON(http.StatusOK, MessageResponse{"done"})
}
/**
This is the main function used to forward the inbound values to specified targets in the
the config.json file. This is called from the handler for the `/webhook/` endpoint.
*/
func forwardRequest(target string, inboundData echo.Map, inboundRequest *http.Request) {
var processedOutputData io.Reader
// if body is present, process the data, else let it be nil
if inboundData != nil && len(inboundData) > 0 {
byteArrayData, _ := json.Marshal(inboundData)
processedOutputData = bytes.NewBuffer(byteArrayData)
}
// prepare the request to be sent
outboundRequest, _ := http.NewRequest(
inboundRequest.Method,
target,
processedOutputData,
)
for headerKey, headerValues := range inboundRequest.Header {
outboundRequest.Header.Set(headerKey, strings.Join(headerValues, ", "))
}
client := &http.Client{}
response, err := client.Do(outboundRequest)
checkAndPanic(err)
defer response.Body.Close()
responseBody, _ := ioutil.ReadAll(response.Body)
// make the string log
log := fmt.Sprintf(
"\nFrom: %s \nTarget: %s \nResponse Code: %d \nResponse Body: %s \nTime: %s \n",
inboundRequest.Host,
target,
response.StatusCode,
string(responseBody),
time.Now().String(),
)
// console log
fmt.Println(log)
// file log
file, _ := os.OpenFile("events.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
defer file.Close()
file.WriteString(log)
}
/**
This function checks if there are errors present. If so, calls the `panic()` function.
This is defined as a function to make things DRY.
*/
func checkAndPanic(e error) {
if e != nil {
panic(e)
}
}