¿Cuál es la mejor manera de agrupar recursos estáticos en un programa Go? [cerrado]

100

Estoy trabajando en una pequeña aplicación web en Go que está destinada a utilizarse como herramienta en la máquina de un desarrollador para ayudar a depurar sus aplicaciones / servicios web. La interfaz del programa es una página web que incluye no solo HTML, sino algo de JavaScript (para funcionalidad), imágenes y CSS (para estilo). Estoy planeando abrir esta aplicación, por lo que los usuarios simplemente deberían poder ejecutar un Makefile y todos los recursos irán donde necesiten ir. Sin embargo, también me gustaría poder distribuir simplemente un ejecutable con la menor cantidad posible de archivos / dependencias. ¿Existe una buena manera de agrupar HTML / CSS / JS con el ejecutable, de modo que los usuarios solo tengan que descargar y preocuparse por un archivo?


En este momento, en mi aplicación, entregar un archivo estático se parece un poco a esto:

// 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
}

Entonces, es bastante simple: si el archivo solicitado existe en mi directorio estático, invoque el controlador, que simplemente abre el archivo e intenta establecer un bien Content-Typeantes de servir. Mi pensamiento era que no hay razón para que esto deba basarse en el sistema de archivos real: si hubiera recursos compilados, simplemente podría indexarlos por el URI de solicitud y servirlos como tales.

Si no hay una buena manera de hacer esto, o estoy ladrando al árbol equivocado al intentar hacer esto, avíseme. Supuse que el usuario final apreciaría la menor cantidad posible de archivos para administrar.

Si hay etiquetas más apropiadas que , no dude en agregarlos o hágamelo saber.

Jimmy Sawczuk
fuente
De hecho, hoy pensé exactamente en la misma pregunta. La solución que podría explorar es usarla go generatecon una pequeña utilidad de línea de comandos (empaquetada con mi código fuente) para convertir los archivos en []byteporciones que están incrustadas como variables en el código, similar a cómo se stringerhace (ver blog.golang.org / generar ).
Ralph

Respuestas:

76

El paquete go-bindata parece que podría ser lo que le interesa.

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

Le permitirá convertir cualquier archivo estático en una llamada de función que se puede incrustar en su código y devolverá un segmento de bytes del contenido del archivo cuando se llame.

Daniel
fuente
8
Votar a favor de esto parece extrañamente egoísta en mi caso, pero lo haré de todos modos: p Para que conste, no es un paquete, sino una herramienta de línea de comandos.
jimt
Solo para que conste, este es el camino que tomé con mi proyecto. En algún momento @jimt introdujo algunas características nuevas para hacer las cosas más fáciles de usar, pero ya no proporcionaba la granularidad que necesitaba, así que escribí mi propia herramienta que tiene menos características pero está diseñada para mi caso de uso (uso esta herramienta como una especie de preámbulo del proceso de construcción): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk
37

Incrustar archivos de texto

Si hablamos de archivos de texto, se pueden incrustar fácilmente en el código fuente. Simplemente use las comillas inversas para declarar el stringliteral de esta manera:

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

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

Consejo de optimización:

Dado que la mayoría de las veces solo necesitará escribir el recurso en un io.Writer, también puede almacenar el resultado de una []byteconversión:

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

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

Lo único con lo que debe tener cuidado es que los literales de cadena sin formato no pueden contener el carácter de comillas inversas (`). Los literales de cadena sin formato no pueden contener secuencias (a diferencia de los literales de cadena interpretados), por lo que si el texto que desea incrustar contiene comillas inversas, debe romper el literal de cadena sin procesar y concatenar comillas inversas como literales de cadena interpretados, como en este ejemplo:

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

El rendimiento no se ve afectado, ya que el compilador ejecutará estas concatenaciones.

Incrustar archivos binarios

Almacenar como un segmento de bytes

Para archivos binarios (por ejemplo, imágenes) más compacto (con respecto al binario nativo resultante) y más eficiente sería tener el contenido del archivo como []byteen su código fuente. Esto puede ser generado por bibliotecas / herramientas de terceros como go-bindata .

Si no desea utilizar una biblioteca de terceros para esto, aquí hay un fragmento de código simple que lee un archivo binario y genera el código fuente de Go que declara una variable de tipo []byteque se inicializará con el contenido exacto del archivo:

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("}")

Salida de ejemplo si el archivo contendría bytes de 0 a 16 (pruébelo en Go Playground ):

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

Almacenar como base64 string

Si el archivo no es "demasiado grande" (la mayoría de las imágenes / iconos cumplen los requisitos), también existen otras opciones viables. Puede convertir el contenido del archivo a Base64 stringy almacenarlo en su código fuente. Al iniciar la aplicación ( func init()) o cuando sea necesario, puede decodificarla con el []bytecontenido original . Go tiene un buen soporte para la codificación Base64 en el encoding/base64paquete.

Convertir un archivo (binario) a base64 stringes tan simple como:

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

Almacene la cadena resultante en base64 en su código fuente, por ejemplo, como un archivo const.

Decodificarlo es solo una llamada de función:

const imgBase64 = "<insert base64 string here>"

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

Almacenando como cotizado string

Más eficiente que almacenar como base64, pero puede ser más largo en el código fuente es almacenar el literal de cadena entre comillas de los datos binarios. Podemos obtener la forma citada de cualquier cadena usando la strconv.Quote()función:

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

Para datos binarios que contienen valores desde 0 hasta 64, así es como se vería la salida (pruébelo en 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:;<=>?"

(Tenga en cuenta que le strconv.Quote()añade y antepone una comilla).

Puede utilizar directamente esta cadena entre comillas en su código fuente, por ejemplo:

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á listo para usar, no es necesario decodificarlo; la eliminación de comillas la realiza el compilador Go, en el momento de la compilación.

También puede almacenarlo como un segmento de bytes si lo necesita así:

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
fuente
¿Hay alguna forma de vincular un sharchivo a un ejecutable de Go?
Kasun Siyambalapitiya
Supongo que los datos deberían ser imgdata en el primer fragmento de código en la sección "almacenar como un segmento de bytes".
lógico x 2
1
@deusexmachina Tienes razón, lo arregló. El código del patio de recreo ya era correcto.
icza
2

También hay una forma exótica: uso el complemento maven para construir proyectos GoLang y permite usar el preprocesador JCP para incrustar bloques binarios y archivos de texto en las fuentes. En el caso, el código se ve como la línea de abajo ( y se puede encontrar un ejemplo aquí )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}
Igor Maznitsa
fuente
@ ¿Es posible vincular un directorio que tenga un shejecutable como el anterior
Kasun Siyambalapitiya
@KasunSiyambalapitiya ¿Vincular un directorio? ¿Vincular un sharchivo? No estoy seguro de lo que quieres decir. Si desea que todo en un directorio esté incrustado, esto es algo con lo que he hecho go-bindata. Por ejemplo, si coloco //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/un archivo go (no generado), go generate ./...ejecutaré go-bindata en el directorio del paquete, incrustando todo en un subdirectorio de datos pero sin el prefijo 'data /'.
Mark
1

Como una alternativa popular a lo go-bindatamencionado en otra respuesta, mjibson / esc también incrusta archivos arbitrarios, pero maneja árboles de directorios de manera particularmente conveniente.

robx
fuente