2017-08-08 125 views
0

我们有一个程序,人们可以在他们的机器上编译。它有一个HTTP接口,但也可以通过命令行调用。加载需要的文件,相对于绝对路径

为了给HTTP客户端提供漂亮的错误页面,我们希望提供错误页面。我们使用go html/template软件包,使用了非常简单的解决方案。

因此,为了使程序找到模板,我们目前正在做的事:

func init() { 
    prefStr := "path/to/http/tmpl" 
    pathPrefix,err := filepath.Abs(prefStr) 
    if err != nil { 
    log.Warn("Template path %s is not available!", prefStr) 
    } 

    pathPrefix + "/err.html" 
} 

现在调试应用程序时,这通常效果很好 - 我们是在包的根目录下,这样filepath.Abs()正确解析,像这样: $GOPATH/github.com/user/repo/path/to/http/tmpl(正确扩展$GOPATH

但是,当我们通过命令行通过可执行文件调用应用程序时,这是行不通的。命令行当然可以从文件系统的任何地方调用,以方便例如提供当前目录中的文件作为参数。

简而言之,运行/some/other/path/on/fs/our-executable filename.txt导致上述init()函数因目录错误拼接而中断:它需要/some/other/path/on/fs/来创建绝对路径,这是错误的。因此,它与 panic: open /some/other/path/on/fs/path/to/http/tmpl/err.html: no such file or directory

我已经搜查,到目前为止,只发现这个崩溃: How can I open files using relative paths in Go?

但这正是并不适用于我们。 另一种解决方案提出捆绑编译的go资源,但这看起来很奇怪,因为错误页面是html文本。

我们也试过 https://stackoverflow.com/a/31464648/169252

,但它具有相同的效果。

我们如何确保路径始终正确解析?这似乎是一件不应该太难完成的事情,但我们目前还没有成功。

编辑:这不是问题How can I open files using relative paths in Go?的确切副本。正如我在问题文本中已经提到的那样,我已经自己查阅了它。它建议使用filepath.Abs()。但正如我的问题所阐明的那样,对我们来说这不起作用,就像我们的可执行文件被从不同的地方调用一样,filepath.Abs()不会返回相同的值,因此对我们不起作用。

+0

'path/to/http/tmpl'是绝对路径吗?这意味着它从文件系统的根目录开始。例子'/ home/faboolous/abc/defg/tmpl' –

+0

@ SamuelToh不,它不是绝对路径,否则它不能移植。这是从回购的根源的相对路径。 – faboolous

+0

使用[go-bindata](https://github.com/jteeuwen/go-bindata)将模板编译为go二进制文件 – Mark

回答

0

我认为你的挑战在于人们可以在磁盘上的任何位置安装该程序,并且该程序必须足够聪明才能知道它以后的位置。

我看到的一种常见方法是人们通常使用environment variables将它们锚定到应用程序的安装路径。我相信您可能已经看到*_HOME的命名模式(如JAVA_HOME,MAVEN_HOME)的环境变量,并且它们的值始终是安装位置的文件路径。

我想你可以在这里做同样的事情。强制你的用户拥有MYAPP_HOME变量定义,并且在应用程序的开始时确保它已被设置,否则抛出一个错误,说MYAPP_HOME没有设置。

然后,您只需要简单查找MYAPP_HOME + /http/tmpl的值以获取模板html文件的值。

例子:

package main 

import "os" 

func main() { 
    // Assuming MYAPP_HOME has been verified that it is set 
    // Then: 
    tmlPath := os.Getenv("MYAPP_HOME") + "/http/tmpl/" 
    errTml := tmlPath + "err.html" 
} 
+0

我明白这个解决方案,感谢您的建议。 Go没有真正的方法来获取文件存储的当前目录吗? – faboolous

+0

我不认为这是因为您的产品的二进制文件可以安装在文件系统的任何位置,并且您无法预测用户将要放置的位置,除非您明确要求它们将其安装到某个位置。然后你可以'将你的代码的路径硬编码,这是不好的。 –

+0

即使在'go'中有一个API可以神奇地在用户的系统上搜索名为'tmpl.html'的文件,我在想如何验证tmpl文件确实是我们之后呢?因为文件名不是全球性的。那么我们可以做'校验和',但也许它有点过分杀,肯定会让性能受到影响...... –

0

如果你不热衷于使用当前工作目录,或通过该目录中,您可以通过从OS软件包调用os.Executable找到绝对的可执行文件的路径。

appPath, err := os.Executable() 

操作系统软件包通常将包含类似如何获取当前工作目录的操作系统特定的东西。在golang.org上查看pkg文档和软件包列表是值得的,因为它们非常好,通常你会在那里找到答案。

https://golang.org/pkg/os

如果用户去得到安装,你可以在这里采取另一种方法是依靠事实,你的模板将与下GOPATH的PKG安装,让您可以随时在$ GOPATH/src目录找到它们/你的/项目/路径/模板(或〜/现在默认gopath,它不是严格要求)。

最安全的方法可能是将它们与二进制文件捆绑在一个虚拟文件系统中,因为这意味着您完全不依赖于外部,并且不关心托管应用程序的位置,甚至根本无法访问文件。

0

我推荐在这种情况下使用相对路径。

根据您的描述,您似乎正在开发一个Web应用程序。虽然它在个人开发人员的机器上可以正常工作,但您需要注意,您的应用程序可以部署在生产服务器上的任何目录下。您无法确定应用程序的部署位置,但始终可以确定静态文件相对于应用程序根目录的位置。

当您在命令行中调用您的应用程序时,应该将所有必需的静态文件复制到与您的开发环境完全相同的相同路径。我的典型结构是:

project/ 
    |- config.json 
    |- main.go 
    |- package1/ 
    |- package2/ 
    |- static/ 
     |- templates/ 
     | |- index.html 
     | |- base.html 
     |- css/ 
     |- javascript/ 
     |- image/ 

当您准备从运行命令行应用程序,请务必将config.json和静态/目录拷贝到同一级别的可执行二进制文件。那么你所需要做的就是在你的代码中使用相对路径而不做任何噩梦。

0

对于记录:因为我们只有两个模板,我们采取将它们作为字符串存储在go文件中以便它们进行编译。我们的html模板非常简单,所以这是一个合理的方法。