วิธีใดที่ดีที่สุดในการรวมทรัพยากรแบบคงที่ในโปรแกรม Go [ปิด]


102

ฉันกำลังทำงานเกี่ยวกับเว็บแอปพลิเคชันขนาดเล็กใน Go ซึ่งมีไว้เพื่อใช้เป็นเครื่องมือในเครื่องของผู้พัฒนาเพื่อช่วยแก้ไขข้อบกพร่องของแอปพลิเคชัน / บริการบนเว็บ อินเทอร์เฟซไปยังโปรแกรมเป็นหน้าเว็บที่ไม่เพียง แต่รวมถึง HTML เท่านั้น แต่ยังรวมถึง JavaScript (สำหรับฟังก์ชันการทำงาน) รูปภาพและ CSS (สำหรับการจัดรูปแบบ) ฉันกำลังวางแผนที่จะเปิดแอปพลิเคชันนี้ดังนั้นผู้ใช้ควรจะสามารถเรียกใช้ Makefile ได้และทรัพยากรทั้งหมดจะไปที่ที่พวกเขาต้องการ อย่างไรก็ตามฉันต้องการเพียงแค่แจกจ่ายไฟล์ปฏิบัติการที่มีไฟล์ / การอ้างอิงให้น้อยที่สุด มีวิธีที่ดีในการรวม HTML / CSS / JS เข้ากับไฟล์ปฏิบัติการหรือไม่ดังนั้นผู้ใช้จึงต้องดาวน์โหลดและกังวลเกี่ยวกับไฟล์เดียวหรือไม่?


ตอนนี้ในแอปของฉันการให้บริการไฟล์คงที่จะมีลักษณะดังนี้:

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

ดังนั้นมันค่อนข้างง่าย: หากไฟล์ที่ร้องขอมีอยู่ในไดเร็กทอรีสแตติกของฉันให้เรียกใช้ตัวจัดการซึ่งเพียงแค่เปิดไฟล์และพยายามตั้งค่าที่ดีContent-Typeก่อนที่จะให้บริการ ความคิดของฉันคือไม่มีเหตุผลที่สิ่งนี้จำเป็นต้องขึ้นอยู่กับระบบไฟล์จริง: หากมีทรัพยากรที่คอมไพล์ฉันสามารถจัดทำดัชนีโดยใช้ URI คำขอและให้บริการได้

หากไม่มีวิธีที่ดีในการทำเช่นนี้หรือฉันกำลังเห่าต้นไม้ที่ไม่ถูกต้องโดยพยายามทำเช่นนี้โปรดแจ้งให้เราทราบ ฉันเพิ่งคิดว่าผู้ใช้ปลายทางจะชอบไฟล์น้อยที่สุดในการจัดการ

หากมีแท็กที่เหมาะสมกว่า โปรดอย่าลังเลที่จะเพิ่มหรือแจ้งให้เราทราบ



ที่จริงฉันเพิ่งคิดคำถามเดียวกันในวันนี้ วิธีแก้ปัญหาที่ฉันอาจสำรวจคือใช้go generateกับยูทิลิตี้บรรทัดคำสั่งขนาดเล็ก (บรรจุด้วยซอร์สโค้ดของฉัน) เพื่อแปลงไฟล์เป็น[]byteชิ้นส่วนที่ฝังเป็นตัวแปรในโค้ดคล้ายกับวิธีการstringer(ดูblog.golang.org / สร้าง )
Ralph

คำตอบ:


77

แพ็คเกจ go-bindata ดูเหมือนว่าอาจเป็นสิ่งที่คุณสนใจ

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

จะช่วยให้คุณสามารถแปลงไฟล์แบบคงที่เป็นการเรียกใช้ฟังก์ชันที่สามารถฝังอยู่ในโค้ดของคุณและจะส่งคืนเนื้อหาไฟล์เป็นไบต์เมื่อถูกเรียก


8
การลงคะแนนสิ่งนี้ดูเหมือนเป็นการให้บริการตัวเองอย่างผิดปกติในกรณีของฉัน แต่ฉันจะทำต่อไป: p สำหรับบันทึกแม้ว่าจะไม่ใช่แพ็คเกจ แต่เป็นเครื่องมือบรรทัดคำสั่ง
jimt

สำหรับการบันทึกนี่คือเส้นทางที่ฉันใช้กับโครงการของฉัน เมื่อถึงจุดหนึ่ง @jimt ได้นำเสนอคุณสมบัติใหม่ ๆ เพื่อทำให้สิ่งต่างๆเป็นมิตรกับผู้ใช้มากขึ้น แต่ไม่ได้ให้รายละเอียดที่ต้องการอีกต่อไปดังนั้นฉันจึงเขียนเครื่องมือของตัวเองซึ่งมีคุณสมบัติน้อยกว่า แต่ได้รับการออกแบบมาสำหรับกรณีการใช้งานของฉัน (ฉันใช้เครื่องมือนี้เป็นประเภท a คำนำสู่กระบวนการสร้าง): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk

40

การฝังไฟล์ข้อความ

หากเรากำลังพูดถึงไฟล์ข้อความสามารถฝังลงในซอร์สโค้ดได้อย่างง่ายดาย เพียงใช้เครื่องหมายคำพูดหลังเพื่อประกาศstringตัวอักษรดังนี้:

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

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

เคล็ดลับการเพิ่มประสิทธิภาพ:

เนื่องจากส่วนใหญ่คุณจะต้องเขียนทรัพยากรลงใน a io.Writerเท่านั้นคุณยังสามารถจัดเก็บผลลัพธ์ของการ[]byteแปลงได้:

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

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

สิ่งเดียวที่คุณต้องระวังคือตัวอักษรสตริงดิบไม่สามารถมีอักขระอัญประกาศย้อนกลับ (`) ลิเทอรัลสตริงดิบไม่สามารถมีลำดับได้ (ซึ่งแตกต่างจากลิเทอรัลสตริงที่ตีความ) ดังนั้นหากข้อความที่คุณต้องการฝังมีเครื่องหมายอัญประกาศย้อนกลับคุณต้องแบ่งลิเทอรัลสตริงดิบและใส่เครื่องหมายคำพูดกลับแบบเรียงต่อกันเป็นตัวอักษรสตริงที่ตีความเช่นในตัวอย่างนี้:

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

ประสิทธิภาพไม่ได้รับผลกระทบเนื่องจากการเชื่อมต่อเหล่านี้จะถูกดำเนินการโดยคอมไพเลอร์

การฝังไฟล์ไบนารี

จัดเก็บเป็นชิ้นส่วนไบต์

สำหรับไฟล์ไบนารี (เช่นรูปภาพ) ขนาดกะทัดรัดที่สุด (เกี่ยวกับไบนารีดั้งเดิมที่เป็นผลลัพธ์) และมีประสิทธิภาพมากที่สุดคือการมีเนื้อหาของไฟล์เป็น[]byteในซอร์สโค้ดของคุณ นี้สามารถสร้างขึ้นโดยบุคคลที่ 3 toos / ห้องสมุดเช่นไป bindata

หากคุณไม่ต้องการใช้ไลบรารีของบุคคลที่สามสำหรับสิ่งนี้ต่อไปนี้เป็นข้อมูลโค้ดง่ายๆที่อ่านไฟล์ไบนารีและส่งออกซอร์สโค้ด Go ที่ประกาศตัวแปรประเภท[]byteที่จะเริ่มต้นด้วยเนื้อหาที่แน่นอนของไฟล์:

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

ตัวอย่างผลลัพธ์หากไฟล์มีไบต์ตั้งแต่ 0 ถึง 16 (ลองใช้บนGo Playground ):

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

จัดเก็บเป็นฐาน 64 string

หากไฟล์ไม่ "ใหญ่เกินไป" (รูปภาพ / ไอคอนส่วนใหญ่มีคุณสมบัติเหมาะสม) ก็มีตัวเลือกอื่น ๆ ที่ใช้งานได้เช่นกัน คุณสามารถแปลงเนื้อหาของไฟล์เป็น Base64 stringและเก็บไว้ในซอร์สโค้ดของคุณ ในการเริ่มต้นแอปพลิเคชัน ( func init()) หรือเมื่อจำเป็นคุณสามารถถอดรหัสเป็น[]byteเนื้อหาต้นฉบับได้ Go มีการสนับสนุนที่ดีสำหรับการเข้ารหัส Base64 ในencoding/base64แพ็คเกจ

การแปลงไฟล์ (ไบนารี) เป็น base64 stringนั้นทำได้ง่ายๆดังนี้:

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

เก็บสตริงผลลัพธ์ base64 ในซอร์สโค้ดของคุณเช่นเป็นไฟล์const.

การถอดรหัสเป็นเพียงการเรียกใช้ฟังก์ชันเดียว:

const imgBase64 = "<insert base64 string here>"

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

จัดเก็บตามที่ยกมา string

มีประสิทธิภาพมากขึ้นกว่าการจัดเก็บเป็น base64 แต่อาจจะนานในรหัสที่มาอยู่ในการจัดเก็บยกอักษรสตริงของข้อมูลไบนารี เราสามารถรับรูปแบบที่ยกมาของสตริงใด ๆ โดยใช้strconv.Quote()ฟังก์ชัน:

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

สำหรับข้อมูลไบนารีที่มีค่าตั้งแต่ 0 ถึง 64 นี่คือลักษณะของผลลัพธ์ (ลองใช้บน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:;<=>?"

(โปรดทราบว่าต่อstrconv.Quote()ท้ายและใส่เครื่องหมายคำพูดไว้ข้างหน้า)

คุณสามารถใช้สตริงที่ยกมานี้โดยตรงในซอร์สโค้ดของคุณตัวอย่างเช่น:

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

พร้อมใช้งานไม่จำเป็นต้องถอดรหัส การยกเลิกการอ้างสิทธิ์จะกระทำโดยคอมไพเลอร์ Go ในเวลาคอมไพล์

คุณสามารถจัดเก็บเป็นชิ้นส่วนไบต์ได้หากคุณต้องการเช่นนั้น:

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

มีวิธีใดในการผูกshไฟล์กับ go executable หรือไม่?
Kasun Siyambalapitiya

ฉันเดาว่าข้อมูลควรเป็น imgdata ในข้อมูลโค้ดแรกในส่วน "การจัดเก็บเป็นชิ้นส่วนไบต์"
ตรรกะ x 2

1
@deusexmachina คุณพูดถูกแก้ไขแล้ว รหัสบนสนามเด็กเล่นถูกต้องแล้ว
icza

2

นอกจากนี้ยังมีวิธีที่แปลกใหม่ - ฉันใช้ปลั๊กอิน mavenเพื่อสร้างโครงการ GoLang และอนุญาตให้ใช้ตัวประมวลผลล่วงหน้า JCPเพื่อฝังบล็อกไบนารีและไฟล์ข้อความลงในแหล่งที่มา ในรหัสเคสมีลักษณะเหมือนบรรทัดด้านล่าง ( และตัวอย่างบางส่วนสามารถพบได้ที่นี่ )

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

@ เป็นไปได้ไหมที่จะผูกไดเร็กทอรีที่มีshหรือปฏิบัติการเหมือนข้างบน
Kasun Siyambalapitiya

@KasunSiyambalapitiya ผูกไดเรกทอรี? ผูกshไฟล์? ไม่แน่ใจคุณหมายถึงอะไร. go-bindataหากคุณต้องการทุกอย่างในไดเรกทอรีที่จะฝังตัวนี้เป็นสิ่งที่ผมเคยทำกับ ตัวอย่างเช่นถ้าฉันใส่//go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/ไฟล์ go (ที่ไม่ได้สร้าง) go generate ./...จะเรียกใช้ go-bindata ใน dir ของแพ็คเกจโดยฝังทุกอย่างใน data subdir แต่ลบคำนำหน้า 'data /' ออก
ทำเครื่องหมาย

1

ในฐานะที่เป็นทางเลือกยอดนิยมที่go-bindataกล่าวถึงในคำตอบอื่นmjibson / escยังฝังไฟล์โดยพลการ แต่จัดการกับแผนผังไดเรกทอรีได้อย่างสะดวกโดยเฉพาะ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.