This commit is contained in:
2025-11-20 13:11:16 +03:00
commit 8b41b82d24
7 changed files with 240 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Code coverage profiles and other test artifacts
*.out
coverage.*
*.coverprofile
profile.cov
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env
# Editor/IDE
# .idea/
.vscode/
!.vscode/settings.json

23
cmd/apiserver/main.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"flag"
"log"
"git.nwaifu.su/sergey/MyGoServer/internal/app/apiserver"
)
var (
configPath string
)
func init() {
flag.StringVar(&configPath, "config-path", "/go/bin/configs/server.toml", "path to config")
}
func main() {
flag.Parse()
if err := apiserver.Start(); err != nil {
log.Fatal(err)
}
}

14
go.mod Normal file
View File

@@ -0,0 +1,14 @@
module git.nwaifu.su/sergey/MyGoServer
go 1.25.4
require (
github.com/felixge/httpsnoop v1.0.4
github.com/gorilla/mux v1.8.1
github.com/sirupsen/logrus v1.9.3
)
require (
github.com/google/uuid v1.6.0
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)

21
go.sum Normal file
View File

@@ -0,0 +1,21 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,29 @@
package apiserver
import (
"context"
"net"
"net/http"
)
type contextKey struct {
key string
}
var connContextKey = &contextKey{"http-conn"}
func saveConnInContext(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, connContextKey, c)
}
// Start the server
func Start() error {
srv := newServer()
server := http.Server{
Addr: ":8080",
ConnContext: saveConnInContext,
Handler: srv,
}
return server.ListenAndServe()
}

View File

@@ -0,0 +1,39 @@
package apiserver
import (
"bufio"
"errors"
"net"
"net/http"
"github.com/felixge/httpsnoop"
)
// Custom RW implementation
type ResponseWriter struct {
http.ResponseWriter
code int
Hijacker http.Hijacker
}
// Write status code to header
func (w *ResponseWriter) WriteHeader(statusCode int) {
w.code = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
// Get new RW
func newResponseWriter(w http.ResponseWriter) *ResponseWriter {
hijacker, _ := w.(http.Hijacker)
return &ResponseWriter{
ResponseWriter: httpsnoop.Wrap(w, httpsnoop.Hooks{}),
Hijacker: hijacker,
}
}
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if w.Hijacker == nil {
return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter")
}
return w.Hijacker.Hijack()
}

View File

@@ -0,0 +1,81 @@
package apiserver
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
const (
ctxKeyRequestID = iota
)
type server struct {
router *mux.Router
logger *logrus.Logger
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.router.ServeHTTP(w, r)
}
func newServer() *server {
s := &server{
router: mux.NewRouter(),
logger: logrus.New(),
}
s.configureRouter()
return s
}
func (s *server) configureRouter() {
s.router.Use(s.setRequestID)
s.router.Use(s.logRequest)
s.router.HandleFunc("/", s.handleHome())
}
func (s *server) handleHome() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}
}
func (s *server) logRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger := logrus.WithFields(logrus.Fields{
"remote_addr": r.RemoteAddr,
"request_id": r.Context().Value(ctxKeyRequestID),
})
logger.Infof("started %s %s", r.Method, r.RequestURI)
start := time.Now()
rw := newResponseWriter(w)
rw.code = http.StatusOK
next.ServeHTTP(rw, r)
logger.Infof("completed with %d %s in %v", rw.code, http.StatusText(rw.code), time.Since(start))
})
}
func (s *server) setRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := uuid.New().String()
w.Header().Set("X-Request-ID", id)
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyRequestID, id)))
})
}
// Helpers
func (s *server) error(w http.ResponseWriter, r *http.Request, status int, err error) {
s.respond(w, r, status, map[string]string{"error": err.Error()})
}
func (s *server) respond(w http.ResponseWriter, r *http.Request, status int, data interface{}) {
w.WriteHeader(status)
if data != nil {
json.NewEncoder(w).Encode(data)
}
}