diff --git a/README.md b/README.md
index 8a44ab0..3741584 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,31 @@
# Playground GO
-A simple web server implementation in Go that demonstrates basic HTTP server concepts.
+A modern web server implementation in Go that demonstrates production-ready HTTP server concepts.
## Overview
-This project implements a basic HTTP server that:
-- Listens on a specified address and port
-- Handles HTTP requests
+This project implements a robust HTTP server that:
+- Listens on a configurable port
+- Handles HTTP requests with a custom router
- Processes requests concurrently using goroutines
+- Implements middleware for logging and request processing
+- Provides graceful shutdown capabilities
+- Uses structured logging
+- Follows clean architecture principles
## Project Structure
```
.
-├── main.go # Application entry point
-├── server/
-│ ├── server.go # Server implementation
-│ └── handler.go # HTTP request handlers
-├── go.mod # Go module definition
-└── README.md # This file
+├── main.go # Application entry point
+├── handlers/ # HTTP request handlers
+├── middleware/ # HTTP middleware components
+├── logger/ # Structured logging implementation
+├── internal/ # Internal packages and configuration
+├── static/ # Static file serving
+├── go.mod # Go module definition
+├── .gitignore # Git ignore rules
+└── README.md # This file
```
## Prerequisites
@@ -27,38 +34,49 @@ This project implements a basic HTTP server that:
## Usage
-1. Import the server package in your Go code:
-
-```go
-import "your-module-name/server"
+1. Clone the repository:
+```bash
+git clone https://github.com/yourusername/playground-go.git
+cd playground-go
```
-2. Create a new server instance:
-
-```go
-server := server.NewServer(":8080") // Listen on port 8080
+2. Run the server:
+```bash
+go run main.go
```
-3. Start the server:
-
-```go
-server.ListenAndServe()
-```
+The server will start on the configured port (default: 8080).
## Features
- Concurrent request handling using goroutines
-- HTTP request processing
-- Graceful error handling
-- Simple and clean API design
+- Graceful server shutdown
+- Structured logging
+- Middleware support for request processing
+- Configuration management
+- Clean architecture with separated concerns
+- Static file serving
+- Error handling and logging
+
+## Configuration
+
+The server can be configured through environment variables:
+- `PORT`: The port number to listen on (default: 8080)
+- Additional configuration options can be added in the `internal/config` package
## Error Handling
-The server includes basic error handling for:
-- Address binding errors
-- Request processing errors
-- HTTP response errors
+The server includes comprehensive error handling for:
+- Server startup and shutdown
+- Request processing
+- Configuration loading
+- Graceful shutdown with timeout
+- Structured error logging
## Contributing
-Feel free to submit issues and enhancement requests!
+Feel free to submit issues and enhancement requests! When contributing, please:
+
+1. Fork the repository
+2. Create a new branch for your feature
+3. Submit a pull request with a clear description of your changes
diff --git a/handlers/routes.go b/handlers/routes.go
new file mode 100644
index 0000000..5e99ba0
--- /dev/null
+++ b/handlers/routes.go
@@ -0,0 +1,30 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func RegisterRoutes() http.Handler {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("/", serveIndex)
+ mux.HandleFunc("/hello", helloHandler)
+ mux.HandleFunc("/goodbye", goodbyeHandler)
+
+ mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
+
+ return mux
+}
+
+func serveIndex(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "static/index.html")
+}
+
+func helloHandler(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, "Hello, world!")
+}
+
+func goodbyeHandler(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, "Goodbye, world!")
+}
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..cbbe06d
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,28 @@
+package config
+
+import (
+ "os"
+)
+
+type Config struct {
+ Port string
+ StaticDir string
+ Environment string
+}
+
+var AppConfig Config
+
+func Load() {
+ AppConfig = Config{
+ Port: getEnv("PORT", "7878"),
+ StaticDir: getEnv("STATIC_DIR", "static"),
+ Environment: getEnv("ENVIRONMENT", "development"),
+ }
+}
+
+func getEnv(key, fallback string) string {
+ if value, ok := os.LookupEnv(key); ok {
+ return value
+ }
+ return fallback
+}
diff --git a/logger/logger.go b/logger/logger.go
new file mode 100644
index 0000000..0e41792
--- /dev/null
+++ b/logger/logger.go
@@ -0,0 +1,41 @@
+package logger
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+// ANSI colors
+const (
+ colorReset = "\033[0m"
+ colorRed = "\033[31m"
+ colorYellow = "\033[33m"
+ colorCyan = "\033[36m"
+ colorWhite = "\033[97m"
+)
+
+// Log levels
+const (
+ LevelInfo = "INFO"
+ LevelWarn = "WARN"
+ LevelError = "ERROR"
+ LevelFatal = "FATAL"
+)
+
+func logMessage(level, color, msg string, args ...any) {
+ timestamp := time.Now().Format("2006-01-02 15:04:05")
+ formatted := fmt.Sprintf(msg, args...)
+ output := fmt.Sprintf("[%s] %s[%s]%s %s\n", timestamp, color, level, colorReset, formatted)
+ fmt.Print(output)
+
+ if level == LevelFatal {
+ os.Exit(1)
+ }
+}
+
+// Public logging functions
+func Info(msg string, args ...any) { logMessage(LevelInfo, colorCyan, msg, args...) }
+func Warn(msg string, args ...any) { logMessage(LevelWarn, colorYellow, msg, args...) }
+func Error(msg string, args ...any) { logMessage(LevelError, colorRed, msg, args...) }
+func Fatal(msg string, args ...any) { logMessage(LevelFatal, colorRed, msg, args...) }
diff --git a/main.go b/main.go
index 7a8b17f..cf0bf4e 100644
--- a/main.go
+++ b/main.go
@@ -1,8 +1,54 @@
package main
-import "git.okseby.com/okseby/playground-go/server"
+import (
+ "context"
+ "net/http"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "git.okseby.com/okseby/playground-go/handlers"
+ "git.okseby.com/okseby/playground-go/internal/config"
+ "git.okseby.com/okseby/playground-go/logger"
+ "git.okseby.com/okseby/playground-go/middleware"
+)
func main() {
- server := server.NewServer("0.0.0.0:7878")
- server.ListenAndServe()
+ // Load config
+ config.Load()
+
+ // Register routes
+ mux := handlers.RegisterRoutes()
+ handler := middleware.Logger(mux)
+
+ server := &http.Server{
+ Addr: ":" + config.AppConfig.Port,
+ Handler: handler,
+ }
+
+ // Channel to listen for shutdown signals
+ quit := make(chan os.Signal, 1)
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
+
+ // Run the server in a goroutine
+ go func() {
+ logger.Info("Starting server on port %s...", config.AppConfig.Port)
+ if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ logger.Fatal("Error starting server: %v", err)
+ }
+ }()
+
+ // Wait for shutdown signal
+ <-quit
+ logger.Info("Shutting down...")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ if err := server.Shutdown(ctx); err != nil {
+ logger.Fatal("Server forced to shutdown: %v", err)
+ }
+
+ logger.Info("Server exited cleanly.")
}
diff --git a/middleware/logging.go b/middleware/logging.go
new file mode 100644
index 0000000..c52aa85
--- /dev/null
+++ b/middleware/logging.go
@@ -0,0 +1,56 @@
+package middleware
+
+import (
+ "net/http"
+ "time"
+
+ "git.okseby.com/okseby/playground-go/logger"
+)
+
+// ANSI color codes
+const (
+ ColorReset = "\033[0m"
+ ColorRed = "\033[31m"
+ ColorGreen = "\033[32m"
+ ColorYellow = "\033[33m"
+ ColorCyan = "\033[36m"
+ ColorGrey = "\033[90m"
+)
+
+type statusRecorder struct {
+ http.ResponseWriter
+ status int
+}
+
+func (r *statusRecorder) WriteHeader(code int) {
+ r.status = code
+ r.ResponseWriter.WriteHeader(code)
+}
+
+func Logger(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+
+ recorder := &statusRecorder{ResponseWriter: w, status: 200}
+ next.ServeHTTP(recorder, r)
+
+ duration := time.Since(start)
+
+ statusColor := ColorGreen
+ switch {
+ case recorder.status >= 500:
+ statusColor = ColorRed
+ case recorder.status >= 400:
+ statusColor = ColorRed
+ case recorder.status >= 300:
+ statusColor = ColorYellow
+ }
+
+ logger.Info("%s%-7s%s %s%-30s%s %s%d%s [%s]",
+ ColorCyan, r.Method, ColorReset,
+ ColorGrey, r.URL.Path, ColorReset,
+ statusColor, recorder.status, ColorReset,
+ duration.Round(time.Millisecond),
+ )
+ })
+}
diff --git a/server/handler.go b/server/handler.go
deleted file mode 100644
index f9f66be..0000000
--- a/server/handler.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package server
-
-import (
- "bufio"
- "fmt"
- "io"
- "net"
-)
-
-func handleConnection(conn net.Conn) {
- defer conn.Close()
-
- reader := bufio.NewReader(conn)
- request, err := reader.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- fmt.Println("read request error:", err)
- }
- return
- }
-
- fmt.Println("request:", request)
-
- response := "HTTP/1.1 200 OK\r\n" +
- "Content-Length: 13\r\n" +
- "Content-Type: text/plain\r\n\r\n" +
- "Hello, world!"
-
- conn.Write([]byte(response))
-}
diff --git a/server/server.go b/server/server.go
deleted file mode 100644
index d14f16a..0000000
--- a/server/server.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package server
-
-import (
- "fmt"
- "net"
-)
-
-type Server struct {
- addr string
-}
-
-func NewServer(addr string) *Server {
- return &Server{addr: addr}
-}
-
-func (s *Server) ListenAndServe() {
- listener, err := net.Listen("tcp", s.addr)
- if err != nil {
- fmt.Println("Error binding to address:", err)
- }
- defer listener.Close()
-
- fmt.Printf("Server listening on %s\n", s.addr)
-
- for {
- conn, err := listener.Accept()
- if err != nil {
- fmt.Println("Error accepting connection:", err)
- continue
- }
- go handleConnection(conn)
- }
-}
diff --git a/static/index.html b/static/index.html
new file mode 100644
index 0000000..b89c5c4
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+ Cool Web Server
+
+
+
+
+
+
+
+
+
Fast & Efficient
+
Built with performance in mind, ensuring quick response times and optimal resource usage.
+
+
+
Modern Stack
+
Utilizing the latest web technologies and best practices for a robust server implementation.
+
+
+
Easy to Use
+
Simple configuration and straightforward setup process for hassle-free deployment.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/styles/index.css b/static/styles/index.css
new file mode 100644
index 0000000..97d6daf
--- /dev/null
+++ b/static/styles/index.css
@@ -0,0 +1,110 @@
+:root {
+ --primary-color: #2563eb;
+ --secondary-color: #1e40af;
+ --background-color: #f8fafc;
+ --text-color: #1e293b;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
+ line-height: 1.6;
+ color: var(--text-color);
+ background-color: var(--background-color);
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+header {
+ text-align: center;
+ padding: 4rem 0;
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
+ color: white;
+ margin-bottom: 3rem;
+}
+
+h1 {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+ animation: fadeIn 1s ease-in;
+}
+
+.subtitle {
+ font-size: 1.2rem;
+ opacity: 0.9;
+}
+
+.features {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+ margin-bottom: 3rem;
+}
+
+.feature-card {
+ background: white;
+ padding: 2rem;
+ border-radius: 10px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: transform 0.3s ease;
+}
+
+.feature-card:hover {
+ transform: translateY(-5px);
+}
+
+.feature-card h3 {
+ color: var(--primary-color);
+ margin-bottom: 1rem;
+}
+
+.cta-button {
+ display: inline-block;
+ background-color: var(--primary-color);
+ color: white;
+ padding: 1rem 2rem;
+ border-radius: 5px;
+ text-decoration: none;
+ transition: background-color 0.3s ease;
+}
+
+.cta-button:hover {
+ background-color: var(--secondary-color);
+}
+
+footer {
+ text-align: center;
+ padding: 2rem;
+ margin-top: 3rem;
+ border-top: 1px solid #e2e8f0;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@media (max-width: 768px) {
+ .container {
+ padding: 1rem;
+ }
+
+ h1 {
+ font-size: 2rem;
+ }
+}
\ No newline at end of file