Cách tốt nhất để gói các tài nguyên tĩnh trong chương trình Go là gì? [đóng cửa]


100

Tôi đang làm việc trên một ứng dụng web nhỏ trong Go được sử dụng như một công cụ trên máy của nhà phát triển để giúp gỡ lỗi các ứng dụng / dịch vụ web của họ. Giao diện của chương trình là một trang web không chỉ bao gồm HTML mà còn một số JavaScript (cho chức năng), hình ảnh và CSS (để tạo kiểu). Tôi đang lên kế hoạch tìm nguồn mở ứng dụng này, vì vậy người dùng chỉ cần có thể chạy Makefile và tất cả các tài nguyên sẽ đến nơi họ cần. Tuy nhiên, tôi cũng muốn có thể đơn giản phân phối một tệp thực thi với càng ít tệp / phụ thuộc càng tốt. Có cách nào tốt để gộp HTML / CSS / JS với tệp thực thi, để người dùng chỉ phải tải xuống và lo lắng về một tệp không?


Hiện tại, trong ứng dụng của tôi, việc phân phát tệp tĩnh trông giống như sau:

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

Vì vậy, nó khá đơn giản: nếu tệp được yêu cầu tồn tại trong thư mục tĩnh của tôi, hãy gọi trình xử lý, chỉ cần mở tệp và cố gắng thiết lập tốt Content-Typetrước khi phân phát. Tôi nghĩ rằng không có lý do gì mà điều này cần phải dựa trên hệ thống tệp thực: nếu có các tài nguyên đã được biên dịch, tôi có thể chỉ cần lập chỉ mục chúng theo URI yêu cầu và phân phát chúng như vậy.

Nếu không có cách nào tốt để làm điều này hoặc tôi đang trồng nhầm cây bằng cách cố gắng làm điều này, hãy cho tôi biết. Tôi chỉ nghĩ rằng người dùng cuối sẽ đánh giá cao càng ít tệp để quản lý càng tốt.

Nếu có nhiều thẻ thích hợp hơn , vui lòng thêm chúng hoặc cho tôi biết.



Tôi thực sự chỉ nghĩ về chính xác câu hỏi ngày hôm nay. Giải pháp mà tôi có thể khám phá là sử dụng go generatemột tiện ích dòng lệnh nhỏ (được đóng gói với mã nguồn của tôi) để chuyển đổi các tệp thành []bytecác phần được nhúng dưới dạng các biến trong mã, tương tự như cách stringerthực hiện (xem blog.golang.org / tạo ).
Ralph

Câu trả lời:


76

Gói go-bindata có vẻ như nó có thể là thứ bạn quan tâm.

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

Nó sẽ cho phép bạn chuyển đổi bất kỳ tệp tĩnh nào thành một lệnh gọi hàm có thể được nhúng vào mã của bạn và sẽ trả về một byte nội dung tệp khi được gọi.


8
Việc ủng hộ điều này có vẻ hơi kỳ cục trong trường hợp của tôi, nhưng tôi vẫn sẽ làm điều đó: p Tuy nhiên, đối với bản ghi, nó không phải là một gói, mà là một công cụ dòng lệnh.
jimt

Chỉ để ghi lại, đây là con đường tôi đã đi với dự án của mình. Tại một số thời điểm, @jimt đã giới thiệu một số tính năng mới để làm cho mọi thứ thân thiện với người dùng hơn nhưng không còn cung cấp mức độ chi tiết mà tôi cần, vì vậy tôi đã viết công cụ của riêng mình có ít tính năng hơn nhưng được thiết kế cho usecase của tôi (tôi sử dụng công cụ này như một loại lời mở đầu cho quá trình xây dựng): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk

37

Nhúng tệp văn bản

Nếu chúng ta đang nói về các tệp văn bản, chúng có thể dễ dàng được nhúng vào chính mã nguồn. Chỉ cần sử dụng dấu ngoặc kép để khai báo stringchữ như sau:

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

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

Mẹo tối ưu hóa:

Vì hầu hết các trường hợp, bạn chỉ cần ghi tài nguyên vào một io.Writer, bạn cũng có thể lưu trữ kết quả của một []bytechuyển đổi:

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

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

Chỉ có điều bạn phải cẩn thận là các ký tự chuỗi thô không thể chứa ký tự dấu ngoặc kép (`). Các ký tự chuỗi thô không thể chứa các chuỗi (không giống như các ký tự chuỗi được thông dịch), vì vậy nếu văn bản bạn muốn nhúng có chứa các dấu ngoặc kép, bạn phải ngắt chuỗi thô theo nghĩa đen và nối các dấu ngoặc kép dưới dạng các ký tự chuỗi được diễn giải, như trong ví dụ này:

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

Hiệu suất không bị ảnh hưởng, vì những nối này sẽ được thực thi bởi trình biên dịch.

Nhúng tệp nhị phân

Lưu trữ dưới dạng một lát byte

Đối với các tệp nhị phân (ví dụ: hình ảnh) nhỏ gọn nhất (liên quan đến kết quả nhị phân gốc) và hiệu quả nhất là có nội dung của tệp dưới dạng []bytemã nguồn của bạn. Điều này có thể được tạo bởi toos / thư viện của bên thứ 3 như go-bindata .

Nếu bạn không muốn sử dụng thư viện của bên thứ 3 cho việc này, thì đây là đoạn mã đơn giản đọc tệp nhị phân và xuất ra mã nguồn Go khai báo một loại biến []bytesẽ được khởi tạo với nội dung chính xác của tệp:

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

Ví dụ đầu ra nếu tệp sẽ chứa byte từ 0 đến 16 (hãy thử nó trên Go Playground ):

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

Lưu trữ dưới dạng cơ sở64 string

Nếu tệp không "quá lớn" (hầu hết các hình ảnh / biểu tượng đều đủ điều kiện), thì cũng có các tùy chọn khả thi khác. Bạn có thể chuyển đổi nội dung của tệp thành Base64 stringvà lưu trữ trong mã nguồn của bạn. Khi khởi động ứng dụng ( func init()) hoặc khi cần, bạn có thể giải mã nó về []bytenội dung gốc . Go có hỗ trợ tốt cho mã hóa Base64 trong encoding/base64gói.

Chuyển đổi tệp (nhị phân) sang base64 stringđơn giản như sau:

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

Lưu trữ chuỗi base64 kết quả trong mã nguồn của bạn, ví dụ: dưới dạng const.

Giải mã nó chỉ là một lệnh gọi hàm:

const imgBase64 = "<insert base64 string here>"

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

Lưu trữ như đã trích dẫn string

Hiệu quả hơn lưu trữ dưới dạng base64, nhưng có thể lâu hơn trong mã nguồn là lưu trữ chuỗi được trích dẫn của dữ liệu nhị phân. Chúng ta có thể lấy dạng trích dẫn của bất kỳ chuỗi nào bằng cách sử dụng strconv.Quote()hàm:

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

Đối với dữ liệu nhị phân chứa các giá trị từ 0 đến 64, đây là kết quả đầu ra trông như thế nào (hãy thử trên 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:;<=>?"

(Lưu ý rằng strconv.Quote()thêm và thêm dấu ngoặc kép vào nó.)

Bạn có thể sử dụng trực tiếp chuỗi được trích dẫn này trong mã nguồn của mình, ví dụ:

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:;<=>?"

Nó đã sẵn sàng để sử dụng, không cần phải giải mã nó; việc hủy trích dẫn được thực hiện bởi trình biên dịch Go, tại thời điểm biên dịch.

Bạn cũng có thể lưu trữ nó dưới dạng một byte Slice nếu bạn cần nó như vậy:

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:;<=>?")

có cách nào để liên kết một shtệp với một tệp thực thi không?
Kasun Siyambalapitiya

Tôi đoán dữ liệu phải là imgdata trong đoạn mã đầu tiên trong phần "lưu trữ dưới dạng lát byte".
logic x 2

1
@deusexmachina Bạn nói đúng, đã sửa nó. Mã trên sân chơi đã chính xác.
icza

2

Ngoài ra, có một số cách kỳ lạ - tôi sử dụng plugin maven để xây dựng các dự án GoLang và nó cho phép sử dụng bộ tiền xử lý JCP để nhúng các khối nhị phân và tệp văn bản vào nguồn. Trong trường hợp mã chỉ giống như dòng bên dưới ( và một số ví dụ có thể được tìm thấy ở đây )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}

@is nó có thể để ràng buộc một thư mục có một shhoặc một thực thi như trên
Kasun Siyambalapitiya

@KasunSiyambalapitiya Ràng buộc một thư mục? Ràng buộc một shtập tin? Không chắc chắn những gì bạn có ý nghĩa. Nếu bạn muốn nhúng mọi thứ trong một thư mục, đây là điều tôi đã làm go-bindata. Ví dụ: nếu tôi đưa //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/vào một tệp go (không được tạo), go generate ./...sẽ chạy go-bindata trong dir của gói, nhúng mọi thứ vào một subir dữ liệu nhưng với tiền tố 'data /' bị xóa.
Đánh dấu

1

Là một giải pháp thay thế phổ biến go-bindatađược đề cập trong một câu trả lời khác, mjibson / esc cũng nhúng các tệp tùy ý, nhưng xử lý cây thư mục đặc biệt thuận tiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.