e0e362d700
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
// Copyright 2025 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package web
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/alibaba/opensandbox/execd/pkg/log"
|
|
)
|
|
|
|
func ProxyMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if !strings.HasPrefix(c.Request.URL.Path, "/proxy/") {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
r := c.Request
|
|
w := c.Writer
|
|
|
|
rest := strings.TrimPrefix(r.URL.Path, "/proxy/")
|
|
parts := strings.SplitN(rest, "/", 2)
|
|
if len(parts) == 0 || parts[0] == "" {
|
|
http.Error(w, "port is required", http.StatusBadRequest)
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
port := parts[0]
|
|
path := "/"
|
|
if len(parts) == 2 && parts[1] != "" {
|
|
path += parts[1]
|
|
}
|
|
|
|
target := &url.URL{
|
|
Scheme: "http",
|
|
Host: "127.0.0.1:" + port,
|
|
Path: path,
|
|
}
|
|
|
|
isWebSocket := strings.ToLower(r.Header.Get("Upgrade")) == "websocket"
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
|
// Flush SSE chunks promptly; a small interval avoids buffering breaks chunked streams.
|
|
proxy.FlushInterval = 200 * time.Millisecond
|
|
|
|
proxy.Director = func(req *http.Request) {
|
|
req.URL.Scheme = "http"
|
|
req.URL.Host = "127.0.0.1:" + port
|
|
req.URL.Path = path
|
|
req.URL.RawQuery = r.URL.RawQuery
|
|
req.URL.RawPath = ""
|
|
req.RequestURI = ""
|
|
|
|
req.Header.Set("X-Forwarded-For", getClientIP(r))
|
|
req.Header.Set("X-Forwarded-Proto", "http")
|
|
req.Header.Del("X-Forwarded-Host")
|
|
|
|
if isWebSocket {
|
|
req.Header.Set("Connection", "Upgrade")
|
|
req.Header.Set("Upgrade", "websocket")
|
|
req.Header.Set("Sec-WebSocket-Version", "13")
|
|
if key := r.Header.Get("Sec-WebSocket-Key"); key != "" {
|
|
req.Header.Set("Sec-WebSocket-Key", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
proxy.Transport = &http.Transport{
|
|
DialContext: (&net.Dialer{
|
|
Timeout: 600 * time.Second,
|
|
KeepAlive: 30 * time.Second,
|
|
}).DialContext,
|
|
MaxIdleConns: 100,
|
|
MaxIdleConnsPerHost: 100,
|
|
IdleConnTimeout: 600 * time.Second,
|
|
}
|
|
|
|
proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
|
|
log.Error("Proxy error: %v, request: %s %s", err, req.Method, req.RequestURI)
|
|
http.Error(rw, "Bad Gateway", http.StatusBadGateway)
|
|
}
|
|
|
|
log.Info("Proxy: %s %s -> %s (WebSocket: %v)", r.Method, r.RequestURI, target.Host, isWebSocket)
|
|
|
|
proxy.ServeHTTP(w, r)
|
|
c.Abort()
|
|
}
|
|
}
|
|
|
|
func getClientIP(r *http.Request) string {
|
|
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
|
|
return strings.Split(ip, ",")[0]
|
|
}
|
|
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
|
return ip
|
|
}
|
|
return r.RemoteAddr
|
|
}
|