[GH-ISSUE #97] Not working on Windows #373

Open
opened 2026-03-07 22:14:46 +03:00 by kerem · 18 comments
Owner

Originally created by @crossworth on GitHub (May 8, 2021).
Original GitHub issue: https://github.com/hibiken/asynqmon/issues/97

Hi, great project. I'm trying using it on Windows, but can't get it to work.

When running and trying to access the browser, the following error is displayed:
open ui\build\index.html: file does not exist

I have done some diging and have found the problem, but don't know the right "fix", so for this reason I'm opening this issue instead of PR.

Basically on this line, filepath.Abs returns C:\
github.com/hibiken/asynqmon@0eb9d88c53/main.go (L59)

This causes problems with the check if is /
github.com/hibiken/asynqmon@0eb9d88c53/main.go (L65)
There is a problem with filepath.Join as well, since it will use \ and GO embed.FS will not find the file then.
https://golang.org/pkg/embed/#hdr-Directives

The path separator is a forward slash, even on Windows systems.

The only way I could get it to work was making the following changes:

p := r.URL.Path
if filepath.IsAbs(p) {
	p = srv.indexFilePath()
} else {
	p = path.Join(srv.staticDirPath, p)
}

// ...
func (srv *staticFileServer) indexFilePath() string {
	return path.Join(srv.staticDirPath, srv.indexFileName)
}
Originally created by @crossworth on GitHub (May 8, 2021). Original GitHub issue: https://github.com/hibiken/asynqmon/issues/97 Hi, great project. I'm trying using it on Windows, but can't get it to work. When running and trying to access the browser, the following error is displayed: `open ui\build\index.html: file does not exist` I have done some diging and have found the problem, but don't know the right "fix", so for this reason I'm opening this issue instead of PR. Basically on this line, `filepath.Abs` returns `C:\` https://github.com/hibiken/asynqmon/blob/0eb9d88c5365d7f34c1a9ff61518a5075170bb41/main.go#L59 This causes problems with the check if is `/` https://github.com/hibiken/asynqmon/blob/0eb9d88c5365d7f34c1a9ff61518a5075170bb41/main.go#L65 There is a problem with `filepath.Join` as well, since it will use `\` and GO `embed.FS` will not find the file then. https://golang.org/pkg/embed/#hdr-Directives > The path separator is a forward slash, even on Windows systems. The only way I could get it to work was making the following changes: ```go p := r.URL.Path if filepath.IsAbs(p) { p = srv.indexFilePath() } else { p = path.Join(srv.staticDirPath, p) } // ... func (srv *staticFileServer) indexFilePath() string { return path.Join(srv.staticDirPath, srv.indexFileName) } ```
Author
Owner

@hibiken commented on GitHub (May 8, 2021):

@crossworth Thank you for reporting this issue!
Apologies for not testing fully on other OSs 🙏

Is this blocking you from using the tool completely? Or have you found a way to work around it until the fix is in?
(I'm trying to gauge the priority of this bug).

<!-- gh-comment-id:835447758 --> @hibiken commented on GitHub (May 8, 2021): @crossworth Thank you for reporting this issue! Apologies for not testing fully on other OSs 🙏 Is this blocking you from using the tool completely? Or have you found a way to work around it until the fix is in? (I'm trying to gauge the priority of this bug).
Author
Owner

@crossworth commented on GitHub (May 8, 2021):

No worries, I'm able to use with the changes from my last message.

So maybe this can be low priority.

<!-- gh-comment-id:835476360 --> @crossworth commented on GitHub (May 8, 2021): No worries, I'm able to use with the changes from my last message. So maybe this can be low priority.
Author
Owner

@hibiken commented on GitHub (May 9, 2021):

Got it, thanks for letting me know!
I'll get to this issue in a few weeks 👍

<!-- gh-comment-id:835688623 --> @hibiken commented on GitHub (May 9, 2021): Got it, thanks for letting me know! I'll get to this issue in a few weeks 👍
Author
Owner

@SkYNewZ commented on GitHub (Mar 5, 2022):

What do you think about embeding templates ? https://pkg.go.dev/embed

<!-- gh-comment-id:1059655209 --> @SkYNewZ commented on GitHub (Mar 5, 2022): What do you think about embeding templates ? https://pkg.go.dev/embed
Author
Owner

@hibiken commented on GitHub (Mar 6, 2022):

@SkYNewZ thank you for the comment. There were some changes made to use the embed package since this issue has been created, so this issue may be stale. I'll look into this issue once again soon when I have enough bandwidth.

<!-- gh-comment-id:1059891501 --> @hibiken commented on GitHub (Mar 6, 2022): @SkYNewZ thank you for the comment. There were some changes made to use the `embed` package since this issue has been created, so this issue may be stale. I'll look into this issue once again soon when I have enough bandwidth.
Author
Owner

@eaglebetter commented on GitHub (Jun 6, 2022):

The problem is still not fixed

<!-- gh-comment-id:1147186629 --> @eaglebetter commented on GitHub (Jun 6, 2022): The problem is still not fixed
Author
Owner

@localhosted commented on GitHub (Aug 23, 2022):

Can't use it at all in Windows, only forking and fixing. Any news on this PR(https://github.com/hibiken/asynqmon/pull/251)?

<!-- gh-comment-id:1224726034 --> @localhosted commented on GitHub (Aug 23, 2022): Can't use it at all in Windows, only forking and fixing. Any news on this PR(https://github.com/hibiken/asynqmon/pull/251)?
Author
Owner

@navossoc commented on GitHub (Jan 23, 2023):

+1

<!-- gh-comment-id:1399786195 --> @navossoc commented on GitHub (Jan 23, 2023): +1
Author
Owner

@4GUO commented on GitHub (Mar 9, 2023):

+1 template: pattern matches no files: ui\build\index.html

<!-- gh-comment-id:1461618730 --> @4GUO commented on GitHub (Mar 9, 2023): +1 template: pattern matches no files: `ui\build\index.html`
Author
Owner

@lidaqi001 commented on GitHub (Apr 25, 2023):

+1 template: pattern matches no files: ui\build\index.html

<!-- gh-comment-id:1521351546 --> @lidaqi001 commented on GitHub (Apr 25, 2023): +1 template: pattern matches no files: ui\build\index.html
Author
Owner

@4GUO commented on GitHub (Apr 25, 2023):

这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

<!-- gh-comment-id:1521352356 --> @4GUO commented on GitHub (Apr 25, 2023): 这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
Author
Owner

@windowshopr commented on GitHub (Oct 26, 2023):

+1 template: pattern matches no files: ui\build\index.html

<!-- gh-comment-id:1780440788 --> @windowshopr commented on GitHub (Oct 26, 2023): +1 template: pattern matches no files: ui\build\index.html
Author
Owner

@4GUO commented on GitHub (Oct 26, 2023):

这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

<!-- gh-comment-id:1780441082 --> @4GUO commented on GitHub (Oct 26, 2023): 这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
Author
Owner

@Raindrifter commented on GitHub (Feb 21, 2024):

+1
It looks like a repository in disrepair, is there a better fork version?
看起来像是个年久失修的仓库,有没有比较好的fork版本呢?

<!-- gh-comment-id:1956145384 --> @Raindrifter commented on GitHub (Feb 21, 2024): +1 It looks like a repository in disrepair, is there a better fork version? 看起来像是个年久失修的仓库,有没有比较好的fork版本呢?
Author
Owner

@4GUO commented on GitHub (Feb 21, 2024):

这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

<!-- gh-comment-id:1956145912 --> @4GUO commented on GitHub (Feb 21, 2024): 这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
Author
Owner

@Fate0x01 commented on GitHub (Oct 28, 2024):

By modifying the static.go file, successfully made the UI open normally under the Windows system.

package asynqmon

import (
	"embed"
	"errors"
	"html/template"
	"io/fs"
	"net/http"
	"path"
	"path/filepath"
	"strings"
)

// uiAssetsHandler is a http.Handler.
// The path to the static file directory and
// the path to the index file within that static directory are used to
// serve the SPA.
type uiAssetsHandler struct {
	rootPath       string
	contents       embed.FS
	staticDirPath  string
	indexFileName  string
	prometheusAddr string
	readOnly       bool
}

// ServeHTTP inspects the URL path to locate a file within the static dir
// on the SPA handler.
// If path '/' is requested, it will serve the index file, otherwise it will
// serve the file specified by the URL path.
func (h *uiAssetsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Get the absolute p to prevent directory traversal.
	p := r.URL.Path
	if filepath.IsAbs(p) {
		p = h.indexFilePath()
	}

	// Get the path relative to the root path.
	if !strings.HasPrefix(p, h.rootPath) {
		http.Error(w, "unexpected path prefix", http.StatusBadRequest)
		return
	}
	p = strings.TrimPrefix(p, h.rootPath)

	if code, err := h.serveFile(w, p); err != nil {
		http.Error(w, err.Error(), code)
		return
	}
}

func (h *uiAssetsHandler) indexFilePath() string {
	return path.Join(h.staticDirPath, h.indexFileName)
}

func (h *uiAssetsHandler) renderIndexFile(w http.ResponseWriter) error {
	// Note: Replace the default delimiter ("{{") with a custom one
	// since webpack escapes the '{' character when it compiles the index.html file.
	// See the "homepage" field in package.json.
	tmpl, err := template.New(h.indexFileName).Delims("/[[", "]]").ParseFS(h.contents, h.indexFilePath())
	if err != nil {
		return err
	}
	data := struct {
		RootPath       string
		PrometheusAddr string
		ReadOnly       bool
	}{
		RootPath:       h.rootPath,
		PrometheusAddr: h.prometheusAddr,
		ReadOnly:       h.readOnly,
	}
	return tmpl.Execute(w, data)
}

// serveFile writes file requested at path and returns http status code and error if any.
// If requested path is root, it serves the index file.
// Otherwise, it looks for file requiested in the static content filesystem
// and serves if a file is found.
// If a requested file is not found in the filesystem, it serves the index file to
// make sure when user refreshes the page in SPA things still work.
func (h *uiAssetsHandler) serveFile(w http.ResponseWriter, p string) (code int, err error) {
	if p == "/" || p == "" {
		if err := h.renderIndexFile(w); err != nil {
			return http.StatusInternalServerError, err
		}
		return http.StatusOK, nil
	}
	p = path.Join(h.staticDirPath, p)
	bytes, err := h.contents.ReadFile(p)
	if err != nil {
		// If path is error (e.g. file not exist, path is a directory), serve index file.
		var pathErr *fs.PathError
		if errors.As(err, &pathErr) {
			if err := h.renderIndexFile(w); err != nil {
				return http.StatusInternalServerError, err
			}
			return http.StatusOK, nil
		}
		return http.StatusInternalServerError, err
	}
	// Setting the MIME type for .js files manually to application/javascript as
	// http.DetectContentType is using https://mimesniff.spec.whatwg.org/ which
	// will not recognize application/javascript for security reasons.
	if strings.HasSuffix(p, ".js") {
		w.Header().Add("Content-Type", "application/javascript; charset=utf-8")
	} else {
		w.Header().Add("Content-Type", http.DetectContentType(bytes))
	}

	if _, err := w.Write(bytes); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}
<!-- gh-comment-id:2440685093 --> @Fate0x01 commented on GitHub (Oct 28, 2024): By modifying the static.go file, successfully made the UI open normally under the Windows system. ``` package asynqmon import ( "embed" "errors" "html/template" "io/fs" "net/http" "path" "path/filepath" "strings" ) // uiAssetsHandler is a http.Handler. // The path to the static file directory and // the path to the index file within that static directory are used to // serve the SPA. type uiAssetsHandler struct { rootPath string contents embed.FS staticDirPath string indexFileName string prometheusAddr string readOnly bool } // ServeHTTP inspects the URL path to locate a file within the static dir // on the SPA handler. // If path '/' is requested, it will serve the index file, otherwise it will // serve the file specified by the URL path. func (h *uiAssetsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Get the absolute p to prevent directory traversal. p := r.URL.Path if filepath.IsAbs(p) { p = h.indexFilePath() } // Get the path relative to the root path. if !strings.HasPrefix(p, h.rootPath) { http.Error(w, "unexpected path prefix", http.StatusBadRequest) return } p = strings.TrimPrefix(p, h.rootPath) if code, err := h.serveFile(w, p); err != nil { http.Error(w, err.Error(), code) return } } func (h *uiAssetsHandler) indexFilePath() string { return path.Join(h.staticDirPath, h.indexFileName) } func (h *uiAssetsHandler) renderIndexFile(w http.ResponseWriter) error { // Note: Replace the default delimiter ("{{") with a custom one // since webpack escapes the '{' character when it compiles the index.html file. // See the "homepage" field in package.json. tmpl, err := template.New(h.indexFileName).Delims("/[[", "]]").ParseFS(h.contents, h.indexFilePath()) if err != nil { return err } data := struct { RootPath string PrometheusAddr string ReadOnly bool }{ RootPath: h.rootPath, PrometheusAddr: h.prometheusAddr, ReadOnly: h.readOnly, } return tmpl.Execute(w, data) } // serveFile writes file requested at path and returns http status code and error if any. // If requested path is root, it serves the index file. // Otherwise, it looks for file requiested in the static content filesystem // and serves if a file is found. // If a requested file is not found in the filesystem, it serves the index file to // make sure when user refreshes the page in SPA things still work. func (h *uiAssetsHandler) serveFile(w http.ResponseWriter, p string) (code int, err error) { if p == "/" || p == "" { if err := h.renderIndexFile(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } p = path.Join(h.staticDirPath, p) bytes, err := h.contents.ReadFile(p) if err != nil { // If path is error (e.g. file not exist, path is a directory), serve index file. var pathErr *fs.PathError if errors.As(err, &pathErr) { if err := h.renderIndexFile(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } return http.StatusInternalServerError, err } // Setting the MIME type for .js files manually to application/javascript as // http.DetectContentType is using https://mimesniff.spec.whatwg.org/ which // will not recognize application/javascript for security reasons. if strings.HasSuffix(p, ".js") { w.Header().Add("Content-Type", "application/javascript; charset=utf-8") } else { w.Header().Add("Content-Type", http.DetectContentType(bytes)) } if _, err := w.Write(bytes); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } ```
Author
Owner

@chaegumi commented on GitHub (Mar 27, 2025):

+1 template: pattern matches no files: ui\build\index.html

<!-- gh-comment-id:2756237421 --> @chaegumi commented on GitHub (Mar 27, 2025): +1 template: pattern matches no files: `ui\build\index.html`
Author
Owner

@4GUO commented on GitHub (Mar 27, 2025):

这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

<!-- gh-comment-id:2756239418 --> @4GUO commented on GitHub (Mar 27, 2025): 这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/asynqmon#373
No description provided.