Qual é a melhor maneira de agrupar recursos estáticos em um programa Go? [fechadas]

100

Estou trabalhando em um pequeno aplicativo da web em Go que deve ser usado como uma ferramenta na máquina de um desenvolvedor para ajudar a depurar seus aplicativos / serviços da web. A interface do programa é uma página da web que inclui não apenas o HTML, mas algum JavaScript (para funcionalidade), imagens e CSS (para estilização). Estou planejando abrir o código-fonte deste aplicativo, então os usuários devem simplesmente ser capazes de executar um Makefile e todos os recursos irão para onde precisam. No entanto, também gostaria de poder simplesmente distribuir um executável com o mínimo de arquivos / dependências possível. Existe uma boa maneira de agrupar o HTML / CSS / JS com o executável, de modo que os usuários só tenham que baixar e se preocupar com um arquivo?


Agora, em meu aplicativo, veicular um arquivo estático se parece um pouco com isto:

// called via http.ListenAndServe
func switchboard(w http.ResponseWriter, r *http.Request) {

    // snipped dynamic routing...

    // look for static resource
    uri := r.URL.RequestURI()
    if fp, err := os.Open("static" + uri); err == nil {
        defer fp.Close()
        staticHandler(w, r, fp)
        return
    }

    // snipped blackhole route
}

Portanto, é muito simples: se o arquivo solicitado existe em meu diretório estático, invoque o manipulador, que simplesmente abre o arquivo e tenta definir um bom Content-Typeantes de servir. Meu pensamento era que não há razão para que isso precise ser baseado no sistema de arquivos real: se houvesse recursos compilados, eu poderia simplesmente indexá-los pelo URI de solicitação e servi-los como tal.

Se não houver uma boa maneira de fazer isso ou se eu estiver latindo na árvore errada ao tentar fazer isso, me avise. Achei que o usuário final gostaria de ter o mínimo de arquivos possível para gerenciar.

Se houver tags mais apropriadas do que , sinta-se à vontade para adicioná-los ou me avisar.

Jimmy Sawczuk
fonte
Na verdade, pensei exatamente na mesma pergunta hoje. A solução que posso explorar é usar go generateum pequeno utilitário de linha de comando (empacotado com meu código-fonte) para converter os arquivos em []bytefatias que são incorporadas como variáveis ​​no código, semelhante a como stringerfaz (consulte blog.golang.org / gerar ).
Ralph de

Respostas:

76

O pacote go-bindata parece ser o que você está interessado.

https://github.com/go-bindata/go-bindata

Isso permitirá que você converta qualquer arquivo estático em uma chamada de função que pode ser incorporada em seu código e retornará uma fatia de byte do conteúdo do arquivo quando chamada.

Daniel
fonte
8
Votar positivamente parece estranhamente egoísta no meu caso, mas farei isso de qualquer maneira: p Só para registrar, não é um pacote, mas uma ferramenta de linha de comando.
jimt
Só para constar, esse é o caminho que fiz com meu projeto. Em algum ponto @jimt introduziu alguns novos recursos para tornar as coisas mais amigáveis ​​ao usuário, mas não forneceu mais a granularidade de que eu precisava, então escrevi minha própria ferramenta que tem menos recursos, mas é projetada para meu caso de uso (eu uso essa ferramenta como uma espécie de preâmbulo do processo de construção): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk
37

Incorporação de arquivos de texto

Se estamos falando de arquivos de texto, eles podem ser facilmente incorporados ao próprio código-fonte. Basta usar as aspas para declarar o stringliteral assim:

const html = `
<html>
<body>Example embedded HTML content.</body>
</html>
`

// Sending it:
w.Write([]byte(html))  // w is an io.Writer

Dica de otimização:

Como na maioria das vezes você só precisa gravar o recurso em um io.Writer, você também pode armazenar o resultado de uma []byteconversão:

var html = []byte(`
<html><body>Example...</body></html>
`)

// Sending it:
w.Write(html)  // w is an io.Writer

A única coisa com a qual você deve ter cuidado é que os literais de string bruta não podem conter o caractere de aspas invertidas (`). Literais de string bruta não podem conter sequências (ao contrário dos literais de string interpretados), portanto, se o texto que você deseja incorporar contiver aspas, você terá que quebrar o literal de string bruto e concatenar aspas invertidas como literais de string interpretados, como neste exemplo:

var html = `<p>This is a back quote followed by a dot: ` + "`" + `.</p>`

O desempenho não é afetado, pois essas concatenações serão executadas pelo compilador.

Incorporando arquivos binários

Armazenando como uma fatia de byte

Para arquivos binários (por exemplo, imagens) mais compactos (em relação ao binário nativo resultante) e mais eficiente seria ter o conteúdo do arquivo como um []byteem seu código-fonte. Isso pode ser gerado por toos / bibliotecas de terceiros, como go-bindata .

Se você não quiser usar uma biblioteca de terceiros para isso, aqui está um snippet de código simples que lê um arquivo binário e gera o código-fonte Go que declara uma variável do tipo []byteque será inicializada com o conteúdo exato do arquivo:

imgdata, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}

fmt.Print("var imgdata = []byte{")
for i, v := range imgdata {
    if i > 0 {
        fmt.Print(", ")
    }
    fmt.Print(v)
}
fmt.Println("}")

Exemplo de saída se o arquivo contiver bytes de 0 a 16 (experimente no Go Playground ):

var imgdata = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

Armazenando como base64 string

Se o arquivo não for "muito grande" (a maioria das imagens / ícones se qualificam), existem outras opções viáveis ​​também. Você pode converter o conteúdo do arquivo em Base64 stringe armazená-lo em seu código-fonte. Na inicialização do aplicativo ( func init()) ou quando necessário, você pode decodificá-lo para o []byteconteúdo original . Go tem bom suporte para codificação Base64 no encoding/base64pacote.

Converter um arquivo (binário) em base64 stringé tão simples quanto:

data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(data))

Armazene a string de resultado base64 em seu código-fonte, por exemplo, como um const.

Decodificá-lo é apenas uma chamada de função:

const imgBase64 = "<insert base64 string here>"

data, err := base64.StdEncoding.DecodeString(imgBase64) // data is of type []byte

Armazenando conforme citado string

Mais eficiente do que armazenar como base64, mas pode ser mais longo no código-fonte é armazenar a string literal entre aspas dos dados binários. Podemos obter a forma entre aspas de qualquer string usando a strconv.Quote()função:

data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(strconv.Quote(string(data))

Para dados binários contendo valores de 0 a 64, esta é a aparência da saída (experimente no Go Playground ):

"\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

(Observe que strconv.Quote()acrescenta e adiciona uma aspa.)

Você pode usar diretamente esta string entre aspas em seu código-fonte, por exemplo:

const imgdata = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

Está pronto para usar, não há necessidade de decodificá-lo; o unquoting é feito pelo compilador Go, em tempo de compilação.

Você também pode armazená-lo como uma fatia de byte, caso precise desta forma:

var imgdata = []byte("\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?")
icza
fonte
existe alguma maneira de vincular um sharquivo a um executável go?
Kasun Siyambalapitiya
Eu acho que os dados devem ser imgdata no primeiro trecho de código na seção "armazenando como uma fatia de byte".
lógico x 2 de
1
@deusexmachina Você está certo, consertou. O código no playground já estava correto.
icza
2

também há uma maneira exótica - eu uso o plugin maven para construir projetos GoLang e permite usar o pré - processador JCP para embutir blocos binários e arquivos de texto em fontes. No caso, o código é semelhante à linha abaixo ( e alguns exemplos podem ser encontrados aqui )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}
Igor Maznitsa
fonte
@é possível vincular um diretório com um shou um executável como acima
Kasun Siyambalapitiya
@KasunSiyambalapitiya Vincular um diretório? Vincular um sharquivo? Não tenho certeza do que você quer dizer. Se você quiser que tudo em um diretório seja incorporado, eu fiz isso go-bindata. Por exemplo, se eu colocar //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/um arquivo go (não gerado), go generate ./...executarei go-bindata no diretório do pacote, incorporando tudo em um subdiretório de dados, mas com o prefixo 'data /' removido.
Marcos
1

Como uma alternativa popular ao go-bindatamencionado em outra resposta, mjibson / esc também incorpora arquivos arbitrários, mas lida com árvores de diretório de maneira particularmente conveniente.

robx
fonte