Я создаю приемник для webhooks в Go, это мое первое приложение в Go.
Я протестировал приложение локально, и оно работает там. Но теперь я развернул его на своем сервере Ubuntu в контейнере Docker за прокси-сервером NGINX (прокси находится вне Docker). pingHandler
работает, а gitlabHandler
может отправить сообщение 403. Но если токен действителен, я всегда буду видеть сообщение 502, и журнал NGINX сообщает мне:
*1115 upstream prematurely closed connection while reading response header from upstream, client: X.X.X.X, server: myserver.home.example, request: "POST /webhookreceiver/gitlab HTTP/1.1", upstream: "http://127.0.0.1:7080/gitlab", host: "myserver.home.example"
Также, когда я отправляю недопустимую полезную нагрузку JSON, она все равно выдаст то же сообщение об ошибке, поэтому, если я правильно понимаю ошибку, мое приложение Go закрывает соединение где-то до строки 72-76. Я предполагаю, что что-то не так с строкой 65?
Для большинства других людей, имеющих эту проблему, ее можно решить, увеличив тайм-аут, поскольку их запросы слишком велики, но в моем случае я тестирую сообщение JSON размером всего в несколько байтов.
main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
)
type ServerConfig struct {
Https bool `json:"https"`
Cert string `json:"cert"`
Key string `json:"key"`
Gitlabtoken string `json:"gitlab_token"`
}
type SplunkConfig struct {
Token string
Url string
Metadata map[string]string
}
type SplunkEvent struct {
Host string `json:"host"`
Sourcetype string `json:"sourcetype"`
Index string `json:"index"`
Source string `json:"source"`
Event map[string]interface{} `json:"event"`
}
var splunk_config SplunkConfig
var server_config ServerConfig
type middleware func(next http.HandlerFunc) http.HandlerFunc
func withLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("IP %s", r.RemoteAddr)
next.ServeHTTP(w, r)
}
}
func chainMiddleware(mw ...middleware) middleware {
return func(final http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
last := final
for i := len(mw) - 1; i >= 0; i-- {
last = mw[i](last)
}
last(w, r)
}
}
}
func gitlabHandler(w http.ResponseWriter, req *http.Request) {
// Check if GitLab token is present and correct
gitlab_header := req.Header.Get("X-Gitlab-Token")
if gitlab_header == server_config.Gitlabtoken {
// Create SplunkEvent to send to Splunk
body, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error reading body"))
return
}
var event map[string]interface{}
err = json.Unmarshal(body, &event)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("400 - Request cannot be parsed"))
return
}
se := SplunkEvent{
Host: splunk_config.Metadata["host"],
Sourcetype: splunk_config.Metadata["sourcetype"],
Index: splunk_config.Metadata["index"],
Source: "gitlab",
Event: event}
j, err := json.Marshal(se)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error creating forwarding call"))
return
}
// Send SplunkEvent to Splunk
b := bytes.NewBuffer(j)
client := &http.Client{
Timeout: time.Second * 30,
}
req, err := http.NewRequest("POST", splunk_config.Url, b)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error creating request"))
return
}
req.Header.Add("Authorization", "Splunk "+splunk_config.Token)
resp, err := client.Do(req)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error sending request"))
return
}
// Check response
bod, err := ioutil.ReadAll(resp.Body)
if strings.Contains(string(bod), "Success") {
log.Println("Received and succesfully processed request")
w.WriteHeader(http.StatusOK)
w.Write([]byte("200 - OK"))
return
} else {
log.Println(string(bod))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error sending to Splunk"))
return
}
defer resp.Body.Close()
} else {
log.Println("Incorrect Gitlab token")
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("403 - Forbidden"))
return
}
}
func pingHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received ping call")
fmt.Fprint(w, "{\"check\": \"online\"}")
}
func main() {
// Setup logging
file, err := os.OpenFile("webhookreceiver.log", os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("Error opening log file: " + err.Error())
}
defer file.Close()
log.SetOutput(file)
// Load Splunk config
data, err := ioutil.ReadFile("configs/splunk.json")
if err != nil {
log.Fatal("Error reading splunk_config.json: " + err.Error())
}
err = json.Unmarshal(data, &splunk_config)
if err != nil {
log.Fatal("Error on unmarshal of Splunk config: " + err.Error())
}
// Load server config
data, err = ioutil.ReadFile("configs/server.json")
if err != nil {
log.Fatal("Error reading server_config.json: " + err.Error())
}
err = json.Unmarshal(data, &server_config)
if err != nil {
log.Fatal("Error on unmarshal of Server config: " + err.Error())
}
// Start server
log.Println("Starting server")
mw := chainMiddleware(withLogging)
http.Handle("/gitlab", mw(gitlabHandler))
http.HandleFunc("/ping", mw(pingHandler))
if server_config.Https {
log.Fatal(http.ListenAndServeTLS(":7443", server_config.Cert, server_config.Key, nil))
} else {
log.Fatal(http.ListenAndServe(":7080", nil))
}
}
Соответствующий бит конфигурации NGINX
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /blabla/fullchain.pem;
ssl_certificate_key /blabla/privkey.pem;
client_max_body_size 3M;
add_header Strict-Transport-Security "max-age=31536000" always;
location /webhookreceiver/ {
proxy_pass http://127.0.0.1:7080/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Также регистрация моего приложения Go работает локально, но не внутри моего контейнера Docker, файл журнала создается, но он остается пустым. Поэтому на данный момент у меня нет никаких сообщений оттуда, если кто-то не знает, почему это так; -)