REST API - การประมวลผลไฟล์ (เช่นรูปภาพ) - แนวทางปฏิบัติที่ดีที่สุด


198

เรากำลังพัฒนาเซิร์ฟเวอร์ด้วย REST API ซึ่งยอมรับและตอบกลับด้วย JSON ปัญหาคือถ้าคุณต้องการอัปโหลดภาพจากลูกค้าไปยังเซิร์ฟเวอร์

หมายเหตุ: และฉันกำลังพูดถึงกรณีใช้งานที่เอนทิตี (ผู้ใช้) สามารถมีหลายไฟล์ (carPhoto, licensePhoto) และยังมีคุณสมบัติอื่น ๆ (ชื่ออีเมล ... ) แต่เมื่อคุณสร้างผู้ใช้ใหม่คุณไม่ต้อง ไม่ต้องส่งภาพเหล่านี้พวกเขาจะถูกเพิ่มหลังจากกระบวนการลงทะเบียน


แนวทางแก้ไขที่ฉันทราบ แต่แต่ละข้อมีข้อบกพร่อง

1. ใช้ multipart / form-data แทน JSON

ดี : คำขอ POST และ PUT นั้นสงบที่สุดเท่าที่จะเป็นไปได้พวกเขาสามารถมีอินพุตข้อความพร้อมไฟล์ได้

ข้อเสีย : มันไม่ใช่ JSON อีกต่อไปซึ่งง่ายต่อการทดสอบการดีบักและอื่น ๆ เปรียบเทียบกับหลายส่วน / แบบฟอร์มข้อมูล

2. อนุญาตให้อัปเดตไฟล์แยกต่างหาก

คำขอ POST สำหรับการสร้างผู้ใช้ใหม่ไม่อนุญาตให้เพิ่มรูปภาพ (ซึ่งเป็นสิ่งที่เราใช้ในกรณีที่ฉันพูดตอนเริ่มต้น) การอัปโหลดรูปภาพทำได้โดยการร้องขอ PUT เป็นหลายส่วน / แบบฟอร์มข้อมูลตัวอย่าง / users / 4 / carPhoto

ดี : ทุกอย่าง (ยกเว้นการอัปโหลดไฟล์เอง) ยังคงอยู่ใน JSON มันเป็นเรื่องง่ายในการทดสอบและแก้ไขข้อบกพร่อง (คุณสามารถบันทึกคำขอ JSON ทั้งหมดโดยไม่ต้องกลัวความยาว)

ข้อเสีย : มันไม่ง่ายคุณไม่สามารถ POST หรือ PUT ตัวแปรทั้งหมดของเอนทิตีในครั้งเดียวและที่อยู่นี้/users/4/carPhotoอาจถือได้ว่าเป็นคอลเลกชัน (กรณีการใช้งานมาตรฐานสำหรับ REST API จะมีลักษณะเช่นนี้/users/4/shipments) โดยปกติคุณไม่สามารถ (และไม่ต้องการ) รับ / วางเอนทิตีแต่ละตัวแปรของเอนทิตีเช่นผู้ใช้ / 4 / ชื่อ คุณสามารถรับชื่อด้วย GET และเปลี่ยนเป็น PUT ได้ที่ users / 4 หากมีบางสิ่งบางอย่างหลังจากรหัสมันมักจะเป็นคอลเลกชันอื่นเช่นผู้ใช้ / 4 / ความคิดเห็น

3. ใช้ Base64

ส่งเป็น JSON แต่เข้ารหัสไฟล์ด้วย Base64

ดี : เช่นเดียวกับวิธีแก้ปัญหาแรกเป็นบริการที่สงบเงียบที่สุด

ข้อด้อย : อีกครั้งการทดสอบและการดีบักจะแย่กว่ามาก (ร่างกายสามารถมีเมกะไบต์ของข้อมูล) มีการเพิ่มขนาดและเวลาในการประมวลผลทั้งลูกค้าและเซิร์ฟเวอร์


ฉันต้องการใช้โซลูชันไม่มี 2 แต่มันมีข้อเสีย ... ทุกคนสามารถให้ข้อมูลเชิงลึกเกี่ยวกับโซลูชันที่ดีที่สุดได้อย่างไร

เป้าหมายของฉันคือการให้บริการที่สงบด้วยมาตรฐานรวมมากที่สุดในขณะที่ฉันต้องการให้มันง่ายที่สุด


คุณอาจพบว่ามีประโยชน์: stackoverflow.com/questions/4083702/…
Markon

5
ฉันรู้ว่าหัวข้อนี้เก่า แต่เราเพิ่งพบปัญหานี้เมื่อเร็ว ๆ นี้ วิธีที่ดีที่สุดที่เรามีคล้ายกับหมายเลข 2 ของคุณเราอัปโหลดไฟล์ไปยัง API แล้วแนบไฟล์เหล่านี้ในรูปแบบ ด้วยสถานการณ์นี้คุณสามารถสร้างภาพที่อัปโหลดก่อนหลังหรือที่หน้าเดียวกับแบบฟอร์มไม่สำคัญ การอภิปรายที่ดี!
Tiago Matos

2
@ TiagoMatos - ใช่แน่นอนฉันอธิบายไว้ในคำตอบเดียวซึ่งฉันเพิ่งได้รับ
libik

6
ขอบคุณที่ถามคำถามนี้
Zuhayer Tahir

1
"เช่นกันที่อยู่ / ผู้ใช้ / 4 / carPhoto นี้ถือได้ว่าเป็นคอลเล็กชัน" - ไม่เหมือนคอลเลกชันและไม่จำเป็นต้องได้รับการพิจารณาว่าเป็นหนึ่ง เป็นเรื่องปกติที่จะมีความสัมพันธ์กับทรัพยากรที่ไม่ใช่แหล่งรวบรวม แต่เป็นทรัพยากรเดียว
B12Toaster

คำตอบ:


156

OP ที่นี่ (ฉันตอบคำถามนี้หลังจากผ่านไปสองปีโพสต์ที่สร้างโดย Daniel Cerecedo นั้นไม่เลวในเวลาเดียว แต่บริการบนเว็บกำลังพัฒนาอย่างรวดเร็ว)

หลังจากการพัฒนาซอฟต์แวร์เต็มเวลาสามปี (โดยเน้นไปที่สถาปัตยกรรมซอฟต์แวร์การจัดการโครงการและสถาปัตยกรรม microservice) ฉันเลือกวิธีที่สองแน่นอน (แต่มีจุดสิ้นสุดทั่วไปหนึ่งจุด) เป็นวิธีที่ดีที่สุด

หากคุณมีจุดปลายพิเศษสำหรับรูปภาพมันจะให้พลังมากกว่าการจัดการภาพเหล่านั้น

เรามี REST API (Node.js) ที่เหมือนกันสำหรับทั้ง - แอพมือถือ (iOS / Android) และส่วนหน้า (โดยใช้ React) นี่คือ 2017 ดังนั้นคุณไม่ต้องการจัดเก็บภาพในเครื่องคุณต้องการอัปโหลดไปยังที่เก็บข้อมูลบนคลาวด์ (Google Cloud, s3, cloudinary, ... ) ดังนั้นคุณจึงต้องการการจัดการทั่วไป

โฟลว์ทั่วไปของเราคือทันทีที่คุณเลือกรูปภาพมันจะเริ่มอัปโหลดบนพื้นหลัง (ปกติจะเป็น POST on / images endpoint) ส่งคืน ID ให้คุณหลังจากอัปโหลด นี่เป็นมิตรกับผู้ใช้จริง ๆ เนื่องจากผู้ใช้เลือกรูปภาพและโดยทั่วไปแล้วจะดำเนินการกับฟิลด์อื่น ๆ (เช่นที่อยู่ชื่อ ... ) ดังนั้นเมื่อเขากดปุ่ม "ส่ง" รูปภาพมักจะอัปโหลดแล้ว เขาไม่รอและดูหน้าจอว่า "กำลังอัปโหลด ... "

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

นอกจากนี้หากข้อมูลไม่เป็นส่วนตัวคุณจะส่งกลับไปที่แอพ / ส่วนหน้าเพียง URL และดาวน์โหลดจากที่เก็บข้อมูลบนคลาวด์โดยตรงซึ่งเป็นการประหยัดแบนด์วิดท์และเวลาในการประมวลผลสำหรับเซิร์ฟเวอร์ของคุณ ในแอพที่ใหญ่กว่าของเรามีการดาวน์โหลดเทราไบต์จำนวนมากทุกเดือนคุณไม่ต้องการจัดการกับเซิร์ฟเวอร์ REST API โดยตรงซึ่งมุ่งเน้นไปที่การทำงานของ CRUD คุณต้องการจัดการที่เดียว (Imageserver ของเราที่มีแคช ฯลฯ ) หรือให้บริการคลาวด์จัดการกับมันทั้งหมด


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

มีหลายวิธีในการจัดการกับสิ่งนี้ สิ่งที่ง่ายที่สุดคือ "ฉันไม่สนใจ" ซึ่งเป็นสิ่งที่เกี่ยวข้องถ้าสิ่งนี้เกิดขึ้นไม่บ่อยนักหรือคุณต้องการเก็บทุกภาพที่ผู้ใช้ส่งมาให้คุณ (ไม่ว่าจะด้วยเหตุผลใดก็ตาม) และคุณไม่ต้องการ การลบ

อีกอันหนึ่งก็ง่ายเช่นกัน - คุณมี CRON และเช่นทุกสัปดาห์และคุณลบภาพที่ไม่ได้กำหนดทั้งหมดที่เก่ากว่าหนึ่งสัปดาห์


จะเกิดอะไรขึ้นหาก [ทันทีที่คุณเลือกภาพมันจะเริ่มอัปโหลดบนพื้นหลัง (ปกติจะเป็นจุดสิ้นสุด POST / / ภาพ) ส่งคืน ID ให้คุณหลังจากอัปโหลด] เมื่อคำขอล้มเหลวเนื่องจากการเชื่อมต่ออินเทอร์เน็ต คุณจะแจ้งให้ผู้ใช้ในขณะที่พวกเขาดำเนินการกับสาขาอื่น ๆ (เช่นที่อยู่ชื่อ ... )? ฉันพนันว่าคุณจะยังคงรอจนกว่าผู้ใช้กดปุ่ม "ส่ง" และลองคำขอของคุณอีกครั้งทำให้พวกเขารอในขณะที่ดูหน้าจอว่า "กำลังอัปโหลด ... "
Adromil Balais

5
@AdromilBalais - RESTful API ไม่มีสถานะดังนั้นจึงไม่ทำอะไรเลย (เซิร์ฟเวอร์ไม่ได้ติดตามสถานะของผู้บริโภค) ผู้บริโภคในการให้บริการ (เช่นหน้าเว็บหรืออุปกรณ์มือถือ) เป็นผู้รับผิดชอบในการจัดการคำขอที่ล้มเหลวดังนั้นผู้บริโภคจะต้องตัดสินใจว่าจะเรียกคำขอเดียวกันทันทีหลังจากที่ล้มเหลวหรือสิ่งที่ต้องทำ (เช่นแสดง "การอัพโหลดภาพล้มเหลว - ต้องการลองอีกครั้ง ")
libik

2
คำตอบที่ให้ข้อมูลและ enlightening มาก ขอบคุณสำหรับคำตอบ.
Zuhayer Tahir

นี่ไม่ได้เป็นการแก้ปัญหาเบื้องต้น นี่แค่บอกว่า "ใช้บริการคลาวด์"
Martin Muzatko

3
@MartinMuzatko - มันจะเลือกตัวเลือกที่สองและบอกคุณว่าคุณควรใช้มันอย่างไรและทำไม หากคุณหมายถึง "แต่นี่ไม่ใช่ตัวเลือกที่สมบูรณ์แบบที่ช่วยให้คุณส่งทุกอย่างในคำขอเดียวโดยไม่มีนัย" - ใช่ไม่มีวิธีแก้ปัญหาดังกล่าว
libik

105

มีการตัดสินใจหลายประการ :

  1. ครั้งแรกเกี่ยวกับเส้นทางของทรัพยากร :

    • ทำโมเดลอิมเมจเป็นรีซอร์สด้วยตนเอง:

      • ซ้อนกันในผู้ใช้ (/ ผู้ใช้ /: id / ภาพ): ความสัมพันธ์ระหว่างผู้ใช้และภาพที่ถูกสร้างขึ้นโดยปริยาย

      • ในรูทพา ธ (/ image):

        • ลูกค้ามีหน้าที่รับผิดชอบในการสร้างความสัมพันธ์ระหว่างภาพและผู้ใช้หรือ;

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

    • ฝังภาพเป็นส่วนหนึ่งของผู้ใช้

  2. การตัดสินใจครั้งที่สองเกี่ยวกับวิธีแสดงทรัพยากรรูปภาพ :

    • ในฐานะที่เป็นส่วนบรรจุ JSON ที่เข้ารหัส 64 ฐาน
    • เป็นเพย์โหลดหลายส่วน

นี่จะเป็นเส้นทางการตัดสินใจของฉัน:

  • ฉันมักจะชอบการออกแบบมากกว่าประสิทธิภาพเว้นแต่มีกรณีที่แข็งแกร่งสำหรับมัน มันทำให้ระบบสามารถบำรุงรักษาได้มากขึ้นและผู้ติดตั้งสามารถเข้าใจได้ง่ายขึ้น
  • ดังนั้นความคิดแรกของฉันคือการไปเป็นตัวแทน Base64 ของทรัพยากรรูปภาพเพราะมันช่วยให้คุณเก็บทุกอย่างไว้ใน JSON หากคุณเลือกตัวเลือกนี้คุณสามารถสร้างโมเดลพา ธ ของทรัพยากรได้ตามต้องการ
    • หากความสัมพันธ์ระหว่างผู้ใช้และภาพเป็น 1 ต่อ 1 ฉันชอบที่จะสร้างแบบจำลองภาพเป็นแอตทริบิวต์พิเศษถ้าชุดข้อมูลทั้งสองมีการปรับปรุงในเวลาเดียวกัน ในกรณีอื่น ๆ คุณสามารถเลือกที่จะสร้างโมเดลของรูปภาพได้อย่างอิสระไม่ว่าจะเป็นแอตทริบิวต์อัปเดตรูปภาพผ่าน PUT หรือ PATCH หรือเป็นทรัพยากรแยกต่างหาก
  • หากคุณเลือกส่วนของข้อมูลที่มีหลายส่วนฉันรู้สึกว่าถูกบังคับให้ทำแบบจำลองภาพเนื่องจากทรัพยากรเป็นของตัวเองดังนั้นในกรณีของเราทรัพยากรผู้ใช้ในกรณีของเราจะไม่ได้รับผลกระทบจากการตัดสินใจใช้การเป็นตัวแทนไบนารีสำหรับภาพ

ถ้าอย่างนั้นคำถามก็คือ : มีผลกระทบอะไรบ้างเกี่ยวกับการเลือก base64 vs multipart? . เราคิดว่าการแลกเปลี่ยนข้อมูลในรูปแบบหลายส่วนควรมีประสิทธิภาพมากกว่า แต่บทความนี้แสดงให้เห็นว่าการเป็นตัวแทนทั้งสองแตกต่างกันอย่างไรในแง่ของขนาด

ตัวเลือกของฉัน Base64:

  • การตัดสินใจออกแบบที่สอดคล้อง
  • ส่งผลกระทบต่อประสิทธิภาพเล็กน้อย
  • เนื่องจากเบราว์เซอร์เข้าใจ URI ของข้อมูล (ภาพที่เข้ารหัส base64) จึงไม่จำเป็นต้องแปลงไฟล์เหล่านี้หากไคลเอ็นต์เป็นเบราว์เซอร์
  • ฉันจะไม่ลงคะแนนว่าจะให้เป็นแอตทริบิวต์หรือทรัพยากรแบบสแตนด์อโลนหรือไม่ขึ้นอยู่กับโดเมนปัญหาของคุณ (ซึ่งฉันไม่รู้) และความชอบส่วนตัวของคุณ

3
เราไม่สามารถเข้ารหัสข้อมูลโดยใช้โปรโตคอลการทำให้เป็นอนุกรมอื่น ๆ เช่น protobuf และอื่น ๆ ได้หรือไม่? โดยทั่วไปฉันพยายามเข้าใจว่ามีวิธีอื่นที่ง่ายกว่าในการเพิ่มขนาดและเวลาในการประมวลผลที่มาพร้อมกับการเข้ารหัส base64
Andy Dufresne

1
คำตอบที่น่าสนใจมาก ขอบคุณสำหรับวิธีการทีละขั้นตอน มันทำให้ฉันเข้าใจคะแนนของคุณดีขึ้นมาก
Zuhayer Tahir

13

ทางออกที่สองของคุณน่าจะถูกที่สุด คุณควรใช้ HTTP ข้อมูลจำเพาะและชนิด mime multipart/form-dataวิธีการที่พวกเขาตั้งใจและอัพโหลดไฟล์ผ่านทาง เท่าที่มีการจัดการความสัมพันธ์ฉันจะใช้กระบวนการนี้ (โปรดจำไว้ว่าฉันรู้เกี่ยวกับสมมติฐานหรือการออกแบบระบบของคุณเป็นศูนย์):

  1. POSTเพื่อ/usersสร้างเอนทิตีผู้ใช้
  2. POSTภาพไปที่/imagesตรวจสอบให้แน่ใจว่าได้กลับLocationส่วนหัวไปยังตำแหน่งที่สามารถดึงภาพตามข้อกำหนด HTTP
  3. PATCHไป/users/carPhotoและกำหนดหมายเลขของภาพที่กำหนดในLocationส่วนหัวของขั้นตอนที่ 2

1
ฉันไม่ได้มีการควบคุมโดยตรงของ "วิธีการที่ลูกค้าจะใช้ API" ... ปัญหาของการนี้ก็คือ "ตาย" ภาพที่ไม่ได้รับการติดตั้งไปยังแหล่งข้อมูลบางอย่าง ...
libik

4
โดยปกติเมื่อคุณเลือกตัวเลือกที่สองเราต้องการอัปโหลดองค์ประกอบสื่อและส่งกลับตัวระบุสื่อไปยังลูกค้าก่อนจากนั้นลูกค้าสามารถส่งข้อมูลเอนทิตีรวมถึงตัวระบุสื่อวิธีการเหล่านี้จะหลีกเลี่ยงข้อมูลที่ไม่ตรงกัน
Kellerman Rivero

2

ไม่มีทางออกที่ง่าย แต่ละวิธีมีข้อดีข้อเสีย แต่วิธีบัญญัติเป็นที่ใช้ตัวเลือกแรก: multipart/form-data. ตามคำแนะนำของ W3บอกว่า

ควรใช้ชนิดเนื้อหา "multipart / form-data" เพื่อส่งแบบฟอร์มที่มีไฟล์ข้อมูลที่ไม่ใช่ ASCII และข้อมูลไบนารี

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

การใช้งานmultipart/form-dataคุณติดอยู่กับปรัชญา REST / http คุณสามารถดูคำตอบของคำถามที่คล้ายกันที่นี่

อีกทางเลือกหนึ่งถ้าผสมทางเลือกคุณสามารถใช้ multipart / form-data แต่แทนที่จะส่งทุกค่าแยกกันคุณสามารถส่งค่าชื่อ payload ด้วย json payload ภายใน (ฉันลองวิธีนี้โดยใช้ ASP.NET WebAPI 2 และทำงานได้ดี)


2
คู่มือการแนะนำ W3 นั้นไม่เกี่ยวข้องที่นี่เนื่องจากอยู่ในบริบทของข้อกำหนด HTML 4
โยฮันน์

1
จริงมาก .... "ไม่ใช่ข้อมูล ASCII" ต้องมีหลายส่วน? ในศตวรรษที่ยี่สิบเอ็ด? ในโลก UTF-8 เหรอ? แน่นอนว่านี่เป็นคำแนะนำที่ไร้สาระสำหรับวันนี้ ฉันยังประหลาดใจที่มีอยู่ใน HTML 4 วัน แต่บางครั้งโลกของอินเทอร์เน็ตโครงสร้างพื้นฐานก็เคลื่อนไหวช้ามาก
Ray Toal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.