REST web hizmeti kullanarak meta veri içeren bir dosyayı nasıl yüklerim?


250

Şu anda bu URL'yi gösteren bir REST web hizmetim var:

http: // sunucu / veri / medya

nerede kullanıcılar POSTaşağıdaki JSON olabilir:

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

yeni bir Medya meta verisi oluşturmak için.

Şimdi medya meta verileriyle aynı anda bir dosya yükleme yeteneğine ihtiyacım var. Bunu yapmanın en iyi yolu nedir? Ben denilen yeni bir özellik tanıtmak olabilir fileve base64 dosyayı kodlamak, ama daha iyi bir yolu olup olmadığını merak ediyordum.

multipart/form-dataBir HTML formunun göndereceği gibi de var , ancak bir REST web hizmeti kullanıyorum ve mümkünse JSON kullanmaya devam etmek istiyorum.


36
RESTful bir web servisine sahip olmak için sadece JSON kullanmaya devam etmeniz gerekmez. REST temelde sadece HTTP yöntemlerinin ve diğer (tartışmasız standart olmayan) kuralların ana ilkelerini izleyen herhangi bir şeydir.
Erik Kaplun

Yanıtlar:


192

Greg ile iki aşamalı bir yaklaşımın makul bir çözüm olduğu konusunda hemfikirim, ancak tam tersini yapardım. Yapardım:

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

Meta veri girişi oluşturmak ve aşağıdaki gibi bir yanıt döndürmek için:

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

Istemci daha sonra bu ContentUrl kullanabilir ve dosya verileri ile bir PUT yapabilir.

Bu yaklaşımla ilgili güzel olan şey, sunucunuzun muazzam miktarda veri ile tartılmaya başlamasıdır, döndüğünüz URL, daha fazla alan / kapasiteye sahip başka bir sunucuya işaret edebilir. Veya bant genişliği önemliyse bir tür yuvarlak robin yaklaşımı uygulayabilirsiniz.


8
Önce içeriği göndermenin bir avantajı, meta veriler mevcut olduğunda içeriğin zaten mevcut olmasıdır. Sonuçta doğru cevap sistemdeki verilerin düzenlenmesine bağlıdır.
Greg Hewgill

Teşekkürler, bunu doğru cevap olarak işaretledim çünkü yapmak istediğim buydu. Ne yazık ki, garip bir iş kuralı nedeniyle, yüklemenin herhangi bir sırada gerçekleşmesine izin vermeliyiz (önce meta veri veya önce dosya). Her iki durumla baş etmenin baş ağrısını kurtarmak için ikisini birleştirmenin bir yolu olup olmadığını merak ediyordum.
Daniel T.

@Daniel Önce veri dosyasını POST yaparsanız, Konum'da döndürülen URL'yi alıp meta verilerdeki ContentUrl özelliğine ekleyebilirsiniz. Bu şekilde, sunucu meta verileri aldığında, bir ContentUrl varsa dosyanın zaten nerede olduğunu bilir. ContentUrl yoksa, bir tane oluşturması gerektiğini bilir.
Darrel Miller

önce POST yapacak olsaydınız, aynı URL’ye yayın gönderir misiniz? (/ server / data / media) veya önce dosya yüklemeleri için başka bir giriş noktası oluşturur musunuz?
Matt Brailsford

1
@Faraway Meta veriler bir görüntünün "beğenme" sayısını içeriyorsa ne olur? O zaman tek bir kaynak gibi davranır mısın? Ya da daha açık bir şekilde, bir görüntünün açıklamasını düzenlemek istersem, görüntüyü yeniden yüklemem gerektiğini mi önerirsiniz? Çok parçalı formların doğru çözüm olduğu birçok durum vardır. Her zaman böyle değildir.
Darrel Miller

104

JSON'da tüm istek gövdesini sarmamanız, multipart/form-datahem JSON'u hem de dosyaları tek bir istekte yayınlamanın RESTful olmadığı anlamına gelmez :

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

sunucu tarafında (sözde kod için Python kullanarak):

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

birden fazla dosya yüklemek için her biri için ayrı "form alanları" kullanmak mümkündür:

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

... bu durumda sunucu kodu request.args['file1'][0]verequest.args['file2'][0]

veya birçoğunu aynı şekilde tekrar kullanın:

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

... bu durumda request.args['files']uzunluk 2'nin bir listesi olacaktır.

veya tek bir alandan birden fazla dosya geçirin:

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

... bu durumda request.args['files'], tüm dosyaları içeren bir dize olacak, kendinizi ayrıştırmanız gerekecek - nasıl yapılacağından emin değilim, ama zor olmadığından emin değilim ya da sadece önceki yaklaşımları kullanın.

Arasındaki fark @ve <yani @, oysa dosya dosya yüklemesinden olarak bağlanmaması gerektiğini nedenleri <bir metin alanı olarak dosyanın Ataşe içeriği.

PS Sadece istekleri curloluşturmak için bir yol olarak kullanıyorum , POSTaynı HTTP isteklerinin Python gibi bir programlama dilinden veya yeterince yetenekli bir araç kullanılarak gönderilemeyeceği anlamına gelmez.


4
Bu yaklaşımı kendim ve neden başka kimsenin ortaya koyduğunu görmediğimi merak ediyordum. Katılıyorum, bana mükemmel RESTful gibi görünüyor.
2013 tarihinde çorba

1
EVET! Bu çok pratik bir yaklaşımdır ve tüm istek için içerik türü olarak "application / json" kullanmaktan daha az RESTful değildir.
sickill

.. ancak bu sadece bir .json dosyasındaki verilere sahipseniz ve yüklerseniz mümkündür, bu durum böyle değil
itsjavi

5
@mjolnic yorumunuz ilgisiz: cURL örnekleri sadece örneklerdir ; cevap açıkça isteği göndermek için herhangi bir şey kullanabileceğinizi belirtir ... Ayrıca, sadece yazmanızı engelleyen curl -f 'metadata={"foo": "bar"}'nedir?
Erik Kaplun

3
Bu yaklaşımı kullanıyorum, çünkü kabul edilen cevap geliştirdiğim uygulama için işe yaramayacak (dosya verilerden önce var olamaz ve verilerin ilk önce yüklendiği ve dosyanın asla yüklenmediği durumu işlemek için gereksiz karmaşıklık ekler) .
Mart'ta

33

Soruna yaklaşmanın bir yolu, yüklemeyi iki aşamalı bir işlem haline getirmektir. İlk olarak, sunucunun bir tanımlayıcıyı istemciye geri döndürdüğü bir POST kullanarak dosyanın kendisini yüklersiniz (tanımlayıcı, dosya içeriğinin SHA1'i olabilir). Ardından, ikinci bir istek meta verileri dosya verileriyle ilişkilendirir:

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

JSON isteğine kodlanmış dosya veri tabanı64'ün dahil edilmesi, aktarılan verilerin boyutunu% 33 oranında artıracaktır. Dosyanın toplam boyutuna bağlı olarak bu önemli olabilir veya olmayabilir.

Başka bir yaklaşım ham dosya verilerinin POST'unu kullanmak olabilir, ancak HTTP istek başlığına herhangi bir meta veri eklemek olabilir. Ancak, bu biraz temel REST işlemlerinin dışında kalır ve bazı HTTP istemci kitaplıkları için daha garip olabilir.


Ascii85'i sadece 1/4 artırarak kullanabilirsiniz.
Singagirl

Base64 neden boyutu bu kadar arttırıyor?
jam01

1
@ jam01: Tesadüfen, dün uzay sorusuna iyi cevap veren bir şey gördüm: Base64 kodlamasının uzay yükü nedir?
Greg Hewgill

10

Bunun çok eski bir soru olduğunu anlıyorum, ama umarım bu yazıya aynı şeyi arayan başka birine yardımcı olur. Benzer bir sorunum vardı, sadece meta verilerimin bir Rehber ve int olmasıydı. Çözüm yine de aynı. Gerekli meta verileri URL'nin bir parçası haline getirebilirsiniz.

"Controller" sınıfınızda POST kabul etme yöntemi:

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

O zaman rotaları kaydettiğiniz her neyse, bu durumda benim için WebApiConfig.Register (HttpConfiguration config).

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

6

Dosyanız ve meta verileri bir kaynak oluşturuyorsa, her ikisini de tek bir istekte yüklemek son derece iyidir. Örnek istek:

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

Sekiz yıl boyunca hiç kimsenin kolay cevabı göndermediğini anlamıyorum. Dosyayı base64 olarak kodlamak yerine, json'u bir dize olarak kodlayın. Sonra sadece sunucu tarafında json kodunu çözmek.

Javascript'te:

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

Content-Type kullanarak POST it: çok bölümlü / form-veri

Sunucu tarafında dosyayı normal şekilde alın ve json'u dize olarak alın. Dizeyi, hangi programlama dilini kullanırsanız kullanın genellikle bir kod satırı olan bir nesneye dönüştürün.

(Evet, harika çalışıyor. Uygulamalarımdan birinde yapıyor.)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.