@@ -6,8 +6,6 @@ package app
66import (
77 "context"
88 "fmt"
9- "io"
10- "io/fs"
119 "log"
1210 "net"
1311 "net/http"
@@ -44,7 +42,6 @@ type clientImpl struct {
4442 DoneCh chan struct {}
4543 SSEventCh chan ssEvent
4644 GlobalEventHandler func (event vdom.VDomEvent )
47- GlobalStylesOption * FileHandlerOption
4845 UrlHandlerMux * http.ServeMux
4946 SetupFn func ()
5047}
@@ -134,7 +131,7 @@ func (c *clientImpl) runMainE() error {
134131 return nil
135132}
136133
137- func (c * clientImpl ) AddSetupFn (fn func ()) {
134+ func (c * clientImpl ) RegisterSetupFn (fn func ()) {
138135 c .SetupFn = fn
139136}
140137
@@ -152,14 +149,10 @@ func (c *clientImpl) listenAndServe(ctx context.Context) error {
152149
153150 // Create a new ServeMux and register handlers
154151 mux := http .NewServeMux ()
155- var manifestOption * FileHandlerOption
156- if len (manifestFileBytes ) > 0 {
157- manifestOption = & FileHandlerOption {Data : manifestFileBytes }
158- }
159152 handlers .registerHandlers (mux , handlerOpts {
160153 AssetsFS : assetsFS ,
161154 StaticFS : staticFS ,
162- ManifestFile : manifestOption ,
155+ ManifestFile : manifestFileBytes ,
163156 })
164157
165158 // Determine listen address from environment variable or use default
@@ -304,162 +297,10 @@ func (c *clientImpl) incrementalRender() (*rpctypes.VDomBackendUpdate, error) {
304297 }, nil
305298}
306299
307- func (c * clientImpl ) RegisterUrlPathHandler (path string , handler http.Handler ) {
308- c .UrlHandlerMux .Handle (path , handler )
309- }
310-
311- type FileHandlerOption struct {
312- FilePath string // optional file path on disk
313- Data []byte // optional byte slice content
314- Reader io.Reader // optional reader for content
315- File fs.File // optional embedded or opened file
316- MimeType string // optional mime type
317- ETag string // optional ETag (if set, resource may be cached)
318- }
319-
320- func determineMimeType (option FileHandlerOption ) (string , []byte ) {
321- // If MimeType is set, use it directly
322- if option .MimeType != "" {
323- return option .MimeType , nil
324- }
325-
326- // Detect from Data if available, no need to buffer
327- if option .Data != nil {
328- return http .DetectContentType (option .Data ), nil
329- }
330-
331- // Detect from FilePath, no buffering necessary
332- if option .FilePath != "" {
333- filePath := util .ExpandHomeDirSafe (option .FilePath )
334- file , err := os .Open (filePath )
335- if err != nil {
336- return "application/octet-stream" , nil // Fallback on error
337- }
338- defer file .Close ()
339-
340- // Read first 512 bytes for MIME detection
341- buf := make ([]byte , 512 )
342- _ , err = file .Read (buf )
343- if err != nil && err != io .EOF {
344- return "application/octet-stream" , nil
345- }
346- return http .DetectContentType (buf ), nil
347- }
348-
349- // Buffer for File (fs.File), since it lacks Seek
350- if option .File != nil {
351- buf := make ([]byte , 512 )
352- n , err := option .File .Read (buf )
353- if err != nil && err != io .EOF {
354- return "application/octet-stream" , nil
355- }
356- return http .DetectContentType (buf [:n ]), buf [:n ]
357- }
358-
359- // Buffer for Reader (io.Reader), same as File
360- if option .Reader != nil {
361- buf := make ([]byte , 512 )
362- n , err := option .Reader .Read (buf )
363- if err != nil && err != io .EOF {
364- return "application/octet-stream" , nil
365- }
366- return http .DetectContentType (buf [:n ]), buf [:n ]
367- }
368-
369- // Default MIME type if none specified
370- return "application/octet-stream" , nil
371- }
372-
373- // serveFileOption handles serving content based on the provided FileHandlerOption
374- func serveFileOption (w http.ResponseWriter , r * http.Request , option FileHandlerOption ) error {
375- // Determine MIME type and get buffered data if needed
376- contentType , bufferedData := determineMimeType (option )
377- w .Header ().Set ("Content-Type" , contentType )
378- // Handle ETag
379- if option .ETag != "" {
380- w .Header ().Set ("ETag" , option .ETag )
381-
382- // Check If-None-Match header
383- if inm := r .Header .Get ("If-None-Match" ); inm != "" {
384- // Strip W/ prefix and quotes if present
385- inm = strings .Trim (inm , `"` )
386- inm = strings .TrimPrefix (inm , "W/" )
387- etag := strings .Trim (option .ETag , `"` )
388- etag = strings .TrimPrefix (etag , "W/" )
389-
390- if inm == etag {
391- // Resource not modified
392- w .WriteHeader (http .StatusNotModified )
393- return nil
394- }
395- }
396- }
397-
398- // Handle the content based on the option type
399- switch {
400- case option .FilePath != "" :
401- filePath := util .ExpandHomeDirSafe (option .FilePath )
402- http .ServeFile (w , r , filePath )
403-
404- case option .Data != nil :
405- w .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , len (option .Data )))
406- w .WriteHeader (http .StatusOK )
407- if _ , err := w .Write (option .Data ); err != nil {
408- return fmt .Errorf ("failed to write data: %v" , err )
409- }
410-
411- case option .File != nil :
412- if bufferedData != nil {
413- if _ , err := w .Write (bufferedData ); err != nil {
414- return fmt .Errorf ("failed to write buffered data: %v" , err )
415- }
416- }
417- if _ , err := io .Copy (w , option .File ); err != nil {
418- return fmt .Errorf ("failed to copy from file: %v" , err )
419- }
420-
421- case option .Reader != nil :
422- if bufferedData != nil {
423- if _ , err := w .Write (bufferedData ); err != nil {
424- return fmt .Errorf ("failed to write buffered data: %v" , err )
425- }
426- }
427- if _ , err := io .Copy (w , option .Reader ); err != nil {
428- return fmt .Errorf ("failed to copy from reader: %v" , err )
429- }
430-
431- default :
432- return fmt .Errorf ("no content available" )
300+ func (c * clientImpl ) HandleDynFunc (pattern string , fn func (http.ResponseWriter , * http.Request )) {
301+ if ! strings .HasPrefix (pattern , "/dyn/" ) {
302+ log .Printf ("invalid dyn pattern: %s (must start with /dyn/)" , pattern )
303+ return
433304 }
434-
435- return nil
436- }
437-
438- func (c * clientImpl ) RegisterFilePrefixHandler (prefix string , optionProvider func (path string ) (* FileHandlerOption , error )) {
439- c .UrlHandlerMux .HandleFunc (prefix , func (w http.ResponseWriter , r * http.Request ) {
440- if ! strings .HasPrefix (r .URL .Path , prefix ) {
441- http .NotFound (w , r )
442- return
443- }
444- option , err := optionProvider (r .URL .Path )
445- if err != nil {
446- http .Error (w , err .Error (), http .StatusInternalServerError )
447- return
448- }
449- if option == nil {
450- http .Error (w , "no content available" , http .StatusNotFound )
451- return
452- }
453- if err := serveFileOption (w , r , * option ); err != nil {
454- http .Error (w , fmt .Sprintf ("Failed to serve content: %v" , err ), http .StatusInternalServerError )
455- }
456- })
457- }
458-
459- func (c * clientImpl ) RegisterFileHandler (path string , option FileHandlerOption ) {
460- c .UrlHandlerMux .HandleFunc (path , func (w http.ResponseWriter , r * http.Request ) {
461- if err := serveFileOption (w , r , option ); err != nil {
462- http .Error (w , err .Error (), http .StatusInternalServerError )
463- }
464- })
305+ c .UrlHandlerMux .HandleFunc (pattern , fn )
465306}
0 commit comments