Skip to content

Commit 2964a58

Browse files
refactor: avoid code duplication in http handlers
1 parent 007703a commit 2964a58

File tree

4 files changed

+155
-140
lines changed

4 files changed

+155
-140
lines changed

coderd/coderd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1261,7 +1261,7 @@ func New(options *Options) *API {
12611261
r.Route("/ttl", func(r chi.Router) {
12621262
r.Put("/", api.putWorkspaceTTL)
12631263
})
1264-
r.Get("/watch", api.watchWorkspace)
1264+
r.Get("/watch", api.watchWorkspaceSSE)
12651265
r.Get("/watch-ws", api.watchWorkspaceWs)
12661266
r.Put("/extend", api.putExtendWorkspace)
12671267
r.Post("/usage", api.postWorkspaceUsage)

coderd/httpapi/httpapi.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,20 @@ func WebsocketCloseSprintf(format string, vars ...any) string {
282282
return msg
283283
}
284284

285+
type SendEventCallback func(ctx context.Context, sse codersdk.ServerSentEvent) error
286+
type InitConnCallback func(rw http.ResponseWriter, r *http.Request) (
287+
SendEventCallback,
288+
chan struct{},
289+
error,
290+
)
291+
285292
// ServerSentEventSender establishes a Server-Sent Event connection and allows
286293
// the consumer to send messages to the client.
287294
//
288295
// As much as possible, this function should be avoided in favor of using the
289296
// OneWayWebSocket function. See OneWayWebSocket for more context.
290297
func ServerSentEventSender(rw http.ResponseWriter, r *http.Request) (
291-
sendEvent func(ctx context.Context, sse codersdk.ServerSentEvent) error,
298+
sendEvent SendEventCallback,
292299
closed chan struct{},
293300
err error,
294301
) {
@@ -406,8 +413,8 @@ func ServerSentEventSender(rw http.ResponseWriter, r *http.Request) (
406413
// open a workspace in multiple tabs, the entire UI can start to lock up.
407414
// WebSockets have no such limitation, no matter what HTTP protocol was used to
408415
// establish the connection.
409-
func OneWayWebSocket[JsonSerializable any](rw http.ResponseWriter, r *http.Request) (
410-
sendEvent func(event JsonSerializable) error,
416+
func OneWayWebSocket(rw http.ResponseWriter, r *http.Request) (
417+
sendEvent SendEventCallback,
411418
closed chan struct{},
412419
err error,
413420
) {
@@ -424,7 +431,7 @@ func OneWayWebSocket[JsonSerializable any](rw http.ResponseWriter, r *http.Reque
424431
Code websocket.StatusCode
425432
Reason string
426433
}
427-
eventC := make(chan JsonSerializable)
434+
eventC := make(chan codersdk.ServerSentEvent)
428435
socketErrC := make(chan SocketError, 1)
429436
closed = make(chan struct{})
430437
go func() {
@@ -471,7 +478,7 @@ func OneWayWebSocket[JsonSerializable any](rw http.ResponseWriter, r *http.Reque
471478
}
472479
}()
473480

474-
sendEvent = func(event JsonSerializable) error {
481+
sendEvent = func(_ context.Context, event codersdk.ServerSentEvent) error {
475482
select {
476483
case eventC <- event:
477484
case <-ctx.Done():

coderd/workspaceagents.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,7 +1347,7 @@ func (api *API) watchWorkspaceAgentMetadataWs(rw http.ResponseWriter, r *http.Re
13471347
//nolint:ineffassign // Release memory.
13481348
initialMD = nil
13491349

1350-
send, closed, err := httpapi.OneWayWebSocket[codersdk.ServerSentEvent](rw, r)
1350+
send, closed, err := httpapi.OneWayWebSocket(rw, r)
13511351
if err != nil {
13521352
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
13531353
Message: "Internal error setting up server-sent events.",
@@ -1377,7 +1377,7 @@ func (api *API) watchWorkspaceAgentMetadataWs(rw http.ResponseWriter, r *http.Re
13771377

13781378
log.Debug(ctx, "sending metadata", "num", len(values))
13791379

1380-
_ = send(codersdk.ServerSentEvent{
1380+
_ = send(ctx, codersdk.ServerSentEvent{
13811381
Type: codersdk.ServerSentEventTypeData,
13821382
Data: convertWorkspaceAgentMetadata(values),
13831383
})
@@ -1409,7 +1409,7 @@ func (api *API) watchWorkspaceAgentMetadataWs(rw http.ResponseWriter, r *http.Re
14091409
if err != nil {
14101410
if !database.IsQueryCanceledError(err) {
14111411
log.Error(ctx, "failed to get metadata", slog.Error(err))
1412-
_ = send(codersdk.ServerSentEvent{
1412+
_ = send(ctx, codersdk.ServerSentEvent{
14131413
Type: codersdk.ServerSentEventTypeError,
14141414
Data: codersdk.Response{
14151415
Message: "Failed to get metadata.",

coderd/workspaces.go

Lines changed: 139 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,12 +1719,20 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) {
17191719
// @Success 200 {object} codersdk.Response
17201720
// @Router /workspaces/{workspace}/watch [get]
17211721
// @Deprecated Use /workspaces/{workspace}/watch-ws instead
1722-
func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
1722+
func (api *API) watchWorkspaceSSE(rw http.ResponseWriter, r *http.Request) {
1723+
api.watchWorkspace(rw, r, httpapi.ServerSentEventSender)
1724+
}
1725+
1726+
func (api *API) watchWorkspaceWs(rw http.ResponseWriter, r *http.Request) {
1727+
api.watchWorkspace(rw, r, httpapi.OneWayWebSocket)
1728+
}
1729+
1730+
func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request, initConn httpapi.InitConnCallback) {
17231731
ctx := r.Context()
17241732
workspace := httpmw.WorkspaceParam(r)
17251733
apiKey := httpmw.APIKey(r)
17261734

1727-
sendEvent, senderClosed, err := httpapi.ServerSentEventSender(rw, r)
1735+
sendEvent, senderClosed, err := initConn(rw, r)
17281736
if err != nil {
17291737
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
17301738
Message: "Internal error setting up server-sent events.",
@@ -1857,135 +1865,135 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
18571865
// @Param workspace path string true "Workspace ID" format(uuid)
18581866
// @Success 200 {object} codersdk.Response
18591867
// @Router /workspaces/{workspace}/watch [get]
1860-
func (api *API) watchWorkspaceWs(rw http.ResponseWriter, r *http.Request) {
1861-
ctx := r.Context()
1862-
workspace := httpmw.WorkspaceParam(r)
1863-
apiKey := httpmw.APIKey(r)
1864-
1865-
send, closed, err := httpapi.OneWayWebSocket[codersdk.ServerSentEvent](rw, r)
1866-
if err != nil {
1867-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
1868-
Message: "Internal error setting up server-sent events.",
1869-
Detail: err.Error(),
1870-
})
1871-
return
1872-
}
1873-
// Prevent handler from returning until the sender is closed.
1874-
defer func() {
1875-
<-closed
1876-
}()
1877-
1878-
sendUpdate := func(_ context.Context, _ []byte) {
1879-
workspace, err := api.Database.GetWorkspaceByID(ctx, workspace.ID)
1880-
if err != nil {
1881-
_ = send(codersdk.ServerSentEvent{
1882-
Type: codersdk.ServerSentEventTypeError,
1883-
Data: codersdk.Response{
1884-
Message: "Internal error fetching workspace.",
1885-
Detail: err.Error(),
1886-
},
1887-
})
1888-
return
1889-
}
1890-
1891-
data, err := api.workspaceData(ctx, []database.Workspace{workspace})
1892-
if err != nil {
1893-
_ = send(codersdk.ServerSentEvent{
1894-
Type: codersdk.ServerSentEventTypeError,
1895-
Data: codersdk.Response{
1896-
Message: "Internal error fetching workspace data.",
1897-
Detail: err.Error(),
1898-
},
1899-
})
1900-
return
1901-
}
1902-
if len(data.templates) == 0 {
1903-
_ = send(codersdk.ServerSentEvent{
1904-
Type: codersdk.ServerSentEventTypeError,
1905-
Data: codersdk.Response{
1906-
Message: "Forbidden reading template of selected workspace.",
1907-
},
1908-
})
1909-
return
1910-
}
1911-
1912-
w, err := convertWorkspace(
1913-
apiKey.UserID,
1914-
workspace,
1915-
data.builds[0],
1916-
data.templates[0],
1917-
api.Options.AllowWorkspaceRenames,
1918-
)
1919-
if err != nil {
1920-
_ = send(codersdk.ServerSentEvent{
1921-
Type: codersdk.ServerSentEventTypeError,
1922-
Data: codersdk.Response{
1923-
Message: "Internal error converting workspace.",
1924-
Detail: err.Error(),
1925-
},
1926-
})
1927-
}
1928-
_ = send(codersdk.ServerSentEvent{
1929-
Type: codersdk.ServerSentEventTypeData,
1930-
Data: w,
1931-
})
1932-
}
1933-
1934-
cancelWorkspaceSubscribe, err := api.Pubsub.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
1935-
wspubsub.HandleWorkspaceEvent(
1936-
func(ctx context.Context, payload wspubsub.WorkspaceEvent, err error) {
1937-
if err != nil {
1938-
return
1939-
}
1940-
if payload.WorkspaceID != workspace.ID {
1941-
return
1942-
}
1943-
sendUpdate(ctx, nil)
1944-
}))
1945-
if err != nil {
1946-
_ = send(codersdk.ServerSentEvent{
1947-
Type: codersdk.ServerSentEventTypeError,
1948-
Data: codersdk.Response{
1949-
Message: "Internal error subscribing to workspace events.",
1950-
Detail: err.Error(),
1951-
},
1952-
})
1953-
return
1954-
}
1955-
defer cancelWorkspaceSubscribe()
1956-
1957-
// This is required to show whether the workspace is up-to-date.
1958-
cancelTemplateSubscribe, err := api.Pubsub.Subscribe(watchTemplateChannel(workspace.TemplateID), sendUpdate)
1959-
if err != nil {
1960-
_ = send(codersdk.ServerSentEvent{
1961-
Type: codersdk.ServerSentEventTypeError,
1962-
Data: codersdk.Response{
1963-
Message: "Internal error subscribing to template events.",
1964-
Detail: err.Error(),
1965-
},
1966-
})
1967-
return
1968-
}
1969-
defer cancelTemplateSubscribe()
1970-
1971-
// An initial ping signals to the request that the server is now ready
1972-
// and the client can begin servicing a channel with data.
1973-
_ = send(codersdk.ServerSentEvent{
1974-
Type: codersdk.ServerSentEventTypePing,
1975-
})
1976-
// Send updated workspace info after connection is established. This avoids
1977-
// missing updates if the client connects after an update.
1978-
sendUpdate(ctx, nil)
1979-
1980-
for {
1981-
select {
1982-
case <-ctx.Done():
1983-
return
1984-
case <-closed:
1985-
return
1986-
}
1987-
}
1988-
}
1868+
//func (api *API) watchWorkspaceWs(rw http.ResponseWriter, r *http.Request) {
1869+
// ctx := r.Context()
1870+
// workspace := httpmw.WorkspaceParam(r)
1871+
// apiKey := httpmw.APIKey(r)
1872+
//
1873+
// send, closed, err := httpapi.OneWayWebSocket[codersdk.ServerSentEvent](rw, r)
1874+
// if err != nil {
1875+
// httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
1876+
// Message: "Internal error setting up server-sent events.",
1877+
// Detail: err.Error(),
1878+
// })
1879+
// return
1880+
// }
1881+
// // Prevent handler from returning until the sender is closed.
1882+
// defer func() {
1883+
// <-closed
1884+
// }()
1885+
//
1886+
// sendUpdate := func(_ context.Context, _ []byte) {
1887+
// workspace, err := api.Database.GetWorkspaceByID(ctx, workspace.ID)
1888+
// if err != nil {
1889+
// _ = send(codersdk.ServerSentEvent{
1890+
// Type: codersdk.ServerSentEventTypeError,
1891+
// Data: codersdk.Response{
1892+
// Message: "Internal error fetching workspace.",
1893+
// Detail: err.Error(),
1894+
// },
1895+
// })
1896+
// return
1897+
// }
1898+
//
1899+
// data, err := api.workspaceData(ctx, []database.Workspace{workspace})
1900+
// if err != nil {
1901+
// _ = send(codersdk.ServerSentEvent{
1902+
// Type: codersdk.ServerSentEventTypeError,
1903+
// Data: codersdk.Response{
1904+
// Message: "Internal error fetching workspace data.",
1905+
// Detail: err.Error(),
1906+
// },
1907+
// })
1908+
// return
1909+
// }
1910+
// if len(data.templates) == 0 {
1911+
// _ = send(codersdk.ServerSentEvent{
1912+
// Type: codersdk.ServerSentEventTypeError,
1913+
// Data: codersdk.Response{
1914+
// Message: "Forbidden reading template of selected workspace.",
1915+
// },
1916+
// })
1917+
// return
1918+
// }
1919+
//
1920+
// w, err := convertWorkspace(
1921+
// apiKey.UserID,
1922+
// workspace,
1923+
// data.builds[0],
1924+
// data.templates[0],
1925+
// api.Options.AllowWorkspaceRenames,
1926+
// )
1927+
// if err != nil {
1928+
// _ = send(codersdk.ServerSentEvent{
1929+
// Type: codersdk.ServerSentEventTypeError,
1930+
// Data: codersdk.Response{
1931+
// Message: "Internal error converting workspace.",
1932+
// Detail: err.Error(),
1933+
// },
1934+
// })
1935+
// }
1936+
// _ = send(codersdk.ServerSentEvent{
1937+
// Type: codersdk.ServerSentEventTypeData,
1938+
// Data: w,
1939+
// })
1940+
// }
1941+
//
1942+
// cancelWorkspaceSubscribe, err := api.Pubsub.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
1943+
// wspubsub.HandleWorkspaceEvent(
1944+
// func(ctx context.Context, payload wspubsub.WorkspaceEvent, err error) {
1945+
// if err != nil {
1946+
// return
1947+
// }
1948+
// if payload.WorkspaceID != workspace.ID {
1949+
// return
1950+
// }
1951+
// sendUpdate(ctx, nil)
1952+
// }))
1953+
// if err != nil {
1954+
// _ = send(codersdk.ServerSentEvent{
1955+
// Type: codersdk.ServerSentEventTypeError,
1956+
// Data: codersdk.Response{
1957+
// Message: "Internal error subscribing to workspace events.",
1958+
// Detail: err.Error(),
1959+
// },
1960+
// })
1961+
// return
1962+
// }
1963+
// defer cancelWorkspaceSubscribe()
1964+
//
1965+
// // This is required to show whether the workspace is up-to-date.
1966+
// cancelTemplateSubscribe, err := api.Pubsub.Subscribe(watchTemplateChannel(workspace.TemplateID), sendUpdate)
1967+
// if err != nil {
1968+
// _ = send(codersdk.ServerSentEvent{
1969+
// Type: codersdk.ServerSentEventTypeError,
1970+
// Data: codersdk.Response{
1971+
// Message: "Internal error subscribing to template events.",
1972+
// Detail: err.Error(),
1973+
// },
1974+
// })
1975+
// return
1976+
// }
1977+
// defer cancelTemplateSubscribe()
1978+
//
1979+
// // An initial ping signals to the request that the server is now ready
1980+
// // and the client can begin servicing a channel with data.
1981+
// _ = send(codersdk.ServerSentEvent{
1982+
// Type: codersdk.ServerSentEventTypePing,
1983+
// })
1984+
// // Send updated workspace info after connection is established. This avoids
1985+
// // missing updates if the client connects after an update.
1986+
// sendUpdate(ctx, nil)
1987+
//
1988+
// for {
1989+
// select {
1990+
// case <-ctx.Done():
1991+
// return
1992+
// case <-closed:
1993+
// return
1994+
// }
1995+
// }
1996+
//}
19891997

19901998
// @Summary Get workspace timings by ID
19911999
// @ID get-workspace-timings-by-id

0 commit comments

Comments
 (0)