package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
type Middleware func(http.Handler) http.Handler
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func RecoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
type APIHandler struct {
version string
}
func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.handleGet(w, r)
case http.MethodPost:
h.handlePost(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *APIHandler) handleGet(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{
"version": h.version,
"status": "ok",
}
respondJSON(w, data)
}
func (h *APIHandler) handlePost(w http.ResponseWriter, r *http.Request) {
var payload map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
respondJSON(w, payload)
}
func respondJSON(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
type AppError struct {
Code int
Message string
Err error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}
func NewAppError(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}
func WrapError(err error, message string) *AppError {
return &AppError{Code: 500, Message: message, Err: err}
}
func ReadConfig(path string) (map[string]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("opening config: %w", err)
}
defer file.Close()
data := make(map[string]string)
content, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}
lines := strings.Split(string(content), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
data[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
return data, nil
}
func WriteJSONFile(path string, data interface{}) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(data)
}
func BuildQuery(params map[string]string) string {
var sb strings.Builder
first := true
for key, value := range params {
if !first {
sb.WriteByte('&')
}
first = false
sb.WriteString(key)
sb.WriteByte('=')
sb.WriteString(value)
}
return sb.String()
}
func FormatDuration(d time.Duration) string {
if d < time.Millisecond {
return fmt.Sprintf("%dµs", d.Microseconds())
}
if d < time.Second {
return fmt.Sprintf("%dms", d.Milliseconds())
}
return d.Round(time.Second).String()
}
type Timestamp time.Time
func (t Timestamp) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(t).Format(time.RFC3339))
}
func (t *Timestamp) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
parsed, err := time.Parse(time.RFC3339, s)
if err != nil {
return err
}
*t = Timestamp(parsed)
return nil
}
func main() {
mux := http.NewServeMux()
api := &APIHandler{version: "1.0"}
mux.Handle("/api/", http.StripPrefix("/api", api))
handler := LoggerMiddleware(RecoverMiddleware(mux))
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Println("Server starting on :8080")
log.Fatal(server.ListenAndServe())
}