-
Notifications
You must be signed in to change notification settings - Fork 195
Create sandbox event endpoint and handlers #784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
d8dc71f
d7363e9
587f92e
61c8fe9
67abc7c
b39dd56
4aefc4a
78bfc5a
599531e
6f1aa77
c47a330
087c421
19a18a7
d8ca2e6
5aa2b58
59ea4ad
be5c2ad
eb5ca83
3179789
30e2cd8
881fbb0
89eb6fb
fdc5cdb
333bb17
e2e6e2c
6abcc04
fd4e9a1
b4bb3f6
7535b6e
d25a9f3
60de6a2
4309142
78ffeba
0b69432
6d666e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package event | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "io" | ||
| "net/http" | ||
|
|
||
| "github.com/redis/go-redis/v9" | ||
| ) | ||
|
|
||
| type EventHandler interface { | ||
| Path() string | ||
| HandlerFunc(w http.ResponseWriter, r *http.Request) | ||
| } | ||
|
|
||
| type EventData struct { | ||
| Path string `json:"path"` | ||
| Body map[string]any `json:"body"` | ||
| } | ||
|
|
||
| type MetricsHandler struct{} | ||
|
|
||
| func (h *MetricsHandler) Path() string { | ||
| return "/metrics" | ||
| } | ||
|
|
||
| func (h *MetricsHandler) HandlerFunc(w http.ResponseWriter, r *http.Request) { | ||
| if r.Method != http.MethodPost { | ||
| http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||
| return | ||
| } | ||
| w.Header().Set("Content-Type", "application/json") | ||
| w.WriteHeader(http.StatusCreated) | ||
| _, err := w.Write([]byte(`{"event_ack":true,"path":"/metrics"}`)) | ||
| if err != nil { | ||
| http.Error(w, "Failed to write response", http.StatusInternalServerError) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| // This handler is used to store event data for all paths that are not registered in the event server. | ||
| // This is used to track ad-hoc events that are not handled by the event server. | ||
| type DefaultHandler struct { | ||
| redisClient redis.UniversalClient | ||
| } | ||
|
|
||
| func (h *DefaultHandler) Path() string { | ||
| return "/" | ||
| } | ||
|
|
||
| func (h *DefaultHandler) HandlerFunc(w http.ResponseWriter, r *http.Request) { | ||
| sandboxID := r.Header.Get("E2B_SANDBOX_ID") | ||
|
|
||
| if r.Method == http.MethodGet { | ||
| body, err := h.redisClient.Get(r.Context(), sandboxID).Result() | ||
| if err != nil { | ||
| http.Error(w, "Failed to get event data for sandbox "+sandboxID, http.StatusInternalServerError) | ||
| return | ||
| } | ||
| w.WriteHeader(http.StatusOK) | ||
| w.Write([]byte(body)) | ||
| return | ||
| } | ||
|
|
||
| if r.Method != http.MethodPost { | ||
| http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||
| return | ||
| } | ||
|
|
||
| // Create event data with path and body | ||
| eventData := EventData{ | ||
| Path: r.URL.Path, | ||
| } | ||
|
|
||
| body, err := io.ReadAll(r.Body) | ||
| if err != nil { | ||
| http.Error(w, "Failed to read request body", http.StatusInternalServerError) | ||
| return | ||
| } | ||
|
|
||
| eventData.Body = make(map[string]any) | ||
| err = json.Unmarshal(body, &eventData.Body) | ||
| if err != nil { | ||
| http.Error(w, "Failed to unmarshal request body", http.StatusInternalServerError) | ||
| return | ||
| } | ||
|
|
||
| // Store in Redis with sandboxID as key | ||
| err = h.redisClient.Set(r.Context(), sandboxID, eventData, 0).Err() | ||
| if err != nil { | ||
| http.Error(w, "Failed to store event data", http.StatusInternalServerError) | ||
| return | ||
| } | ||
|
|
||
| w.WriteHeader(http.StatusCreated) | ||
| w.Write([]byte(`{"event_ack":true}`)) | ||
| } | ||
|
|
||
| func NewEventHandlers(redisClient redis.UniversalClient) []EventHandler { | ||
| return []EventHandler{ | ||
| &MetricsHandler{}, | ||
| &DefaultHandler{redisClient}, | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package event | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "net/http" | ||
|
|
||
| "go.uber.org/zap" | ||
| ) | ||
|
|
||
| // EventServer handles outbound HTTP requests from sandboxes calling the event.e2b.com endpoint | ||
| type EventServer struct { | ||
| server *http.Server | ||
| } | ||
|
|
||
| func validateHeaders(next http.HandlerFunc) http.HandlerFunc { | ||
| return func(w http.ResponseWriter, r *http.Request) { | ||
| sandboxID := r.Header.Get("E2B_SANDBOX_ID") | ||
| teamID := r.Header.Get("E2B_TEAM_ID") | ||
0div marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if sandboxID == "" || teamID == "" { | ||
| http.Error(w, "missing required headers", http.StatusBadRequest) | ||
| return | ||
| } | ||
|
|
||
| next.ServeHTTP(w, r) | ||
| } | ||
| } | ||
|
|
||
| func NewEventServer(port uint, handlers []EventHandler) *EventServer { | ||
| mux := http.NewServeMux() | ||
|
|
||
| for _, handler := range handlers { | ||
| mux.HandleFunc(handler.Path(), validateHeaders(handler.HandlerFunc)) | ||
| } | ||
|
|
||
| server := &http.Server{ | ||
| Addr: fmt.Sprintf(":%d", port), | ||
| Handler: mux, | ||
| } | ||
|
Comment on lines
+21
to
+24
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about running Gin here so we can use a strict Golang client in envd and track all version changes?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure, we can use gin, but i'm not sure it's more strict than the std lib and also what do u mean by tracking version changes in this case?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was thinking that the "metrics server" can be Gin-backed by the OpenAPI scheme, as we are using for the API, so all breaking changes will be clear (because of the re-generated schema). You will also be able to use the generated Go client from EnvD to call metrics service endpoints.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point was just make it strong types on both caller and receiver
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with @sitole |
||
|
|
||
| return &EventServer{ | ||
| server: server, | ||
| } | ||
| } | ||
|
|
||
| func (p *EventServer) Start() error { | ||
| zap.L().Info("Starting event server") | ||
| return p.server.ListenAndServe() | ||
| } | ||
|
|
||
| func (p *EventServer) Close(ctx context.Context) error { | ||
| var err error | ||
| select { | ||
| case <-ctx.Done(): | ||
| err = p.server.Close() | ||
| default: | ||
| err = p.server.Shutdown(ctx) | ||
| } | ||
| if err != nil { | ||
| return fmt.Errorf("failed to shutdown event server: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,6 +217,12 @@ func (s *Slot) CreateNetwork() error { | |
| return fmt.Errorf("error creating postrouting rule: %w", err) | ||
| } | ||
|
|
||
| // Redirect http://event.e2b.dev traffic destined to event server | ||
| err = tables.Append("nat", "PREROUTING", "-i", s.VethName(), "-p", "tcp", "-d", "8.8.8.7", "--dport", "80", "-j", "REDIRECT", "--to-port", "5010") | ||
0div marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if err != nil { | ||
| return fmt.Errorf("error creating HTTP redirect rule to sandbox event server: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.