Skip to content

Commit

Permalink
implement proxy api (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
pablomendezroyo authored Nov 26, 2024
1 parent cd7f34c commit cd4c69d
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ WORKDIR /app
COPY --from=builder /app/main .

# Expose the application port
EXPOSE 8080
EXPOSE 8080 8081

# Command to run the binary
ENTRYPOINT ["./main"]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
- `GET /api/v0/event_indexer/operator_performance`: Retrieves the operator performance.
- query params:
- `operatorId`: Operator ID to get performance data.
- Proxy API: a proxy API that redirect requests to the [Lico API keys](https://github.com/lidofinance/lido-keys-api). Its main functionality is to avoid the cors issues when the Lido CSM UI tries to fetch the API.

## Environment Variables

Expand All @@ -55,6 +56,7 @@ To configure the app, set the following environment variables:
|------------------|------------------------------------------------------------------------------------------------------|
| `NETWORK` | Ethereum network (e.g., `mainnet`, `holesky`). Default holesky |
| `API_PORT` | Port on which the API will be exposed. Default 8080 |
| `PROXY_API_PORT` | Proxy port on which the Proxy API will be exposed. Default 8081 |
| `BEACONCHAIN_URL`| URL of the Ethereum beacon chain client. Default http://beacon-chain.<network>,dncore.dappnode:3500 |
| `WS_URL` | URL of the Ethereum WebSocket client. Default ws://execution.<network>.dncore.dappnode:8546 |
| `RPC_URL` | URL of the Ethereum RPC client. Default http://execution.<network>.dncore.dappnode:8545 |
Expand Down
25 changes: 24 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
exitvalidator "lido-events/internal/adapters/exitValidator"
"lido-events/internal/adapters/ipfs"
"lido-events/internal/adapters/notifier"
proxyapi "lido-events/internal/adapters/proxyApi"
"lido-events/internal/adapters/storage"
"lido-events/internal/adapters/vebo"
"lido-events/internal/logger"
Expand Down Expand Up @@ -67,9 +68,9 @@ func main() {

// Initialize adapters
storageAdapter := storage.NewStorageAdapter()
apiAdapter := api.NewAPIAdapter(storageAdapter, networkConfig.CORS)

// Start HTTP server
apiAdapter := api.NewAPIAdapter(storageAdapter, networkConfig.CORS)
server := &http.Server{
Addr: ":" + strconv.FormatUint(networkConfig.ApiPort, 10),
Handler: apiAdapter.Router,
Expand All @@ -83,6 +84,21 @@ func main() {
}
}()

// Start Proxy API server
proxyApiAdapter := proxyapi.NewProxyAPIAdapter(networkConfig.CORS, networkConfig.LidoKeysApiUrl)
proxyServer := &http.Server{
Addr: ":" + strconv.FormatUint(networkConfig.ProxyApiPort, 10),
Handler: proxyApiAdapter.Router,
}
wg.Add(1)
go func() {
defer wg.Done()
logger.Info("Proxy API server started on :%d", networkConfig.ProxyApiPort)
if err := proxyServer.ListenAndServe(); err != http.ErrServerClosed {
logger.Fatal("Proxy API server ListenAndServe: %v", err)
}
}()

// Wait for initial configuration to be set
waitForInitialConfig(storageAdapter)

Expand Down Expand Up @@ -143,6 +159,13 @@ func main() {
if err := server.Shutdown(serverCtx); err != nil {
logger.Info("HTTP server Shutdown: %v", err)
}

// Give the Proxy API server time to finish ongoing requests
proxyServerCtx, proxyServerCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer proxyServerCancel()
if err := proxyServer.Shutdown(proxyServerCtx); err != nil {
logger.Info("Proxy API server Shutdown: %v", err)
}
}()

// Wait for all goroutines to finish
Expand Down
104 changes: 104 additions & 0 deletions internal/adapters/proxyApi/proxy_api_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package proxyapi

import (
"bytes"
"io"
"net/http"
"net/url"

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)

// APIHandler holds the necessary dependencies for API endpoints
type APIHandler struct {
Router *mux.Router
proxyApiURL string
adapterPrefix string
}

// NewProxyAPIAdapter initializes the APIHandler and sets up routes
func NewProxyAPIAdapter(allowedOrigins []string, proxyApiURL string) *APIHandler {
h := &APIHandler{
mux.NewRouter(),
proxyApiURL,
"PROXY-API",
}

h.SetupRoutes()

// Configure CORS middleware with restricted headers
corsAllowedMethods := handlers.AllowedMethods([]string{"GET", "POST", "OPTIONS"})
corsAllowedHeaders := handlers.AllowedHeaders([]string{"Content-Type", "Authorization"})

h.Router.Use(func(next http.Handler) http.Handler {
return handlers.CORS(corsAllowedMethods, corsAllowedHeaders)(next)
})

return h
}

// SetupRoutes sets up all the routes for the Proxy API
func (h *APIHandler) SetupRoutes() {
h.Router.PathPrefix("/v1/").HandlerFunc(h.proxyHandler)
}

// proxyHandler handles all requests and proxies them to the target API
func (h *APIHandler) proxyHandler(w http.ResponseWriter, r *http.Request) {
// Construct the target URL
targetURL, err := url.Parse(h.proxyApiURL)
if err != nil {
http.Error(w, "Invalid proxy URL", http.StatusInternalServerError)
return
}

// Append the incoming request's path to the target URL
targetURL.Path = r.URL.Path
targetURL.RawQuery = r.URL.RawQuery

// Create the proxy request
proxyReq, err := http.NewRequest(r.Method, targetURL.String(), r.Body)
if err != nil {
http.Error(w, "Failed to create proxy request", http.StatusInternalServerError)
return
}

// Copy headers from the original request, excluding the Origin header
for key, values := range r.Header {
if key == "Origin" {
continue
}
for _, value := range values {
proxyReq.Header.Add(key, value)
}
}

// Execute the proxy request
client := &http.Client{}
resp, err := client.Do(proxyReq)
if err != nil {
http.Error(w, "Failed to execute proxy request", http.StatusInternalServerError)
return
}
defer resp.Body.Close()

// Copy response headers, excluding CORS-related headers
for key, values := range resp.Header {
if key == "Access-Control-Allow-Origin" || key == "Origin" {
continue
}
for _, value := range values {
w.Header().Add(key, value)
}
}
w.WriteHeader(resp.StatusCode)

// Copy response body
body, err := io.ReadAll(resp.Body)
if err != nil {
http.Error(w, "Failed to read response body", http.StatusInternalServerError)
return
}

_, _ = io.Copy(w, bytes.NewReader(body))
}
19 changes: 19 additions & 0 deletions internal/config/config_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ type Config struct {
// Block number of the deployment of the VEBO contract and the CSFeeDistributor contract
VeboBlockDeployment uint64
CsFeeDistributorBlockDeployment uint64

// Lido specifics
LidoKeysApiUrl string
ProxyApiPort uint64
}

// Helper function to parse and validate CORS from environment variable
Expand Down Expand Up @@ -67,6 +71,17 @@ func LoadNetworkConfig() (Config, error) {
}
}

proxyApiPortStr := os.Getenv("PROXY_API_PORT")
proxyApiPort := uint64(8081)
if proxyApiPortStr != "" {
// Try to parse the port as uint64
if port, err := strconv.ParseUint(proxyApiPortStr, 10, 64); err == nil {
proxyApiPort = port
} else {
logger.Fatal("Invalid PROXY_API_PORT value: %s", proxyApiPortStr)
}
}

network := os.Getenv("NETWORK")
// Default to holesky
if network == "" {
Expand Down Expand Up @@ -121,6 +136,8 @@ func LoadNetworkConfig() (Config, error) {
VeboBlockDeployment: uint64(30701),
CsFeeDistributorBlockDeployment: uint64(1774650),
CSModuleAddress: common.HexToAddress("0x4562c3e63c2e586cD1651B958C22F88135aCAd4f"),
LidoKeysApiUrl: "https://keys-api-holesky.testnet.fi",
ProxyApiPort: proxyApiPort,
}
case "mainnet":
// Configure default values for the mainnet
Expand Down Expand Up @@ -151,6 +168,8 @@ func LoadNetworkConfig() (Config, error) {
VeboBlockDeployment: uint64(17172556),
CsFeeDistributorBlockDeployment: uint64(20935463),
CSModuleAddress: common.HexToAddress("0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F"),
LidoKeysApiUrl: "https://keys-api.lido.fi",
ProxyApiPort: proxyApiPort,
}
default:
logger.Fatal("Unknown network: %s", network)
Expand Down

0 comments on commit cd4c69d

Please sign in to comment.