मैं एक REST वेब सेवा का उपयोग करके मेटाडेटा के साथ फ़ाइल कैसे अपलोड करूं?


250

मेरे पास एक REST वेब सेवा है जो वर्तमान में इस URL को उजागर करती है:

http: // सर्वर / डेटा / मीडिया

उपयोगकर्ता POSTनिम्नलिखित JSON कर सकते हैं :

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}

एक नया मीडिया मेटाडेटा बनाने के लिए।

अब मुझे मीडिया मेटाडेटा के समान फ़ाइल अपलोड करने की क्षमता चाहिए। इस बारे में जाने का सबसे अच्छा तरीका क्या है? मैं एक नई प्रॉपर्टी का परिचय दे सकता हूं जिसे बुलाया गया है fileऔर आधार 64 फाइल को एनकोड करता है, लेकिन मैं सोच रहा था कि क्या कोई बेहतर तरीका है।

multipart/form-dataHTML फॉर्म क्या भेजेगा, इसका भी उपयोग कर रहा हूं, लेकिन मैं एक REST वेब सेवा का उपयोग कर रहा हूं और यदि संभव हो तो JSON का उपयोग करने के लिए छड़ी करना चाहता हूं।


36
केवल JSON का उपयोग करने के लिए चिपके रहना वास्तव में एक RESTful वेब सेवा के लिए आवश्यक नहीं है। REST मूल रूप से कुछ भी है जो HTTP विधियों के मुख्य सिद्धांतों और कुछ अन्य (यकीनन गैर-मानकीकृत) नियमों का पालन करता है।
एरिक कप्लून

जवाबों:


192

मैं ग्रेग के साथ सहमत हूं कि एक दो चरण दृष्टिकोण एक उचित समाधान है, हालांकि मैं इसे दूसरे तरीके से करूंगा। मुझे क्या करना होगा:

POST http://server/data/media
body:
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}

मेटाडेटा प्रविष्टि बनाने के लिए और इस तरह की प्रतिक्रिया लौटाएं:

201 Created
Location: http://server/data/media/21323
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentUrl": "http://server/data/media/21323/content"
}

क्लाइंट तब इस ContentUrl का उपयोग कर सकता है और फ़ाइल डेटा के साथ एक PUT कर सकता है।

इस दृष्टिकोण के बारे में अच्छी बात यह है कि जब आपका सर्वर डेटा की भारी मात्रा के साथ तौलना शुरू कर देता है, तो आपके द्वारा लौटाया जाने वाला url अधिक स्थान / क्षमता वाले किसी अन्य सर्वर को इंगित कर सकता है। या आप किसी प्रकार का राउंड रॉबिन दृष्टिकोण लागू कर सकते हैं यदि बैंडविड्थ एक समस्या है।


8
पहले सामग्री भेजने का एक फायदा यह है कि जब तक मेटाडेटा मौजूद है, तब तक सामग्री पहले से मौजूद है। अंततः सही उत्तर सिस्टम में डेटा के संगठन पर निर्भर करता है।
ग्रेग हेविगेल

धन्यवाद, मैंने इसे सही उत्तर के रूप में चिह्नित किया क्योंकि यह वही है जो मैं करना चाहता था। दुर्भाग्य से, एक अजीब व्यापार नियम के कारण, हमें अपलोड को किसी भी क्रम में होने की अनुमति देनी होगी (मेटाडेटा पहले या फ़ाइल पहले)। मैं सोच रहा था कि क्या दोनों स्थितियों से निपटने के सिरदर्द को बचाने के लिए दोनों को मिलाने का कोई तरीका था।
डैनियल टी।

@ डैनियल यदि आप डेटा फ़ाइल को पहले पोस्ट करते हैं, तो आप स्थान पर लौटाए गए URL को ले सकते हैं और मेटाडेटा में ContentUrl विशेषता में जोड़ सकते हैं। इस तरह, जब सर्वर मेटाडेटा प्राप्त करता है, यदि कोई ContentUrl मौजूद है, तो यह पहले से ही जानता है कि फ़ाइल कहाँ है। यदि कोई ContentUrl नहीं है, तो यह जानता है कि इसे एक बनाना चाहिए।
डारेल मिलर

यदि आप पहले POST करते थे, तो क्या आप उसी URL पर पोस्ट करेंगे? (/ सर्वर / डेटा / मीडिया) या आप फ़ाइल-प्रथम अपलोड के लिए एक और प्रविष्टि बिंदु बनाएंगे?
मैट ब्रिल्सफोर्ड

1
@ फ़ारवे क्या होगा यदि मेटाडेटा में किसी छवि की "पसंद" की संख्या शामिल है? क्या आप इसे एकल संसाधन के रूप में मानेंगे? या अधिक स्पष्ट रूप से, क्या आप यह सुझाव दे रहे हैं कि यदि मैं किसी छवि का विवरण संपादित करना चाहता हूं, तो मुझे छवि को फिर से अपलोड करना होगा? ऐसे कई मामले हैं जहां बहु-भाग के रूप सही समाधान हैं। यह हमेशा मामला नहीं है।
डारेल मिलर

104

सिर्फ इसलिए कि आप JSON में संपूर्ण अनुरोध निकाय को नहीं लपेट रहे हैं, इसका मतलब यह नहीं है multipart/form-dataकि JSON और फ़ाइल (दोनों) को एक ही अनुरोध में पोस्ट करने के लिए उपयोग करना उचित नहीं है :

curl -F "metadata=<metadata.json" -F "file=@my-file.tar.gz" http://example.com/add-file

सर्वर की तरफ (स्यूडोकोड के लिए पायथन का उपयोग करके):

class AddFileResource(Resource):
    def render_POST(self, request):
        metadata = json.loads(request.args['metadata'][0])
        file_body = request.args['file'][0]
        ...

एकाधिक फ़ाइलों को अपलोड करने के लिए, प्रत्येक के लिए अलग-अलग "फ़ॉर्म फ़ील्ड" का उपयोग करना संभव है:

curl -F "metadata=<metadata.json" -F "file1=@some-file.tar.gz" -F "file2=@some-other-file.tar.gz" http://example.com/add-file

... जिस स्थिति में सर्वर कोड होगा request.args['file1'][0] औरrequest.args['file2'][0]

या कई के लिए एक ही पुन: उपयोग:

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz" -F "files=@some-other-file.tar.gz" http://example.com/add-file

... किस स्थिति request.args['files']में बस लंबाई 2 की सूची होगी।

या एक ही क्षेत्र के माध्यम से कई फाइलें पास करें:

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz,some-other-file.tar.gz" http://example.com/add-file

...कौनसे मामलेमें request.args['files'] में सभी फ़ाइलों वाली एक स्ट्रिंग होगी, जिसे आपको खुद को पार्स करना होगा - यह सुनिश्चित नहीं करना है कि यह कैसे करना है, लेकिन मुझे यकीन है कि यह मुश्किल नहीं है, या बेहतर है कि पिछले दृष्टिकोण का उपयोग करें।

के बीच का अंतर है @और <वह है@ कारणों फ़ाइल एक फ़ाइल अपलोड के रूप में संलग्न करने के लिए है, जबकि <देता एक पाठ क्षेत्र के रूप में फ़ाइल की सामग्री।

PS सिर्फ इसलिए कि मैं अनुरोधों curlको उत्पन्न करने के तरीके के रूप में उपयोग कर रहा हूं, POSTइसका मतलब यह नहीं है कि सटीक HTTP अनुरोधों को प्रोग्रामिंग भाषा जैसे कि पायथन या किसी भी पर्याप्त रूप से सक्षम उपकरण का उपयोग करके नहीं भेजा जा सकता है।


4
मैं खुद इस दृष्टिकोण के बारे में सोच रहा था, और मैंने किसी और को अभी तक इसे क्यों नहीं देखा था। मैं सहमत हूँ, मुझे पूरी तरह से लगता है।
सूपडॉग

1
हाँ! यह बहुत ही व्यावहारिक दृष्टिकोण है, और यह संपूर्ण अनुरोध के लिए सामग्री प्रकार के रूप में "एप्लिकेशन / जोंस" का उपयोग करने से कम नहीं है।
बीमारी

.. लेकिन यह केवल तभी संभव है जब आपके पास एक .json फ़ाइल में डेटा हो और उसे अपलोड करें, जो कि मामला नहीं है
itsjavi

5
@mjolnic आपकी टिप्पणी अप्रासंगिक है: CURL उदाहरण सिर्फ, ठीक है, उदाहरण हैं ; उत्तर स्पष्ट रूप से बताता है कि आप अनुरोध को भेजने के लिए किसी भी चीज़ का उपयोग कर सकते हैं ... यह भी, क्या आपको सिर्फ लिखने से रोकता है curl -f 'metadata={"foo": "bar"}'?
एरिक कप्लुन

3
मैं इस दृष्टिकोण का उपयोग कर रहा हूं क्योंकि स्वीकृत उत्तर मैं उस एप्लिकेशन के लिए काम नहीं कर रहा हूं जो मैं विकसित कर रहा हूं (फ़ाइल डेटा से पहले मौजूद नहीं हो सकती है और यह उस मामले को संभालने के लिए अनावश्यक जटिलता जोड़ता है जहां डेटा पहले अपलोड किया गया है और फ़ाइल कभी अपलोड नहीं होती है) ।
बिट्सएवल्ड

33

समस्या को हल करने का एक तरीका अपलोड को दो चरण की प्रक्रिया बनाना है। सबसे पहले, आप एक POST का उपयोग करके फ़ाइल को स्वयं अपलोड करेंगे, जहाँ सर्वर क्लाइंट को कुछ पहचानकर्ता लौटाता है (एक पहचानकर्ता फ़ाइल सामग्री का SHA1 हो सकता है)। फिर, दूसरा अनुरोध मेटाडेटा को फ़ाइल डेटा के साथ जोड़ता है:

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentID": "7a788f56fa49ae0ba5ebde780efe4d6a89b5db47"
}

JSON अनुरोध में इनकोड किए गए फ़ाइल डेटा बेस 64 को शामिल करने से डेटा का आकार 33% बढ़ जाएगा। यह फ़ाइल के समग्र आकार के आधार पर महत्वपूर्ण हो सकता है या नहीं भी हो सकता है।

एक अन्य दृष्टिकोण कच्चे फ़ाइल डेटा के एक पोस्ट का उपयोग करना हो सकता है, लेकिन HTTP अनुरोध शीर्ष लेख में कोई मेटाडेटा शामिल करें। हालाँकि, यह मूल REST परिचालनों से थोड़ा बाहर है और कुछ HTTP क्लाइंट पुस्तकालयों के लिए अधिक अजीब हो सकता है।


आप Ascii85 को केवल 1/4 बढ़ाकर उपयोग कर सकते हैं।
सिंगागुलर

किसी भी संदर्भ में क्यों base64 आकार को इतना बढ़ाता है?
जाम 01

1
@ जाम 01: संयोग से, मैंने कल ही कुछ देखा था जो अंतरिक्ष प्रश्न का उत्तर अच्छी तरह से देता है: बेस 64 एन्कोडिंग का स्पेस ओवरहेड क्या है?
ग्रेग हेवगिल

10

मुझे एहसास है कि यह एक बहुत पुराना सवाल है, लेकिन उम्मीद है कि इससे किसी और को मदद मिलेगी क्योंकि मैं इस पोस्ट पर उसी चीज की तलाश में आया था। मेरे पास एक समान मुद्दा था, बस मेरा मेटाडेटा एक गाइड और इंट था। हालांकि समाधान एक ही है। आप बस आवश्यक मेटाडेटा URL का हिस्सा बना सकते हैं।

आपके "नियंत्रक" वर्ग में POST स्वीकार करने की विधि:

public Task<HttpResponseMessage> PostFile(string name, float latitude, float longitude)
{
    //See http://stackoverflow.com/a/10327789/431906 for how to accept a file
    return null;
}

फिर जो भी आप मार्गों को पंजीकृत कर रहे हैं, इस मामले में मेरे लिए WebApiConfig.Register (HttpConfiguration config)।

config.Routes.MapHttpRoute(
    name: "FooController",
    routeTemplate: "api/{controller}/{name}/{latitude}/{longitude}",
    defaults: new { }
);

6

यदि आपकी फ़ाइल और उसका मेटाडाटा एक संसाधन बनाते हैं, तो यह उन दोनों को एक अनुरोध में अपलोड करने के लिए पूरी तरह से ठीक है। नमूना अनुरोध होगा:

POST https://target.com/myresources/resourcename HTTP/1.1

Accept: application/json

Content-Type: multipart/form-data; 

boundary=-----------------------------28947758029299

Host: target.com

-------------------------------28947758029299

Content-Disposition: form-data; name="application/json"

{"markers": [
        {
            "point":new GLatLng(40.266044,-74.718479), 
            "homeTeam":"Lawrence Library",
            "awayTeam":"LUGip",
            "markerImage":"images/red.png",
            "information": "Linux users group meets second Wednesday of each month.",
            "fixture":"Wednesday 7pm",
            "capacity":"",
            "previousScore":""
        },
        {
            "point":new GLatLng(40.211600,-74.695702),
            "homeTeam":"Hamilton Library",
            "awayTeam":"LUGip HW SIG",
            "markerImage":"images/white.png",
            "information": "Linux users can meet the first Tuesday of the month to work out harward and configuration issues.",
            "fixture":"Tuesday 7pm",
            "capacity":"",
            "tv":""
        },
        {
            "point":new GLatLng(40.294535,-74.682012),
            "homeTeam":"Applebees",
            "awayTeam":"After LUPip Mtg Spot",
            "markerImage":"images/newcastle.png",
            "information": "Some of us go there after the main LUGip meeting, drink brews, and talk.",
            "fixture":"Wednesday whenever",
            "capacity":"2 to 4 pints",
            "tv":""
        },
] }

-------------------------------28947758029299

Content-Disposition: form-data; name="name"; filename="myfilename.pdf"

Content-Type: application/octet-stream

%PDF-1.4
%
2 0 obj
<</Length 57/Filter/FlateDecode>>stream
x+r
26S00SI2P0Qn
F
!i\
)%!Y0i@.k
[
endstream
endobj
4 0 obj
<</Type/Page/MediaBox[0 0 595 842]/Resources<</Font<</F1 1 0 R>>>>/Contents 2 0 R/Parent 3 0 R>>
endobj
1 0 obj
<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>
endobj
3 0 obj
<</Type/Pages/Count 1/Kids[4 0 R]>>
endobj
5 0 obj
<</Type/Catalog/Pages 3 0 R>>
endobj
6 0 obj
<</Producer(iTextSharp 5.5.11 2000-2017 iText Group NV \(AGPL-version\))/CreationDate(D:20170630120636+02'00')/ModDate(D:20170630120636+02'00')>>
endobj
xref
0 7
0000000000 65535 f 
0000000250 00000 n 
0000000015 00000 n 
0000000338 00000 n 
0000000138 00000 n 
0000000389 00000 n 
0000000434 00000 n 
trailer
<</Size 7/Root 5 0 R/Info 6 0 R/ID [<c7c34272c2e618698de73f4e1a65a1b5><c7c34272c2e618698de73f4e1a65a1b5>]>>
%iText-5.5.11
startxref
597
%%EOF

-------------------------------28947758029299--

3

मुझे समझ में नहीं आता कि, आठ वर्षों में, किसी ने भी आसान उत्तर पोस्ट नहीं किया है। फ़ाइल को बेस 64 के रूप में एन्कोड करने के बजाय, एक स्ट्रिंग के रूप में जसन को एनकोड करें। तो बस सर्वर की तरफ json डिकोड करें।

जावास्क्रिप्ट में:

let formData = new FormData();
formData.append("file", myfile);
formData.append("myjson", JSON.stringify(myJsonObject));

सामग्री-प्रकार: मल्टीपार्ट / फॉर्म-डेटा का उपयोग करके इसे पोस्ट करें

सर्वर साइड पर, फ़ाइल को सामान्य रूप से पुनः प्राप्त करें, और एक स्ट्रिंग के रूप में जोंस को पुनः प्राप्त करें। स्ट्रिंग को ऑब्जेक्ट में कनवर्ट करें, जो आमतौर पर कोड की एक पंक्ति होती है, चाहे आप प्रोग्रामिंग भाषा का उपयोग करें।

(हां, यह बहुत अच्छा काम करता है। इसे मेरे एक ऐप में करना।)

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.