Skip to content

A Go-based HTTP reverse proxy featuring load balancing, rate limiting, and CORS support with Kubernetes deployments.

Notifications You must be signed in to change notification settings

montybechir/http-reverse-proxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP Reverse Proxy

A Go-based HTTP reverse proxy featuring load balancing, rate limiting, and CORS support. This reverse proxy is designed without relying on third-party implementations or the net/http/httputil package, ensuring a lightweight and customizable solution.

Assumption: the use of other packages unrelated to reverse proxy implementations, such as uber-go/zap for logging, was permitted.

Table of Contents

  • Features
  • Getting Started
  • Prerequisites
  • Installation
  • Configuration
  • Running the Proxy
  • Usage
  • Project Structure
  • Testing
  • Design Decisions & Limitations
  • Scaling
  • Security Enhancements
  • Resources

Features

Load Balancing (Round Robin):

  • Distributes incoming requests evenly across healthy backend servers using a simple Round Robin algorithm.
  • Easily extendable to other balancing strategies, such as Least Connections or Weighted Round Robin.

Rate Limiting:

  • Prevents abuse by limiting the number of requests a client can make within a specified timeframe.
  • Configurable to adjust thresholds based on application needs.

CORS Support:

  • Configurable to allow connections from any origin or restrict to specific hosts.
  • Customizable headers and methods to control cross-origin requests.

Health Checks with Recovery:

  • Continuously monitors the health of backend servers.
  • Automatically reintegrates recovered backends into the pool without manual intervention.
  • Ensures the proxy only forwards requests to active and healthy backends.

Multiple Backend Support:

  • Easily configurable to support multiple backend servers via YAML configuration files.
  • Ensures high availability by requiring at least one healthy backend to operate.

Docker Integration:

  • Dockerfiles provided for the proxy and backend servers for streamlined containerization.
  • docker-compose setup included for easy orchestration and deployment.

Prerequisites

  • Docker & Docker Compose
  • Go 1.22+

Deployment

For detailed deployment instructions see Deployments Guide.

Quick Start

git clone https://github.com/montybechir/http-reverse-proxy.git

# or

git clone git@github.com:montybechir/http-reverse-proxy.git

Configure Docker Resource Sharing:

Add the project directory (e.g., /Users/montasir/dev/http-reverse-proxy) to Docker's file sharing settings. For Mac:

  1. Navigate to Docker -> Preferences... -> Resources -> File Sharing.
  2. Add the project directory and apply changes.
  3. Restart Docker if necessary.
  4. Docker File Sharing Documentation

Configuration

Edit Configuration Files:

  • Modify /configs/config.yaml to set proxy configurations.
  • Add or update backend configurations in /configs/backenda.yaml and /configs/backendb.yaml.
  • Ensure at least one backend is active to allow the proxy to start successfully.

Build and run the container

docker-compose -f deploy/docker-compose.yaml up --build

Verify Successful Launch:

Monitor the logs to ensure all services (proxy and backends) start without errors.

Example successful log entries:

 ✔ Service backendb  Built                                                                                              2.0s
 ✔ Service backenda  Built                                                                                              2.1s
 ✔ Service proxy     Built                                                                                              0.5s
Attaching to backenda-1, backendb-1, proxy-1
backendb-1  | {"level":"info","timestamp":"2025-01-14T17:40:59.728Z","caller":"server/server.go:88","msg":"Server is starting","address":":60409"}
backenda-1  | {"level":"info","timestamp":"2025-01-14T17:40:59.728Z","caller":"server/server.go:88","msg":"Server is starting","address":":60408"}
backenda-1  | {"level":"info","timestamp":"2025-01-14T17:40:59.839Z","caller":"middleware/logging.go:19","msg":"Incoming request","method":"GET","path":"/health","remote_addr":"172.18.0.4:58352"}
backenda-1  | {"level":"info","timestamp":"2025-01-14T17:40:59.943Z","caller":"middleware/logging.go:29","msg":"Completed request","status":200,"method":"GET","path":"/health","duration":0.103956}
backendb-1  | {"level":"info","timestamp":"2025-01-14T17:40:59.955Z","caller":"middleware/logging.go:19","msg":"Incoming request","method":"GET","path":"/health","remote_addr":"172.18.0.4:42270"}
backendb-1  | {"level":"info","timestamp":"2025-01-14T17:41:00.060Z","caller":"middleware/logging.go:29","msg":"Completed request","status":200,"method":"GET","path":"/health","duration":0.1035535}
proxy-1     | {"level":"info","timestamp":"2025-01-14T17:41:00.066Z","caller":"loadbalancer/loadbalancer.go:58","msg":"Ensuring at least one backend is healthy"}
proxy-1     | {"level":"info","timestamp":"2025-01-14T17:41:00.070Z","caller":"proxy/main.go:71","msg":"Starting server","address":":8080"}
proxy-1     | {"level":"info","timestamp":"2025-01-14T17:41:09.843Z","caller":"middleware/logging.go:19","msg":"Incoming request","method":"GET","path":"/health","remote_addr":"[::1]:53694"}
proxy-1     | {"level":"info","timestamp":"2025-01-14T17:41:09.948Z","caller":"proxy/handler.go:89","msg":"Request proxied successfully","method":"GET","path":"/health","backend":"http://backenda:60408/health","bytes_written":23,"status":200}

Usage

You can interact with the proxy using curl or API clients like Postman.

curl localhost:8080/pleasefowardthiscommand -v

Expected result

MB-Machine:~ montasir$ curl localhost:8080/pleasefowardthiscommand -v
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET /pleasefowardthiscommand HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
< Access-Control-Allow-Origin:
< Content-Length: 23
< Content-Type: text/plain; charset=utf-8
< Date: Tue, 14 Jan 2025 17:43:59 GMT
<
* Connection #0 to host localhost left intact
Response from Backend B

Project Structure

http-reverse-proxy/
├── cmd/
│   └── proxy/
│       └── main.go
│   └── backenda/
│       └── main.go
│   └── backendb/
│       └── main.go
├── internal/
│   ├── proxy/
│   │   ├── handler.go
│   │   └── proxy.go
│   │   └── router.go
│   ├── loadbalancer/
│   │   └── loadbalancer.go
│   └── middleware/
│       ├── logging.go
│       ├── middleware.go
│       ├── rate_limiting.go
│       └── cors.go
├── pkg/
│   ├── logger/
│   │   ├── logger.go
│   ├── models/
│   │   ├── config.go
│   ├── server/
│   │   ├── server.go
│   └── utils/
│       └── config.go
├── deploy/
│   ├── k8s/
│   │   ├── proxy-config.yaml
│   │   └── reverse-proxy.yaml
│   │   └── webapp.yaml
├── ├── docker-compose.yaml
│   ├── README.md
├── configs/
│   ├── config.yaml
│   └── backenda.yaml
│   └── backendb.yaml
├── scripts/
│   └── wait-for.sh
│   └── run-tests.sh
├── tests/
│   ├── helpers/
│   │   ├── logger.go
│   │   └── mock.go
│   │   ├── proxy.go
│   │   └── utils.go
│   ├── utils/
│   │   ├── cors_test.go
│   │   └── health_test.go
│   │   ├── loadbalancer_test.go
│   │   └── proxy_test.go
│   │   ├── rate_limiting_test.go
│   │   └── status_test.go
├── build/
│   ├── package/
│   │   ├── proxy/
│   │   │    └── Dockerfile
│   │   ├── backenda/
│   │   │    └── Dockerfile
│   │   ├── backendb/
│   │   │    └── Dockerfile
│   │   ├── tests/
│   │   │    └── Dockerfile
├── go.mod
├── go.sum
└── README.md

Testing

Run integration tests to ensure all components function as expected.

Note: running all tests in VS code works.

# Run all integration tests (no parallel)
go test -v ./tests/integration/... -p 1


# Run specific test

go test ./tests/integration -run TestHealthChecks

Design & Limitations

Design Decisions

  • Go was selected for its simplicity, high performance, and excellent concurrency support, making it ideal for building scalable network services like reverse proxies.
  • Avoided third-party packages and the net/http/httputil package to adhere to the assignment constraints and ensure a deeper understanding of proxy mechanics.
  • Implemented Round Robin for its simplicity and even distribution of requests across backends.
  • Structured to allow easy integration of alternative algorithms in the future.

Limitations

  • Does not currently support SSL termination or advanced authentication mechanisms.
  • Caching & Compression: Lacks built-in caching and response compression, which could enhance performance.
  • Load Balancing: Currently limited to Round Robin without considering backend load or response times.
  • Error Handling: Basic error handling in place; more granular logging and alerting could be beneficial.

Scaling

To handle increased traffic and ensure high availability, the system can be scaled as follows:

Horizontal Scaling:

  • Deploy the load balancer using A records on DNS records using a round-robin setup. However, this would require us to maintain the IP addresses of incoming requests across our proxies to keep track of requests. This would also require us to ensure traffic is only routed to healthy proxies and may introduce cache invalidation headaches. Enhanced Load Balancing:
  • Implement more sophisticated load balancing algorithms (e.g., Least Connections, Weighted Round Robin) to optimize request distribution based on backend performance. Caching Mechanisms:
  • Integrate a caching layer (e.g., Redis or in-memory caches) to store frequently accessed data, reducing load on backend servers. Redundancy & Failover:
  • We can achieve high availability via redundancy for failover by having an additional proxies as well. This can be deployed in another region to further reduce the odds of complete loss of access. However, this may introduce data inconsistency and latency issues.

K8 can be used to manage and auto-scale proxies depending on load.

Security

Enhancing the security posture of the reverse proxy involves several strategies:

  • Input Validation: Implement rigorous validation of incoming requests to prevent common attacks such as SQL injection, Cross-Site Scripting (XSS), and others listed in the OWASP Top 10.
  • TLS/SSL Support: Enable TLS to encrypt data in transit, ensuring secure communication between clients and the proxy. Support SSL termination at the proxy, or opt for end-to-end encryption based on security requirements.
  • Authentication & Authorization: Introduce authentication middleware to enforce API key checks or integrate OAuth/JWT-based authentication.
  • Environment Variable Management: Use environment variables or secure key management services (e.g., Azure Key Vault, AWS Secrets Manager) to manage sensitive information like API keys and certificates securely.
  • Rate Limiting & Throttling: Enhance rate limiting strategies to include dynamic thresholds based on user roles or IP reputation, etc. If more than one instance of a proxy is used in our design, we'd need to maintain the request counts across different instances for rate-limitting as well.
  • Logging & Monitoring: Implement comprehensive logging and monitoring to detect and respond to suspicious activities promptly. Integrate with monitoring tools like Prometheus or ELK Stack for real-time insights.
  • Security: To overcome the shortcomings of the current implementation, leveraging Azure Front Door for CDN + Web Application Firewalls would take care of the caching, and security limitations above.

Resources

About

A Go-based HTTP reverse proxy featuring load balancing, rate limiting, and CORS support with Kubernetes deployments.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published