Text/Tao Gang
การจัดการดาวน์โหลดไฟล์ขนาดใหญ่ในเว็บแอปพลิเคชันเป็นเรื่องยากเสมอมา ดังนั้นสำหรับไซต์ส่วนใหญ่ วิบัติจะเกิดขึ้นกับผู้ใช้หากการดาวน์โหลดถูกขัดจังหวะ แต่เราไม่จำเป็นต้องทำเช่นนั้นในตอนนี้ เนื่องจากคุณสามารถทำให้แอปพลิเคชัน ASP.NET ของคุณสามารถรองรับการดาวน์โหลดไฟล์ขนาดใหญ่แบบดำเนินการต่อได้ (ต่อเนื่อง) เมื่อใช้วิธีการที่ให้ไว้ในบทความนี้ คุณสามารถติดตามกระบวนการดาวน์โหลด เพื่อให้คุณสามารถจัดการไฟล์ที่สร้างขึ้นแบบไดนามิก และดำเนินการนี้ได้โดยไม่ต้องใช้ไลบรารีลิงก์ไดนามิก ISAPI แบบเก่าและโค้ด C++ ที่ไม่มีการจัดการ
การให้บริการลูกค้าดาวน์โหลดไฟล์จากอินเทอร์เน็ตง่ายที่สุดใช่ไหม? เพียงคัดลอกไฟล์ที่ดาวน์โหลดได้ลงในไดเร็กทอรีเว็บแอปพลิเคชันของคุณ เผยแพร่ลิงก์ และปล่อยให้ IIS จัดการงานที่เกี่ยวข้องทั้งหมด อย่างไรก็ตาม การให้บริการไฟล์ไม่ควรเกินความยุ่งยาก คุณไม่ต้องการให้คนทั้งโลกเข้าถึงข้อมูลของคุณ คุณไม่ต้องการให้เซิร์ฟเวอร์ของคุณอุดตันด้วยไฟล์คงที่หลายร้อยไฟล์ คุณไม่ต้องการ ไม่ต้องการดาวน์โหลดไฟล์ชั่วคราวด้วยซ้ำ - ไฟล์เหล่านี้ถูกสร้างขึ้นในช่วงเวลาว่างหลังจากที่ไคลเอนต์เริ่มดาวน์โหลดเท่านั้น
ขออภัย ไม่สามารถบรรลุผลเหล่านี้ได้โดยใช้การตอบสนองเริ่มต้นของ IIS ต่อคำขอดาวน์โหลด ดังนั้น โดยทั่วไป เพื่อให้สามารถควบคุมกระบวนการดาวน์โหลดได้ นักพัฒนาจำเป็นต้องลิงก์ไปยังหน้า .aspx แบบกำหนดเอง ซึ่งพวกเขาจะตรวจสอบข้อมูลประจำตัวของผู้ใช้ สร้างไฟล์ที่ดาวน์โหลดได้ และใช้โค้ดต่อไปนี้เพื่อส่งไฟล์ไปยังไคลเอนต์:
Response.WriteFile
Response.End()
และนี่คือจุดที่ปัญหาที่แท้จริงเกิดขึ้น
มีปัญหาอะไร?
วิธีการ WriteFile ดูสมบูรณ์แบบ โดยทำให้ข้อมูลไบนารี่ของไฟล์ไหลไปยังไคลเอนต์ แต่สิ่งที่เราไม่รู้จนกระทั่งเมื่อไม่นานมานี้ก็คือเมธอด WriteFile นั้นเป็นหน่วยความจำที่ฉาวโฉ่ โดยโหลดไฟล์ทั้งหมดลงใน RAM ของเซิร์ฟเวอร์เพื่อให้บริการ (อันที่จริงแล้ว มันใช้ขนาดใหญ่กว่าไฟล์ถึงสองเท่า) สำหรับไฟล์ขนาดใหญ่ สิ่งนี้อาจทำให้เกิดปัญหาหน่วยความจำบริการและอาจทำซ้ำกระบวนการ ASP.NET แต่ในเดือนมิถุนายน พ.ศ. 2547 Microsoft ได้เปิดตัวโปรแกรมแก้ไขที่แก้ไขปัญหาได้ โปรแกรมปรับปรุงนี้เป็นส่วนหนึ่งของ .NET Framework 1.1 Service Pack (SP1) แล้ว
โปรแกรมปรับปรุงนี้จะแนะนำวิธี TransmitFile ซึ่งจะอ่านไฟล์ดิสก์ลงในบัฟเฟอร์หน่วยความจำขนาดเล็ก จากนั้นจึงเริ่มถ่ายโอนไฟล์ แม้ว่าโซลูชันนี้จะช่วยแก้ปัญหาหน่วยความจำและลูป แต่ก็ยังไม่เป็นที่น่าพอใจ คุณไม่สามารถควบคุมวงจรการตอบสนองได้ คุณไม่มีทางรู้ได้ว่าการดาวน์โหลดเสร็จสมบูรณ์อย่างถูกต้องหรือไม่ คุณไม่มีทางรู้ได้ว่าการดาวน์โหลดถูกขัดจังหวะหรือไม่ และ (หากคุณสร้างไฟล์ชั่วคราว) คุณไม่มีทางรู้ได้เลยว่าคุณควรลบไฟล์นั้นเมื่อใดและเมื่อใด ที่แย่ไปกว่านั้นคือ หากการดาวน์โหลดล้มเหลว เมธอด TransmitFile จะเริ่มดาวน์โหลดจากจุดเริ่มต้นของไฟล์ที่ไคลเอนต์พยายามครั้งถัดไป
วิธีแก้ปัญหาหนึ่งที่เป็นไปได้คือการนำ Background Intelligent Transfer Service (BITS) ไปใช้นั้นไม่สามารถทำได้สำหรับไซต์ส่วนใหญ่ เนื่องจากจะขัดต่อวัตถุประสงค์ในการรักษาเบราว์เซอร์ไคลเอ็นต์และระบบปฏิบัติการอย่างเป็นอิสระ
พื้นฐานสำหรับการแก้ปัญหาที่น่าพอใจมาจากความพยายามครั้งแรกของ Microsoft ในการแก้ปัญหาความสับสนของหน่วยความจำที่เกิดจาก WriteFile (ดูบทความฐานความรู้ 812406) บทความดังกล่าวแสดงให้เห็นถึงกระบวนการดาวน์โหลดข้อมูลก้อนอัจฉริยะที่อ่านข้อมูลจากสตรีมไฟล์ ก่อนที่เซิร์ฟเวอร์จะส่งก้อนไบต์ไปยังไคลเอนต์ เซิร์ฟเวอร์จะใช้คุณสมบัติ Response.IsClientConnected เพื่อตรวจสอบว่าไคลเอนต์ยังคงเชื่อมต่ออยู่หรือไม่ หากการเชื่อมต่อยังคงเปิดอยู่ การเชื่อมต่อจะส่งไบต์สตรีมต่อไป ไม่เช่นนั้นจะหยุดเพื่อป้องกันไม่ให้เซิร์ฟเวอร์ส่งข้อมูลที่ไม่จำเป็น
นี่คือแนวทางที่เราใช้ โดยเฉพาะอย่างยิ่งเมื่อดาวน์โหลดไฟล์ชั่วคราว ในกรณีที่ IsClientConnected ส่งคืนค่า False คุณทราบว่ากระบวนการดาวน์โหลดถูกขัดจังหวะ และคุณควรบันทึกไฟล์ มิฉะนั้น เมื่อกระบวนการเสร็จสมบูรณ์ คุณจะลบไฟล์ชั่วคราว นอกจากนี้ เพื่อให้การดาวน์โหลดถูกขัดจังหวะต่อ สิ่งที่คุณต้องทำคือเริ่มการดาวน์โหลดจากจุดที่การเชื่อมต่อไคลเอนต์ล้มเหลวในระหว่างการดาวน์โหลดครั้งล่าสุด
การสนับสนุนโปรโตคอล HTTP และข้อมูลส่วนหัว (ส่วนหัว)
การสนับสนุนโปรโตคอล HTTP สามารถใช้เพื่อจัดการข้อมูลส่วนหัวสำหรับการดาวน์โหลดที่ถูกขัดจังหวะ การใช้ส่วนหัว HTTP จำนวนเล็กน้อย คุณสามารถปรับปรุงกระบวนการดาวน์โหลดของคุณให้สอดคล้องกับข้อกำหนดเฉพาะของโปรโตคอล HTTP ได้อย่างสมบูรณ์ ข้อมูลจำเพาะนี้พร้อมด้วยช่วงจะให้ข้อมูลทั้งหมดที่จำเป็นในการดำเนินการดาวน์โหลดต่อที่ถูกขัดจังหวะ
นี่คือวิธีการทำงาน ขั้นแรก หากเซิร์ฟเวอร์รองรับการดาวน์โหลดที่กลับมาทำงานต่อฝั่งไคลเอ็นต์ได้ เซิร์ฟเวอร์จะส่งส่วนหัว Accept-Ranges ในการตอบกลับเริ่มต้น เซิร์ฟเวอร์ยังส่งส่วนหัวแท็กเอนทิตี (ETag) ซึ่งมีสตริงการระบุที่ไม่ซ้ำกัน
รหัสด้านล่างแสดงส่วนหัวบางส่วนที่ IIS ส่งไปยังไคลเอนต์เพื่อตอบสนองต่อคำขอดาวน์โหลดครั้งแรก ซึ่งส่งรายละเอียดของไฟล์ที่ร้องขอไปยังไคลเอนต์
HTTP/1.1 200 ตกลง
การเชื่อมต่อ: ปิด
วันที่: อังคารที่ 19 ต.ค. 2547 เวลา 15:11:23 น. GMT
ยอมรับ-ช่วง: ไบต์
แก้ไขล่าสุด: วันอาทิตย์ที่ 26 กันยายน 2004 เวลา 15:52:45 น. GMT
แท็ก: "47febb2cfd76c41:2062"
การควบคุมแคช: ส่วนตัว
ประเภทเนื้อหา: application/x-zip-compressed
ความยาวเนื้อหา: 2844011
หลังจากได้รับข้อมูลส่วนหัวเหล่านี้แล้ว หากการดาวน์โหลดถูกขัดจังหวะ เบราว์เซอร์ IE จะส่งค่า Etag และข้อมูลส่วนหัวของ Range กลับไปยังเซิร์ฟเวอร์ในคำขอดาวน์โหลดครั้งต่อไป โค้ดด้านล่างแสดงส่วนหัวบางส่วนที่ IE ส่งไปยังเซิร์ฟเวอร์เมื่อพยายามดำเนินการดาวน์โหลดต่อที่ถูกขัดจังหวะ
รับ
ส่วนหัวเหล่านี้ระบุว่า IE แคชแท็กเอนทิตีที่ IIS จัดเตรียมไว้ให้และส่งกลับไปยังเซิร์ฟเวอร์ในส่วนหัว If-Range นี่เป็นวิธีเพื่อให้แน่ใจว่าการดาวน์โหลดจะดำเนินการต่อจากไฟล์เดียวกันทุกประการ ขออภัย ไม่ใช่ว่าเบราว์เซอร์ทั้งหมดจะทำงานในลักษณะเดียวกัน ส่วนหัว HTTP อื่นๆ ที่ไคลเอ็นต์ส่งเพื่อตรวจสอบไฟล์อาจเป็น If-Match, If-Unmodified-Since หรือ Unless-Modified-Since แน่นอนว่าข้อกำหนดไม่ได้ชัดเจนว่าซอฟต์แวร์ไคลเอนต์ส่วนหัวใดที่ต้องรองรับ หรือต้องใช้ส่วนหัวใด ดังนั้นไคลเอ็นต์บางตัวจึงไม่ใช้ข้อมูลส่วนหัวเลย ในขณะที่ IE ใช้เฉพาะ If-Range และ Unless-Modified-Since คุณควรตรวจสอบข้อมูลนี้ด้วยรหัส เมื่อใช้วิธีการนี้ แอปพลิเคชันของคุณจะสามารถปฏิบัติตามข้อกำหนด HTTP ในระดับที่สูงมาก และทำงานกับเบราว์เซอร์ที่หลากหลายได้ ส่วนหัวของ Range ระบุช่วงไบต์ที่ร้องขอ - ในกรณีนี้คือจุดเริ่มต้นที่เซิร์ฟเวอร์ควรดำเนินการสตรีมไฟล์ต่อ
เมื่อ IIS ได้รับการร้องขอประเภทการดาวน์โหลดประวัติต่อ IIS จะส่งการตอบกลับที่มีข้อมูลส่วนหัวต่อไปนี้:
HTTP/1.1 206 เนื้อหาบางส่วน
เนื้อหา-ช่วง: ไบต์ 822603-2844010/2844011
ยอมรับ-ช่วง: ไบต์
แก้ไขล่าสุด: วันอาทิตย์ที่ 26 กันยายน 2004 เวลา 15:52:45 น. GMT
แท็ก: "47febb2cfd76c41:2062"
การควบคุมแคช: ส่วนตัว
ประเภทเนื้อหา: application/x-zip-compressed
ความยาวของเนื้อหา: 2021408
โปรดทราบว่าโค้ดด้านบนมีการตอบสนอง HTTP ที่แตกต่างกันเล็กน้อยจากคำขอดาวน์โหลดดั้งเดิม - คำขอให้ดาวน์โหลดต่อคือ 206 ในขณะที่คำขอดาวน์โหลดดั้งเดิมคือ 200 สิ่งนี้บ่งชี้ว่าสิ่งที่ถูกส่งผ่านสายนั้นเป็นไฟล์บางส่วน คราวนี้ส่วนหัวของ Content-Range จะระบุจำนวนและตำแหน่งของไบต์ที่ถูกส่งผ่านอย่างแน่นอน
IE พิถีพิถันมากเกี่ยวกับข้อมูลส่วนหัวเหล่านี้ หากการตอบสนองเริ่มต้นไม่มีข้อมูลส่วนหัวของ Etag IE จะไม่พยายามดาวน์โหลดต่อ ไคลเอนต์อื่นๆ ที่ฉันได้ทดสอบไม่ได้ใช้ส่วนหัว ETag เพียงแต่ใช้ชื่อไฟล์ ขอบเขตคำขอ และใช้ส่วนหัว Last-Modified หากพวกเขากำลังพยายามตรวจสอบความถูกต้องของไฟล์
ความเข้าใจเชิงลึกเกี่ยวกับโปรโตคอล HTTP
ข้อมูลส่วนหัวที่แสดงในส่วนก่อนหน้านี้เพียงพอที่จะทำให้โซลูชันสำหรับการดาวน์โหลดต่อทำงานได้ แต่ไม่ครอบคลุมข้อกำหนด HTTP ทั้งหมด
ส่วนหัวของ Range สามารถขอได้หลายช่วงในคำขอเดียว ซึ่งเป็นคุณลักษณะที่เรียกว่า "ช่วงที่มีหลายส่วน" เพื่อไม่ให้สับสนกับการดาวน์โหลดแบบแบ่งส่วน เครื่องมือดาวน์โหลดเกือบทั้งหมดใช้การดาวน์โหลดแบบแบ่งส่วนเพื่อเพิ่มความเร็วในการดาวน์โหลด เครื่องมือเหล่านี้อ้างว่าเพิ่มความเร็วในการดาวน์โหลดโดยการเปิดการเชื่อมต่อพร้อมกันตั้งแต่ 2 รายการขึ้นไป โดยแต่ละการเชื่อมต่อจะร้องขอช่วงไฟล์ที่แตกต่างกัน
แนวคิดของช่วงที่มีหลายส่วนไม่ได้เปิดการเชื่อมต่อหลายรายการ แต่อนุญาตให้ซอฟต์แวร์ไคลเอ็นต์ขอไฟล์สิบไบต์แรกและสิบไบต์สุดท้ายในรอบคำขอ/ตอบกลับเดียว
พูดตามตรงฉันไม่เคยพบซอฟต์แวร์สักชิ้นที่ใช้ฟีเจอร์นี้เลย แต่ฉันปฏิเสธที่จะเขียน "ไม่รองรับ HTTP โดยสมบูรณ์" ในการประกาศโค้ด การละเว้นคุณลักษณะนี้จะถือเป็นการละเมิดกฎของเมอร์ฟี่อย่างแน่นอน ไม่ว่าช่วงที่มีหลายส่วนจะถูกนำมาใช้ในการส่งอีเมลเพื่อแยกข้อมูลส่วนหัว ข้อความธรรมดา และไฟล์แนบ
โค้ดตัวอย่าง
เราทราบวิธีที่ไคลเอ็นต์และเซิร์ฟเวอร์แลกเปลี่ยนข้อมูลส่วนหัวเพื่อให้แน่ใจว่าการดาวน์โหลดกลับมาทำงานต่อได้ เมื่อรวมความรู้นี้เข้ากับแนวคิดของการบล็อกไฟล์ คุณสามารถเพิ่มความสามารถในการจัดการการดาวน์โหลดที่เชื่อถือได้ให้กับแอปพลิเคชัน ASP.NET ของคุณ
วิธีควบคุมกระบวนการดาวน์โหลดคือการสกัดกั้นคำขอดาวน์โหลดจากไคลเอนต์ อ่านข้อมูลส่วนหัว และตอบสนองอย่างเหมาะสม ก่อน .NET คุณต้องเขียนแอปพลิเคชัน ISAPI (Internet Server API) เพื่อใช้ฟังก์ชันนี้ แต่ส่วนประกอบ .NET Framework มีอินเทอร์เฟซ IHttpHandler ซึ่งเมื่อนำไปใช้ในคลาส จะอนุญาตให้คุณดำเนินการนี้โดยใช้เพียงการสกัดกั้นโค้ด .NET และดำเนินการตามคำขอ ซึ่งหมายความว่าแอปพลิเคชันของคุณสามารถควบคุมและตอบสนองต่อกระบวนการดาวน์โหลดได้อย่างสมบูรณ์ และไม่เกี่ยวข้องหรือใช้ฟังก์ชันอัตโนมัติของ IIS
รหัสตัวอย่างรวมถึงคลาส HttpHandler แบบกำหนดเอง (ZIPHandler) ในไฟล์ HttpHandler.vb ZipHandler ใช้อินเทอร์เฟซ IhttpHandler และจัดการคำขอสำหรับไฟล์ .zip ทั้งหมด
เพื่อทดสอบโค้ดตัวอย่าง คุณต้องสร้างไดเร็กทอรีเสมือนใหม่ใน IIS และคัดลอกไฟล์ต้นฉบับที่นั่น สร้างไฟล์ชื่อ download.zip ในไดเร็กทอรีนี้ (โปรดทราบว่า IIS และ ASP.NET ไม่สามารถรองรับการดาวน์โหลดที่มีขนาดใหญ่กว่า 2GB ได้ ดังนั้น ตรวจสอบให้แน่ใจว่าไฟล์ของคุณไม่เกินขีดจำกัดนี้) กำหนดค่าไดเรกทอรีเสมือน IIS ของคุณเพื่อแมปส่วนขยาย .zip ผ่าน aspnet_isapi.dll
คลาส HttpHandler: หลังจากที่ ZIPHandler
แมปส่วนขยาย .zip ใน ASP.NET ทุกครั้งที่ไคลเอนต์ร้องขอไฟล์ .zip จากเซิร์ฟเวอร์ IIS จะเรียกใช้เมธอด ProcessRequest ของคลาส ZipHandler (ดูโค้ดดาวน์โหลด)
ขั้นแรกเมธอด ProcessRequest จะสร้างอินสแตนซ์ของคลาส FileInformation แบบกำหนดเอง (ดูโค้ดดาวน์โหลด) ซึ่งจะสรุปสถานะของการดาวน์โหลด (เช่น กำลังดำเนินการ ถูกขัดจังหวะ ฯลฯ) ตัวอย่างฮาร์ดโค้ดเส้นทางไปยังไฟล์ตัวอย่าง download.zip ลงในโค้ด หากคุณใช้รหัสนี้กับแอปพลิเคชันของคุณเอง คุณจะต้องแก้ไขรหัสเพื่อเปิดไฟล์ที่ร้องขอ
' ใช้ objRequest เพื่อตรวจสอบว่าไฟล์ใดถูกร้องขอ และใช้ไฟล์นั้นเพื่อเปิด objFile
' ตัวอย่างเช่น objFile = New Download.FileInformation(<ชื่อไฟล์เต็ม>)
objFile = ดาวน์โหลดใหม่ FileInformation ( _
objContext.Server.MapPath("~/download.zip"))
จากนั้นโปรแกรมจะดำเนินการตามคำขอโดยใช้ส่วนหัว HTTP ที่อธิบายไว้ (หากระบุส่วนหัวไว้ในคำขอ) ฉันอธิษฐานเป็นครั้งแรกภายใต้ดวงอาทิตย์ หากการตรวจสอบความถูกต้องล้มเหลว การตอบสนองจะสิ้นสุดลงทันทีและส่งค่า StatusCode ที่เหมาะสม
หากไม่ใช่ objRequest.HttpMethod.Equals(HTTP_METHOD_GET) หรือไม่
objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD) จากนั้น
' ปัจจุบันรองรับเฉพาะวิธี GET และ HEAD เท่านั้น objResponse.StatusCode = 501 ' ไม่ถูกดำเนินการ
ElseIf ไม่ใช่ objFile.Exists แล้ว
' ไม่พบไฟล์ที่ร้องขอ objResponse.StatusCode = 404 ' ไม่พบ
ElseIf objFile.Length > Int32.MaxValue จากนั้น
'ไฟล์มีขนาดใหญ่เกินไป objResponse.StatusCode = 413 'เอนทิตีคำขอมีขนาดใหญ่เกินไป
อื่นถ้าไม่ ParseRequestHeaderRange (objRequest, alRequestedRangesBegin, alRequestedRangesend, _
objFile.Length, bIsRangeRequest) จากนั้น
' คำขอ Range มีเอนทิตีที่ไม่มีประโยชน์ objResponse.StatusCode = 400 ' คำขอที่ไม่มีประโยชน์
มิฉะนั้นหากไม่ได้ CheckIfModifiedSince(objRequest,objFile) จากนั้น
'เอนทิตียังไม่ได้รับการแก้ไข objResponse.StatusCode = 304 'เอนทิตียังไม่ได้รับการแก้ไข
มิฉะนั้นหากไม่ CheckIfUnmodifiedSince(objRequest,objFile) จากนั้น
' เอนทิตีได้รับการแก้ไขตั้งแต่วันที่ร้องขอล่าสุด objResponse.StatusCode = 412 ' การประมวลผลล่วงหน้าล้มเหลว
ElseIf Not CheckIfMatch(objRequest, objFile) จากนั้น
' เอนทิตีไม่ตรงกับคำขอ objResponse.StatusCode = 412 ' การประมวลผลล่วงหน้าล้มเหลว
อย่างอื่นถ้าไม่ CheckIfNoneMatch(objRequest, objResponse,objFile) จากนั้น
' เอนทิตีตรงกับคำขอที่ไม่ตรงกัน
'รหัสตอบกลับอยู่ในฟังก์ชัน CheckIfNoneMatch
อื่น
'ตรวจสอบเบื้องต้นสำเร็จ'
ฟังก์ชัน ParseRequestHeaderRange ในการตรวจสอบเบื้องต้น (ดูโค้ดดาวน์โหลด) จะตรวจสอบว่าไคลเอ็นต์ร้องขอช่วงไฟล์หรือไม่ (ซึ่งหมายถึงการดาวน์โหลดบางส่วน) หากช่วงที่ร้องขอไม่ถูกต้อง (ช่วงที่ไม่ถูกต้องคือค่าช่วงที่เกินขนาดไฟล์หรือมีตัวเลขที่ไม่สมเหตุสมผล) วิธีการนี้จะตั้งค่า bIsRangeRequest เป็น True หากมีการร้องขอช่วง วิธีการ CheckIfRange จะตรวจสอบข้อมูลส่วนหัว IfRange
หากช่วงที่ร้องขอนั้นถูกต้อง โค้ดจะคำนวณขนาดของข้อความตอบกลับ หากไคลเอนต์ร้องขอหลายช่วง ค่าขนาดการตอบสนองจะรวมค่าความยาวส่วนหัวแบบหลายส่วนด้วย
หากไม่สามารถระบุค่าส่วนหัวที่ส่งได้ โปรแกรมจะจัดการคำขอดาวน์โหลดเป็นคำขอเริ่มต้น แทนที่จะเป็นการดาวน์โหลดบางส่วน โดยจะส่งสตรีมการดาวน์โหลดใหม่โดยเริ่มจากด้านบนของไฟล์
ถ้า bIsRangeRequest AndAlso CheckIfRange(objRequest, objFile) แล้ว
'นี่คือคำขอช่วง' หากอาร์เรย์ช่วงมีหลายเอนทิตี มันก็เป็นคำขอช่วงแบบหลายส่วนด้วย bMultipart = CBool(alRequestedRangesBegin.GetUpperBound(0)>0)
' ไปที่แต่ละช่วงเพื่อรับความยาวการตอบสนองทั้งหมด For iLoop = alRequestedRangesBegin.GetLowerBound(0) ถึง alRequestedRangesBegin.GetUpperBound(0)
'ความยาวของเนื้อหา (ในช่วงนี้)
iResponseContentLength += แปลง ToInt32 (alRequestedRangesend ( _
iLoop) - alRequestedRangesBegin(iLoop)) + 1
ถ้าbหลายส่วนแล้ว
' หากเป็นคำขอช่วงที่มีหลายส่วน ให้คำนวณความยาวของข้อมูลส่วนหัวระดับกลางที่จะส่ง iResponseContentLength += MULTIPART_BOUNDARY.Length
iResponseContentLength += objFile.ContentType.Length
iResponseContentLength += alRequestedRangesBegin(iLoop).ToString.Length
iResponseContentLength += alRequestedRangesend (iLoop) ToString.Length
iResponseContentLength += objFile.Length.ToString.Length
' 49 คือความยาวของตัวแบ่งบรรทัดและอักขระที่จำเป็นอื่นๆ ในการดาวน์โหลดแบบหลายส่วน iResponseContentLength += 49
สิ้นสุดถ้า
iLoop ถัดไป
ถ้า bMultipart แล้ว
' หากเป็นคำขอช่วงที่มีหลายส่วน
' เรายังต้องคำนวณความยาวของส่วนหัวกลางสุดท้ายที่จะถูกส่ง iResponseContentLength +=MULTIPART_BOUNDARY.Length
' 8 คือความยาวของเส้นประและขึ้นบรรทัดใหม่ iResponseContentLength += 8
อื่น
' ไม่ใช่การดาวน์โหลดแบบหลายส่วน ดังนั้นเราต้องระบุช่วงการตอบสนองของส่วนหัว HTTP เริ่มต้น objResponse.AppendHeader( HTTP_HEADER_CONTENT_RANGE, "bytes " & _
alRequestedRangesBegin (0). ToString & "-" & _
alRequestedRangesend (0) ToString & "/" & _
objFile.Length.ToString)
'สิ้นสุดถ้า
' การตอบสนองช่วง objResponse.StatusCode = 206 ' การตอบสนองบางส่วน มิฉะนั้น
' นี่ไม่ใช่คำขอขอบเขต หรือ ID เอนทิตีขอบเขตที่ร้องขอไม่ตรงกับ ID เอนทิตีปัจจุบัน
'เริ่มการดาวน์โหลดใหม่' ระบุว่าขนาดของส่วนที่เสร็จสมบูรณ์ของไฟล์เท่ากับความยาวของเนื้อหา iResponseContentLength =Convert.ToInt32(objFile.Length)
'กลับสู่สถานะปกติปกติ objResponse.StatusCode = 200
สิ้นสุดถ้า
' ถัดไป เซิร์ฟเวอร์จะต้องส่งส่วนหัวการตอบกลับที่สำคัญหลายประการ เช่น ความยาวของเนื้อหา Etag และประเภทเนื้อหาไฟล์:
' เขียนความยาวเนื้อหาลงในการตอบสนอง objResponse.AppendHeader( HTTP_HEADER_CONTENT_LENGTH,iResponseContentLength.ToString)
' เขียนวันที่แก้ไขล่าสุดลงในการตอบสนอง objResponse.AppendHeader( HTTP_HEADER_LAST_MODIFIED,objFile.LastWriteTimeUTC.ToString("r"))
' แจ้งซอฟต์แวร์ไคลเอ็นต์ว่าเรายอมรับคำขอช่วง objResponse.AppendHeader( HTTP_HEADER_ACCEPT_RANGES,HTTP_HEADER_ACCEPT_RANGES_BYTES)
' เขียนแท็กเอนทิตีของไฟล์ไปยังการตอบกลับ (อยู่ในเครื่องหมายคำพูด)
objResponse.AppendHeader(HTTP_HEADER_ENTITY_TAG, """" & objFile.EntityTag & """")
'เขียนประเภทเนื้อหาเพื่อตอบกลับถ้า bMultipart แล้ว'
'ข้อความหลายส่วนมีประเภทพิเศษนี้' ในตัวอย่างประเภท mime ที่แท้จริงของไฟล์จะถูกเขียนไปยังการตอบกลับในภายหลัง objResponse.ContentType = MULTIPART_CONTENTTYPE
อื่น
'ประเภทเนื้อหาไฟล์ที่เป็นของข้อความบางส่วน objResponse.ContentType = objFile.ContentType
สิ้นสุดถ้า
ทุกสิ่งที่คุณต้องการสำหรับการดาวน์โหลดพร้อมแล้ว และคุณสามารถเริ่มดาวน์โหลดไฟล์ได้ คุณจะใช้วัตถุ FileStream เพื่ออ่านจำนวนไบต์จากไฟล์ ตั้งค่าคุณสมบัติสถานะของอินสแตนซ์ FileInformation objFile เป็น fsDownloadInProgress ตราบใดที่ไคลเอนต์ยังคงเชื่อมต่ออยู่ เซิร์ฟเวอร์จะอ่านไบต์จำนวนมากจากไฟล์และส่งไปยังไคลเอนต์ สำหรับการดาวน์โหลดแบบหลายส่วน โค้ดนี้จะส่งข้อมูลส่วนหัวเฉพาะ หากไคลเอ็นต์ยกเลิกการเชื่อมต่อ เซิร์ฟเวอร์จะตั้งค่าสถานะไฟล์เป็น fsDownloadBroken หากเซิร์ฟเวอร์เสร็จสิ้นการส่งช่วงที่ร้องขอ เซิร์ฟเวอร์จะตั้งค่าสถานะเป็น fsDownloadFinished (ดูโค้ดดาวน์โหลด)
FileInformation Auxiliary Class
ในส่วน ZIPHandler คุณจะพบว่า FileInformation เป็นคลาสเสริมที่ห่อหุ้มข้อมูลสถานะการดาวน์โหลด (เช่น การดาวน์โหลด การขัดจังหวะ เป็นต้น)
ในการสร้างอินสแตนซ์ของ FileInformation คุณจะต้องส่งเส้นทางไปยังไฟล์ที่ร้องขอไปยังตัวสร้างคลาส:
Public Sub New(ByVal sPath As String)
m_objFile = System.IO.FileInfo ใหม่ (sPath)
End Sub
FileInformation ใช้อ็อบเจ็กต์ System.IO.FileInfo เพื่อรับข้อมูลไฟล์ ซึ่งเปิดเผยเป็นคุณสมบัติของอ็อบเจ็กต์ (เช่น มีไฟล์อยู่หรือไม่ ชื่อเต็มของไฟล์ ขนาด เป็นต้น) คลาสนี้ยังเปิดเผยการแจงนับ DownloadState ซึ่งอธิบายสถานะต่างๆ ของคำขอดาวน์โหลด:
Enum DownloadState
' ล้าง: ไม่มีขั้นตอนการดาวน์โหลด ไฟล์อาจคง fsClear = 1 ไว้
'ถูกล็อค: ไฟล์ที่สร้างขึ้นแบบไดนามิกไม่สามารถเปลี่ยนแปลงได้ fsLocked = 2
'อยู่ระหว่างดำเนินการ: ไฟล์ถูกล็อคและกำลังดาวน์โหลดอยู่ fsDownloadInProgress = 6
'ใช้งานไม่ได้: ไฟล์ถูกล็อค กำลังดำเนินการดาวน์โหลด แต่ถูกยกเลิก fsDownloadBroken = 10
' เสร็จสิ้น: ไฟล์ถูกล็อคและกระบวนการดาวน์โหลดเสร็จสิ้น fsDownloadFinished = 18
End Enum
FileInformation ยังมีค่าแอตทริบิวต์ EntityTag อีกด้วย ค่านี้เป็นค่าฮาร์ดโค้ดในโค้ดตัวอย่าง เนื่องจากโค้ดตัวอย่างใช้ไฟล์ดาวน์โหลดเพียงไฟล์เดียวเท่านั้น และไฟล์นั้นจะไม่มีการเปลี่ยนแปลง แต่สำหรับแอปพลิเคชันจริง คุณจะต้องจัดเตรียมไฟล์หลายไฟล์ แม้จะเป็นแบบไดนามิกก็ตาม ในการสร้างไฟล์ โค้ดของคุณจะต้องระบุ ค่า EntityTag ที่ไม่ซ้ำกันสำหรับแต่ละไฟล์ นอกจากนี้ ค่านี้จะต้องเปลี่ยนทุกครั้งที่มีการเปลี่ยนแปลงหรือแก้ไขไฟล์ ซึ่งช่วยให้ซอฟต์แวร์ไคลเอ็นต์สามารถตรวจสอบได้ว่าจำนวนไบต์ที่ดาวน์โหลดยังคงเป็นข้อมูลล่าสุด นี่คือส่วนหนึ่งของโค้ดตัวอย่างที่ส่งคืนค่า EntityTag แบบฮาร์ดโค้ด:
คุณสมบัติ Public ReadOnly EntityTag() As String
' EntityTag ใช้สำหรับการตอบสนองเริ่มต้น (200) ไปยังไคลเอนต์ และสำหรับการร้องขอการกู้คืนจากไคลเอนต์รับ
' สร้างสตริงเฉพาะสำหรับไฟล์
' โปรดทราบว่าตราบใดที่ไฟล์ไม่เปลี่ยนแปลง จะต้องเก็บรหัสเฉพาะไว้
' อย่างไรก็ตาม หากไฟล์มีการเปลี่ยนแปลงหรือแก้ไข โค้ดนี้จะต้องเปลี่ยน
ส่งคืน "MyExampleFileID"
จบรับ
คุณสมบัติสุดท้าย
EntityTag ที่เรียบง่ายและปลอดภัยโดยทั่วไปอาจประกอบด้วยชื่อไฟล์และวันที่ที่แก้ไขไฟล์ครั้งล่าสุด ไม่ว่าคุณจะใช้วิธีการใด คุณต้องแน่ใจว่าค่านี้ไม่ซ้ำกันอย่างแท้จริง และต้องไม่สับสนกับ EntityTag ของไฟล์อื่นๆ ฉันต้องการตั้งชื่อไฟล์ที่สร้างขึ้นในแอปพลิเคชันของฉันแบบไดนามิกตามไคลเอนต์ ลูกค้า และดัชนีรหัสไปรษณีย์ และจัดเก็บ GUID ที่ใช้เป็น EntityTag ในฐานข้อมูล
คลาส ZipFileHandler อ่านและตั้งค่าคุณสมบัติสถานะสาธารณะ หลังจากดาวน์โหลดเสร็จแล้ว จะตั้งค่าสถานะเป็น fsDownloadFinished ในเวลานี้คุณสามารถลบไฟล์ชั่วคราวได้ โดยทั่วไปคุณจะต้องเรียกใช้เมธอด Save เพื่อรักษาสถานะไว้
รัฐทรัพย์สินสาธารณะ () เป็น DownloadState
รับ
ส่งคืน m_nState
จบรับ
ตั้งค่า (ByVal nState เป็น DownloadState)
m_nState = nสถานะ
' การดำเนินการทางเลือก: คุณสามารถลบไฟล์โดยอัตโนมัติได้ในขณะนี้
' หากสถานะถูกตั้งค่าเป็นเสร็จสิ้น คุณจะไม่ต้องการไฟล์นี้อีกต่อไป
' ถ้า nState =DownloadState.fsDownloadFinished แล้ว
'ชัดเจน()
'อื่น
'บันทึก()
'สิ้นสุดถ้า
บันทึก()
จบเซต
คุณสมบัติสิ้นสุด
ZipFileHandler ควรเรียกใช้เมธอด Save ทุกครั้งที่สถานะไฟล์เปลี่ยนแปลงเพื่อบันทึกสถานะไฟล์เพื่อให้สามารถแสดงต่อผู้ใช้ในภายหลัง คุณยังใช้เพื่อบันทึก EntityTag ที่คุณสร้างขึ้นเองได้ด้วย โปรดอย่าบันทึกสถานะไฟล์และค่า EntityTag ในแอปพลิเคชัน เซสชัน หรือแคช คุณต้องบันทึกข้อมูลตลอดวงจรชีวิตของออบเจ็กต์เหล่านี้ทั้งหมด
ไพรเวทซับเซฟ()
'บันทึกสถานะการดาวน์โหลดของไฟล์ลงในฐานข้อมูลหรือไฟล์ XML
' แน่นอน หากคุณไม่ได้สร้างไฟล์แบบไดนามิก คุณไม่จำเป็นต้องบันทึกสถานะนี้
สิ้นสุดย่อย
ตามที่กล่าวไว้ก่อนหน้านี้ โค้ดตัวอย่างจะจัดการเฉพาะไฟล์ที่มีอยู่ (download.zip) แต่คุณสามารถปรับปรุงโปรแกรมนี้เพิ่มเติมเพื่อสร้างไฟล์ที่ร้องขอได้ตามต้องการ
เมื่อทดสอบโค้ดตัวอย่าง ระบบภายในเครื่องหรือ LAN ของคุณอาจเร็วเกินไปที่จะขัดขวางกระบวนการดาวน์โหลด ดังนั้นฉันขอแนะนำให้คุณใช้การเชื่อมต่อ LAN ที่ช้า (การลดแบนด์วิดท์ของไซต์ใน IIS เป็นวิธีการจำลอง) หรือวางเซิร์ฟเวอร์ไว้บน อินเทอร์เน็ต
การดาวน์โหลดไฟล์บนไคลเอนต์ยังคงเป็นปัญหา เว็บแคชเซิร์ฟเวอร์ที่ไม่ถูกต้องหรือกำหนดค่าไม่ถูกต้องซึ่งดำเนินการโดย ISP อาจทำให้การดาวน์โหลดไฟล์ขนาดใหญ่ล้มเหลว รวมถึงประสิทธิภาพการดาวน์โหลดที่ไม่ดีหรือการยุติเซสชันก่อนกำหนด หากขนาดไฟล์เกิน 255MB คุณควรสนับสนุนให้ลูกค้าใช้ซอฟต์แวร์การจัดการการดาวน์โหลดของบุคคลที่สาม แม้ว่าเบราว์เซอร์ล่าสุดบางรุ่นจะมีตัวจัดการการดาวน์โหลดพื้นฐานในตัวก็ตาม
หากคุณต้องการขยายโค้ดตัวอย่างเพิ่มเติม การพิจารณาข้อกำหนดเฉพาะของ HTTP อาจเป็นประโยชน์ คุณสามารถสร้างการตรวจสอบ MD5 สำหรับการดาวน์โหลด โดยเพิ่มโดยใช้ส่วนหัว Content-MD5 เพื่อให้มีวิธีตรวจสอบความสมบูรณ์ของไฟล์ที่ดาวน์โหลด โค้ดตัวอย่างไม่เกี่ยวข้องกับวิธี HTTP อื่น ๆ ยกเว้น GET และ HEAD