Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

176 lines
8.2 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/http"
"github.com/gin-gonic/gin"
"github.com/alibaba/opensandbox/execd/pkg/log"
"github.com/alibaba/opensandbox/execd/pkg/web/controller"
"github.com/alibaba/opensandbox/execd/pkg/web/model"
)
// NewRouter builds a Gin engine with all execd routes.
func NewRouter(accessToken string) *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(logMiddleware(), otelHTTPMetricsMiddleware(), accessTokenMiddleware(accessToken), ProxyMiddleware())
r.GET("/ping", controller.PingHandler)
files := r.Group("/files")
{
files.DELETE("", withFilesystem(func(c *controller.FilesystemController) { c.RemoveFiles() }))
files.GET("/info", withFilesystem(func(c *controller.FilesystemController) { c.GetFilesInfo() }))
files.POST("/mv", withFilesystem(func(c *controller.FilesystemController) { c.RenameFiles() }))
files.POST("/permissions", withFilesystem(func(c *controller.FilesystemController) { c.ChmodFiles() }))
files.GET("/search", withFilesystem(func(c *controller.FilesystemController) { c.SearchFiles() }))
files.POST("/replace", withFilesystem(func(c *controller.FilesystemController) { c.ReplaceContent() }))
files.POST("/upload", withFilesystem(func(c *controller.FilesystemController) { c.UploadFile() }))
files.GET("/download", withFilesystem(func(c *controller.FilesystemController) { c.DownloadFile() }))
}
directories := r.Group("/directories")
{
directories.GET("/list", withFilesystem(func(c *controller.FilesystemController) { c.ListDirectory() }))
directories.POST("", withFilesystem(func(c *controller.FilesystemController) { c.MakeDirs() }))
directories.DELETE("", withFilesystem(func(c *controller.FilesystemController) { c.RemoveDirs() }))
}
code := r.Group("/code")
{
code.POST("", withCode(func(c *controller.CodeInterpretingController) { c.RunCode() }))
code.DELETE("", withCode(func(c *controller.CodeInterpretingController) { c.InterruptCode() }))
code.POST("/context", withCode(func(c *controller.CodeInterpretingController) { c.CreateContext() }))
code.GET("/contexts", withCode(func(c *controller.CodeInterpretingController) { c.ListContexts() }))
code.DELETE("/contexts", withCode(func(c *controller.CodeInterpretingController) { c.DeleteContextsByLanguage() }))
code.DELETE("/contexts/:contextId", withCode(func(c *controller.CodeInterpretingController) { c.DeleteContext() }))
code.GET("/contexts/:contextId", withCode(func(c *controller.CodeInterpretingController) { c.GetContext() }))
}
session := r.Group("/session")
{
session.POST("", withCode(func(c *controller.CodeInterpretingController) { c.CreateSession() }))
session.POST("/:sessionId/run", withCode(func(c *controller.CodeInterpretingController) { c.RunInSession() }))
session.DELETE("/:sessionId", withCode(func(c *controller.CodeInterpretingController) { c.DeleteSession() }))
}
command := r.Group("/command")
{
command.POST("", withCode(func(c *controller.CodeInterpretingController) { c.RunCommand() }))
command.DELETE("", withCode(func(c *controller.CodeInterpretingController) { c.InterruptCommand() }))
command.GET("/status/:id", withCode(func(c *controller.CodeInterpretingController) { c.GetCommandStatus() }))
command.GET("/:id/logs", withCode(func(c *controller.CodeInterpretingController) { c.GetBackgroundCommandOutput() }))
}
metric := r.Group("/metrics")
{
metric.GET("", withMetric(func(c *controller.MetricController) { c.GetMetrics() }))
metric.GET("/watch", withMetric(func(c *controller.MetricController) { c.WatchMetrics() }))
}
pty := r.Group("/pty")
{
pty.POST("", withPTY(func(c *controller.PTYController) { c.CreatePTYSession() }))
pty.GET("/:sessionId", withPTY(func(c *controller.PTYController) { c.GetPTYSessionStatus() }))
pty.DELETE("/:sessionId", withPTY(func(c *controller.PTYController) { c.DeletePTYSession() }))
pty.GET("/:sessionId/ws", controller.PTYSessionWebSocket)
}
isolated := r.Group("/v1/isolated")
{
isolated.POST("/session", withIsolated(func(c *controller.IsolatedSessionController) { c.Create() }))
isolated.GET("/sessions", withIsolated(func(c *controller.IsolatedSessionController) { c.List() }))
isolated.GET("/session/:sessionId", withIsolated(func(c *controller.IsolatedSessionController) { c.Get() }))
isolated.POST("/session/:sessionId/run", withIsolated(func(c *controller.IsolatedSessionController) { c.Run() }))
isolated.DELETE("/session/:sessionId", withIsolated(func(c *controller.IsolatedSessionController) { c.Delete() }))
isolated.GET("/session/:sessionId/diff", withIsolated(func(c *controller.IsolatedSessionController) { c.Diff() }))
isolated.POST("/session/:sessionId/commit", withIsolated(func(c *controller.IsolatedSessionController) { c.Commit() }))
isolated.GET("/session/:sessionId/files/info", withIsolated(func(c *controller.IsolatedSessionController) { c.GetFilesInfo() }))
isolated.GET("/session/:sessionId/files/download", withIsolated(func(c *controller.IsolatedSessionController) { c.DownloadFile() }))
isolated.POST("/session/:sessionId/files/upload", withIsolated(func(c *controller.IsolatedSessionController) { c.UploadFile() }))
isolated.DELETE("/session/:sessionId/files", withIsolated(func(c *controller.IsolatedSessionController) { c.RemoveFiles() }))
isolated.POST("/session/:sessionId/files/mv", withIsolated(func(c *controller.IsolatedSessionController) { c.RenameFiles() }))
isolated.POST("/session/:sessionId/files/permissions", withIsolated(func(c *controller.IsolatedSessionController) { c.ChmodFiles() }))
isolated.POST("/session/:sessionId/files/replace", withIsolated(func(c *controller.IsolatedSessionController) { c.ReplaceContent() }))
isolated.GET("/session/:sessionId/files/search", withIsolated(func(c *controller.IsolatedSessionController) { c.SearchFiles() }))
isolated.GET("/session/:sessionId/directories/list", withIsolated(func(c *controller.IsolatedSessionController) { c.ListDirectory() }))
isolated.POST("/session/:sessionId/directories", withIsolated(func(c *controller.IsolatedSessionController) { c.MakeDirs() }))
isolated.DELETE("/session/:sessionId/directories", withIsolated(func(c *controller.IsolatedSessionController) { c.RemoveDirs() }))
isolated.GET("/capabilities", withIsolated(func(c *controller.IsolatedSessionController) { c.Capabilities() }))
}
return r
}
func withFilesystem(fn func(*controller.FilesystemController)) gin.HandlerFunc {
return func(ctx *gin.Context) {
fn(controller.NewFilesystemController(ctx))
}
}
func withCode(fn func(*controller.CodeInterpretingController)) gin.HandlerFunc {
return func(ctx *gin.Context) {
fn(controller.NewCodeInterpretingController(ctx))
}
}
func withMetric(fn func(*controller.MetricController)) gin.HandlerFunc {
return func(ctx *gin.Context) {
fn(controller.NewMetricController(ctx))
}
}
func withPTY(fn func(*controller.PTYController)) gin.HandlerFunc {
return func(ctx *gin.Context) {
fn(controller.NewPTYController(ctx))
}
}
func withIsolated(fn func(*controller.IsolatedSessionController)) gin.HandlerFunc {
return func(ctx *gin.Context) {
fn(controller.NewIsolatedSessionController(ctx))
}
}
func accessTokenMiddleware(token string) gin.HandlerFunc {
return func(ctx *gin.Context) {
if token == "" {
ctx.Next()
return
}
requestedToken := ctx.GetHeader(model.ApiAccessTokenHeader)
if requestedToken == "" || requestedToken != token {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, map[string]any{
"error": "Unauthorized: invalid or missing header " + model.ApiAccessTokenHeader,
})
return
}
ctx.Next()
}
}
func logMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
log.Info("Requested: %v - %v", ctx.Request.Method, ctx.Request.URL.String())
ctx.Next()
}
}