feat: some refactoring
This commit is contained in:
0
internal/apiserver/docs/.gitkeep
Normal file
0
internal/apiserver/docs/.gitkeep
Normal file
37
internal/apiserver/handlers/home.go
Normal file
37
internal/apiserver/handlers/home.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/models"
|
||||
)
|
||||
|
||||
// HomeHandler handles the root endpoint
|
||||
type HomeHandler struct{}
|
||||
|
||||
func NewHomeHandler() *HomeHandler {
|
||||
return &HomeHandler{}
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHome(w, r)
|
||||
}
|
||||
|
||||
// @Summary Health check
|
||||
// @Description Проверка состояния сервера
|
||||
// @Tags Health
|
||||
// @Success 200 {object} models.Response "Server is running"
|
||||
// @Router / [get]
|
||||
func (h *HomeHandler) handleHome(w http.ResponseWriter, r *http.Request) {
|
||||
// Create a simple success response
|
||||
response := models.NewSuccessResponse(map[string]string{
|
||||
"status": "ok",
|
||||
"message": "API Server is running",
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
41
internal/apiserver/handlers/home_test.go
Normal file
41
internal/apiserver/handlers/home_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHomeHandler_ServeHTTP(t *testing.T) {
|
||||
// Create a new home handler
|
||||
handler := NewHomeHandler()
|
||||
|
||||
// Create a test request
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
|
||||
// Create a test response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Call the handler
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
// Check status code
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
status, http.StatusOK)
|
||||
}
|
||||
|
||||
// Check content type
|
||||
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
|
||||
t.Errorf("handler returned wrong content type: got %v want %v",
|
||||
ct, "application/json")
|
||||
}
|
||||
|
||||
// Check response body contains expected data
|
||||
expectedBody := `"success":true`
|
||||
if body := rr.Body.String(); !strings.Contains(body, expectedBody) {
|
||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
||||
body, expectedBody)
|
||||
}
|
||||
}
|
||||
76
internal/apiserver/logger/logger.go
Normal file
76
internal/apiserver/logger/logger.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
logger *logrus.Logger
|
||||
once sync.Once
|
||||
initialized bool
|
||||
)
|
||||
|
||||
// Initialize logger with configuration
|
||||
func Initialize(level string, format string, output string) {
|
||||
once.Do(func() {
|
||||
logger = logrus.New()
|
||||
|
||||
// Set level
|
||||
lvl, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
lvl = logrus.InfoLevel
|
||||
}
|
||||
logger.SetLevel(lvl)
|
||||
|
||||
// Set format
|
||||
switch format {
|
||||
case "json":
|
||||
logger.SetFormatter(&logrus.JSONFormatter{
|
||||
TimestampFormat: "2006-01-02T15:04:05.000Z",
|
||||
})
|
||||
case "text":
|
||||
logger.SetFormatter(&logrus.TextFormatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
FullTimestamp: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Set output
|
||||
if output != "" {
|
||||
//TODO: Use files
|
||||
}
|
||||
|
||||
initialized = true
|
||||
})
|
||||
}
|
||||
|
||||
// GetLogger returns the singleton logger instance
|
||||
func GetLogger() *logrus.Logger {
|
||||
if !initialized {
|
||||
// Initialize with defaults
|
||||
Initialize("info", "json", "stdout")
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// WithFields creates a logger with additional fields
|
||||
func WithFields(fields map[string]interface{}) *logrus.Entry {
|
||||
return GetLogger().WithFields(fields)
|
||||
}
|
||||
|
||||
// Info logs an info message
|
||||
func Info(msg string) {
|
||||
GetLogger().Info(msg)
|
||||
}
|
||||
|
||||
// Error logs an error message
|
||||
func Error(msg string) {
|
||||
GetLogger().Error(msg)
|
||||
}
|
||||
|
||||
// Debug logs a debug message
|
||||
func Debug(msg string) {
|
||||
GetLogger().Debug(msg)
|
||||
}
|
||||
42
internal/apiserver/middleware/logging.go
Normal file
42
internal/apiserver/middleware/logging.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
|
||||
rhttp "git.nwaifu.su/sergey/MyGoServer/pkg/http"
|
||||
)
|
||||
|
||||
// LoggingMiddleware logs HTTP requests
|
||||
func LoggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// Use the custom response writer to capture status code
|
||||
rw := rhttp.NewResponseWriter(w)
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
|
||||
// Log the request
|
||||
logger := logger.GetLogger()
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"method": r.Method,
|
||||
"uri": r.RequestURI,
|
||||
"remote_addr": r.RemoteAddr,
|
||||
"user_agent": r.UserAgent(),
|
||||
"status_code": rw.GetStatusCode(),
|
||||
"duration": time.Since(start).String(),
|
||||
"request_id": getRequestID(r.Context()),
|
||||
}).Info("HTTP request")
|
||||
})
|
||||
}
|
||||
|
||||
// getRequestID retrieves request ID from context
|
||||
func getRequestID(ctx context.Context) string {
|
||||
if id, ok := ctx.Value("request_id").(string); ok {
|
||||
return id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
33
internal/apiserver/middleware/request_id.go
Normal file
33
internal/apiserver/middleware/request_id.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// RequestIDMiddleware adds a unique request ID to each request
|
||||
func RequestIDMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Generate a new request ID
|
||||
requestID := uuid.New().String()
|
||||
|
||||
// Set request ID in response header
|
||||
w.Header().Set("X-Request-ID", requestID)
|
||||
|
||||
// Add request ID to context
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, "request_id", requestID)
|
||||
|
||||
// Log the request ID assignment
|
||||
logger := logger.GetLogger()
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"request_id": requestID,
|
||||
}).Debug("Request ID assigned")
|
||||
|
||||
// Continue with the request
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
25
internal/apiserver/models/response.go
Normal file
25
internal/apiserver/models/response.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package models
|
||||
|
||||
// Response represents a standard API response
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
}
|
||||
|
||||
// NewSuccessResponse creates a successful response
|
||||
func NewSuccessResponse(data interface{}) *Response {
|
||||
return &Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// NewErrorResponse creates an error response
|
||||
func NewErrorResponse(err error) *Response {
|
||||
return &Response{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
22
internal/apiserver/server/routes.go
Normal file
22
internal/apiserver/server/routes.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
_ "git.nwaifu.su/sergey/MyGoServer/internal/apiserver/docs"
|
||||
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/handlers"
|
||||
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/middleware"
|
||||
|
||||
httpSwagger "github.com/swaggo/http-swagger"
|
||||
)
|
||||
|
||||
// setupRoutes configures all routes
|
||||
func (s *Server) setupRoutes() {
|
||||
// Add request ID middleware to all routes
|
||||
s.router.Handle("/", middleware.RequestIDMiddleware(
|
||||
middleware.LoggingMiddleware(
|
||||
handlers.NewHomeHandler(),
|
||||
),
|
||||
))
|
||||
|
||||
// Swagger UI
|
||||
s.router.Handle("/swagger/", httpSwagger.WrapHandler)
|
||||
}
|
||||
70
internal/apiserver/server/server.go
Normal file
70
internal/apiserver/server/server.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"git.nwaifu.su/sergey/MyGoServer/cmd/apiserver/config"
|
||||
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Server represents the HTTP server
|
||||
type Server struct {
|
||||
config *config.Config
|
||||
router *http.ServeMux
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// NewServer creates a new server instance
|
||||
func NewServer(cfg *config.Config) *Server {
|
||||
s := &Server{
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
// Initialize logger
|
||||
logger.Initialize(cfg.Logging.Level, cfg.Logging.Format, cfg.Logging.Output)
|
||||
|
||||
// Create router
|
||||
s.router = http.NewServeMux()
|
||||
s.setupRoutes()
|
||||
|
||||
// Create HTTP server
|
||||
s.server = &http.Server{
|
||||
Addr: cfg.Server.Address,
|
||||
Handler: s.router,
|
||||
ConnContext: saveConnInContext,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the server
|
||||
func (s *Server) Start() error {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
// GetRouter returns the HTTP router
|
||||
func (s *Server) GetRouter() *http.ServeMux {
|
||||
return s.router
|
||||
}
|
||||
|
||||
// GetServer returns the HTTP server instance
|
||||
func (s *Server) GetServer() *http.Server {
|
||||
return s.server
|
||||
}
|
||||
|
||||
// GetConfig returns the server configuration
|
||||
func (s *Server) GetConfig() *config.Config {
|
||||
return s.config
|
||||
}
|
||||
0
internal/apiserver/services/.gitkeep
Normal file
0
internal/apiserver/services/.gitkeep
Normal file
0
internal/apiserver/utils/.gitkeep
Normal file
0
internal/apiserver/utils/.gitkeep
Normal file
@@ -1,29 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
0
internal/pkg/errors/.gitkeep
Normal file
0
internal/pkg/errors/.gitkeep
Normal file
0
internal/pkg/validators/.gitkeep
Normal file
0
internal/pkg/validators/.gitkeep
Normal file
Reference in New Issue
Block a user