Wysyłanie pliku i powiązanych danych do RESTful WebService najlepiej jako JSON

To pewnie będzie głupie pytanie, ale mam jedną z tych nocy. W aplikacji rozwijam RESTful API i chcemy, aby Klient wysyłał dane jako JSON. Część tej aplikacji wymaga od klienta przesłania pliku (Zwykle obrazu), a także informacji o obrazie.

Ciężko mi namierzyć, jak to się dzieje w jednym zapytaniu. Czy jest możliwe, aby Base64 dane Pliku w łańcuchu JSON? Czy będę musiał wykonać 2 posty do serwer? Czy nie powinienem używać do tego JSON?

Na marginesie, używamy Grails na zapleczu i te usługi są dostępne dla natywnych klientów mobilnych (iPhone, Android, itp.), Jeśli coś z tego robi różnicę.

Author: Dhruvan Ganesh, 2010-11-03

11 answers

Zadałem podobne pytanie tutaj:

Jak przesłać plik z metadanymi za pomocą usługi internetowej REST?

W zasadzie masz trzy opcje:

  1. Base64 koduje plik, kosztem zwiększenia rozmiaru danych o około 33%.
  2. Wyślij najpierw plik w poście multipart/form-data i zwróć ID klientowi. Następnie klient wysyła metadane z identyfikatorem, a serwer ponownie kojarzy plik i metadane.
  3. najpierw Wyślij metadane, a zwróć klientowi identyfikator. Następnie klient wysyła plik z identyfikatorem, a serwer ponownie kojarzy plik i metadane.
 472
Author: Daniel T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:25

Możesz przesłać plik i dane w jednym żądaniu za pomocą multipart/form-data content Type:

W wielu aplikacjach możliwe jest przedstawienie użytkownika z formularz. Użytkownik wypełni formularz, w tym informację, że jest wpisywane, generowane przez wejście użytkownika lub dołączane z plików, które użytkownik wybrał. Po wypełnieniu formularza dane z formularz jest wysyłany od użytkownika do otrzymującej aplikacji.

Definicja MultiPart/formularz-Dane pochodzą z jednego z tych aplikacje...

Z http://www.faqs.org/rfcs/rfc2388.html :

"multipart / form-data" zawiera szereg części. Każda część jest oczekuje się, że będzie zawierał nagłówek Content-disposition [RFC 2183], gdzie typem dyspozycyjnym jest "form-data" i gdzie dyspozycja zawiera (dodatkowy) parametr "name", gdzie wartość tego parametr jest oryginalną nazwą pola w formularzu. Na przykład, a część może zawierać nagłówek:

Content-Disposition: form-data; name= "użytkownik"

Z wartością odpowiadającą wpisowi pola "user".

W każdej sekcji między granicami można umieścić informacje o pliku lub informacje o polach. Z powodzeniem wdrożyłem usługę RESTful, która wymagała od użytkownika podania zarówno danych, jak i formularza, a multipart / form-data działało idealnie. Usługa została zbudowana przy użyciu Java / Spring, a Klient korzystał z C#, więc niestety nie mam żadnych przykładów Grails, aby podać jak skonfigurować usługę. W tym przypadku nie musisz używać JSON, ponieważ każda sekcja "form-data" zapewnia miejsce do podania nazwy parametru i jego wartości.

Dobrą rzeczą w używaniu multipart / form-data jest to, że używasz nagłówków zdefiniowanych przez HTTP, więc pozostajesz przy filozofii korzystania z istniejących narzędzi HTTP do tworzenia usługi.

 78
Author: McStretch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-03 02:49:09

Wiem, że ten wątek jest dość stary, jednak brakuje mi tutaj jednej opcji. Jeśli masz metadane (w dowolnym formacie), które chcesz wysłać wraz z danymi do przesłania, możesz złożyć jedno żądanie multipart/related.

Multipart/Related media type jest przeznaczony dla obiektów złożonych składających się z kilku powiązanych ze sobą części ciała.

Możesz sprawdzić specyfikację RFC 2387 , aby uzyskać więcej szczegółowych informacji.

W zasadzie każda część takiego żądania może mieć zawartość o innym typie i wszystkie części są w jakiś sposób powiązane(np. obraz i metadane it). Części są identyfikowane za pomocą ciągu granicznego, a po końcowym ciągu granicznym następują dwa myślniki.

Przykład:

POST /upload HTTP/1.1
Host: www.hostname.com
Content-Type: multipart/related; boundary=xyz
Content-Length: [actual-content-length]

--xyz
Content-Type: application/json; charset=UTF-8

{
    "name": "Sample image",
    "desc": "...",
    ...
}

--xyz
Content-Type: image/jpeg

[image data]
[image data]
[image data]
...
--foo_bar_baz--
 32
Author: pgiecek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 15:03:28

Wiem, że to pytanie jest stare, ale w ostatnich dniach przeszukałem całą sieć, aby rozwiązać to samo pytanie. Mam grails REST webservices i klienta iPhone ' a, który wysyła zdjęcia, tytuł i opis.

Nie wiem, czy moje podejście jest najlepsze, ale jest takie łatwe i proste.

Robię zdjęcie używając UIImagePickerController i wysyłam do serwera NSData używając znaczników nagłówka żądania, aby wysłać dane Zdjęcia.

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"myServerAddress"]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:UIImageJPEGRepresentation(picture, 0.5)];
[request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"myPhotoTitle" forHTTPHeaderField:@"Photo-Title"];
[request setValue:@"myPhotoDescription" forHTTPHeaderField:@"Photo-Description"];

NSURLResponse *response;

NSError *error;

[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

Po stronie serwera otrzymuję zdjęcie użycie kodu:

InputStream is = request.inputStream

def receivedPhotoFile = (IOUtils.toByteArray(is))

def photo = new Photo()
photo.photoFile = receivedPhotoFile //photoFile is a transient attribute
photo.title = request.getHeader("Photo-Title")
photo.description = request.getHeader("Photo-Description")
photo.imageURL = "temp"    

if (photo.save()) {    

    File saveLocation = grailsAttributes.getApplicationContext().getResource(File.separator + "images").getFile()
    saveLocation.mkdirs()

    File tempFile = File.createTempFile("photo", ".jpg", saveLocation)

    photo.imageURL = saveLocation.getName() + "/" + tempFile.getName()

    tempFile.append(photo.photoFile);

} else {

    println("Error")

}

Nie wiem, czy mam problemy w przyszłości, ale teraz działa dobrze w środowisku produkcyjnym.

 10
Author: Rscorreia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-28 13:38:06

Oto moje API podejścia ( używam przykładu) - jak widać, nie używam żadnego file_id (identyicator przesłanego pliku na serwerze) w API:

1.Tworzenie obiektu 'photo' na serwerze:

POST: /projects/{project_id}/photos   
params in: {name:some_schema.jpg, comment:blah}
return: photo_id

2.Prześlij plik (zauważ, że 'plik' jest w liczbie pojedynczej, ponieważ jest tylko jeden na zdjęcie):

POST: /projects/{project_id}/photos/{photo_id}/file
params in: file to upload
return: -

I wtedy na przykład:

3.Przeczytaj listę zdjęć

GET: /projects/{project_id}/photos
params in: -
return: array of objects: [ photo, photo, photo, ... ]

4.Przeczytaj szczegóły zdjęcia

GET: /projects/{project_id}/photos/{photo_id}
params in: -
return: photo = { id: 666, name:'some_schema.jpg', comment:'blah'}

5.Read photo file

GET: /projects/{project_id}/photos/{photo_id}/file
params in: -
return: file content

Więc wniosek jest taki, że najpierw ty Utwórz obiekt (zdjęcie) pocztą, a następnie wyślij żądanie secod z plikiem(ponownie POST).

 6
Author: Kamil Kiełczewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-05 15:20:53

Ponieważ jedynym brakującym przykładem jest Przykład Androida , dodam go. Ta technika używa niestandardowego Asynktasku, który powinien być zadeklarowany wewnątrz klasy Activity.

private class UploadFile extends AsyncTask<Void, Integer, String> {
    @Override
    protected void onPreExecute() {
        // set a status bar or show a dialog to the user here
        super.onPreExecute();
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        // progress[0] is the current status (e.g. 10%)
        // here you can update the user interface with the current status
    }

    @Override
    protected String doInBackground(Void... params) {
        return uploadFile();
    }

    private String uploadFile() {

        String responseString = null;
        HttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost("http://example.com/upload-file");

        try {
            AndroidMultiPartEntity ampEntity = new AndroidMultiPartEntity(
                new ProgressListener() {
                    @Override
                        public void transferred(long num) {
                            // this trigger the progressUpdate event
                            publishProgress((int) ((num / (float) totalSize) * 100));
                        }
            });

            File myFile = new File("/my/image/path/example.jpg");

            ampEntity.addPart("fileFieldName", new FileBody(myFile));

            totalSize = ampEntity.getContentLength();
            httpPost.setEntity(ampEntity);

            // Making server call
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                responseString = EntityUtils.toString(httpEntity);
            } else {
                responseString = "Error, http status: "
                        + statusCode;
            }

        } catch (Exception e) {
            responseString = e.getMessage();
        }
        return responseString;
    }

    @Override
    protected void onPostExecute(String result) {
        // if you want update the user interface with upload result
        super.onPostExecute(result);
    }

}

Więc, gdy chcesz wgrać swój plik po prostu zadzwoń:

new UploadFile().execute();
 6
Author: lifeisfoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-10 09:45:23

Obiekty FormData: Przesyłanie Plików Za Pomocą Ajax

XMLHttpRequest Poziom 2 dodaje obsługę nowego interfejsu FormData. Obiekty FormData umożliwiają łatwe konstruowanie zestawu par klucz / wartość reprezentujących pola formularza i ich wartości, które można następnie łatwo przesłać za pomocą metody XMLHttpRequest send ().

function AjaxFileUpload() {
    var file = document.getElementById("files");
    //var file = fileInput;
    var fd = new FormData();
    fd.append("imageFileData", file);
    var xhr = new XMLHttpRequest();
    xhr.open("POST", '/ws/fileUpload.do');
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
             alert('success');
        }
        else if (uploadResult == 'success')
             alert('error');
    };
    xhr.send(fd);
}

Https://developer.mozilla.org/en-US/docs/Web/API/FormData

 5
Author: lakhan_Ideavate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 13:29:47
@RequestMapping(value = "/uploadImageJson", method = RequestMethod.POST)
    public @ResponseBody Object jsongStrImage(@RequestParam(value="image") MultipartFile image, @RequestParam String jsonStr) {
-- use  com.fasterxml.jackson.databind.ObjectMapper convert Json String to Object
}
 0
Author: sunleo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-30 09:47:22

Chciałem wysłać kilka ciągów do serwera backend. Nie używałem json z multipart, użyłem request params.

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public void uploadFile(HttpServletRequest request,
        HttpServletResponse response, @RequestParam("uuid") String uuid,
        @RequestParam("type") DocType type,
        @RequestParam("file") MultipartFile uploadfile)

Url wyglądałby jak

http://localhost:8080/file/upload?uuid=46f073d0&type=PASSPORT

Przekazuję dwa paramy (uuid I type) wraz z przesłaniem pliku. Mam nadzieję, że pomoże to tym, którzy nie mają złożonych danych json do wysłania.

 0
Author: Aslam anwer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-18 18:22:30

Upewnij się, że masz następujący import. Ofcourse other standard imports

import org.springframework.core.io.FileSystemResource


    void uploadzipFiles(String token) {

        RestBuilder rest = new RestBuilder(connectTimeout:10000, readTimeout:20000)

        def zipFile = new File("testdata.zip")
        def Id = "001G00000"
        MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
        form.add("id", id)
        form.add('file',new FileSystemResource(zipFile))
        def urld ='''http://URL''';
        def resp = rest.post(urld) {
            header('X-Auth-Token', clientSecret)
            contentType "multipart/form-data"
            body(form)
        }
        println "resp::"+resp
        println "resp::"+resp.text
        println "resp::"+resp.headers
        println "resp::"+resp.body
        println "resp::"+resp.status
    }
 -5
Author: Mak Kul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-10 14:54:12

Jeśli tworzysz serwer rest możesz to zrobić

  1. niech klient wystawi plik przez HTTP
  2. klient może następnie wysłać adres url z danymi json, np. plik obrazu {"file_url":"http://cockwombles.com/blah.jpg"}
  3. serwer może następnie pobrać plik.
 -5
Author: jim smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 14:20:07