package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)
type Handler func(http.ResponseWriter, *http.Request) error
type ErrorResponse struct {
Error string `json:"error"`
Code int `json:"code"`
Message string `json:"message"`
}
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
respondWithError(w, err)
}
}
func respondWithError(w http.ResponseWriter, err error) {
code := http.StatusInternalServerError
if httpErr, ok := err.(HTTPError); ok {
code = httpErr.Code
}
w.WriteHeader(code)
json.NewEncoder(w).Encode(ErrorResponse{
Error: err.Error(),
Code: code,
Message: "Request failed",
})
}
type HTTPError struct {
Code int
Msg string
}
func (e HTTPError) Error() string {
return e.Msg
}
type Middleware func(http.Handler) http.Handler
func LoggingMiddleware(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 %s", r.Method, r.URL.Path, time.Since(start))
})
}
func RecoveryMiddleware(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 recovered: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func AuthMiddleware(token string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader != "Bearer "+token {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func handleUsers(w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case http.MethodGet:
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
return respondWithJSON(w, users, http.StatusOK)
case http.MethodPost:
body, err := io.ReadAll(r.Body)
if err != nil {
return HTTPError{Code: http.StatusBadRequest, Msg: "Invalid body"}
}
var user User
if err := json.Unmarshal(body, &user); err != nil {
return HTTPError{Code: http.StatusBadRequest, Msg: "Invalid JSON"}
}
user.ID = 3
return respondWithJSON(w, user, http.StatusCreated)
default:
return HTTPError{Code: http.StatusMethodNotAllowed, Msg: "Method not allowed"}
}
}
func respondWithJSON(w http.ResponseWriter, data interface{}, code int) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
return json.NewEncoder(w).Encode(data)
}
type HTTPClient struct {
BaseURL string
HTTPClient *http.Client
Headers map[string]string
}
func NewHTTPClient(baseURL string) *HTTPClient {
return &HTTPClient{
BaseURL: baseURL,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
Headers: make(map[string]string),
}
}
func (c *HTTPClient) Get(endpoint string, result interface{}) error {
req, err := http.NewRequest(http.MethodGet, c.BaseURL+endpoint, nil)
if err != nil {
return err
}
for k, v := range c.Headers {
req.Header.Set(k, v)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("HTTP %d", resp.StatusCode)
}
return json.NewDecoder(resp.Body).Decode(result)
}
func (c *HTTPClient) Post(endpoint string, body, result interface{}) error {
jsonBody, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, c.BaseURL+endpoint, nil)
if err != nil {
return err
}
req.Body = nil
req.GetBody = nil
req.ContentLength = int64(len(jsonBody))
req.Body = &bodyReader{data: jsonBody}
req.Header.Set("Content-Type", "application/json")
for k, v := range c.Headers {
req.Header.Set(k, v)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("HTTP %d", resp.StatusCode)
}
return json.NewDecoder(resp.Body).Decode(result)
}
type bodyReader struct {
data []byte
pos int
}
func (b *bodyReader) Read(p []byte) (int, error) {
if b.pos >= len(b.data) {
return 0, nil
}
n := copy(p, b.data[b.pos:])
b.pos += n
return n, nil
}
func (b *bodyReader) Close() error {
return nil
}
func ServerExample() {
mux := http.NewServeMux()
mux.Handle("/users", Handler(handleUsers))
handler := Chain(mux,
RecoveryMiddleware,
LoggingMiddleware,
)
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
func main() {
ServerExample()
}