Affichage d'un fichier et des données associées sur un service Web RESTful de préférence sous forme de JSON
ce sera probablement une question stupide, mais je vais passer une de ces nuits. Dans une application, je développe RESTful API et nous voulons que le client envoie des données en JSON. Une partie de cette application demande au client de télécharger un fichier (généralement une image) ainsi que des informations sur l'image.
j'ai du mal à trouver comment cela se produit dans une seule requête. Est-il possible de Base64 les données du fichier dans une chaîne JSON? Vais-je avoir besoin d' effectuer 2 messages sur le serveur? Je ne devrais pas utiliser JSON pour ça?
comme note, nous utilisons des grils sur le backend et ces services sont accessibles par les clients mobiles natifs (iPhone, Android, etc), Si l'un de ceux-ci fait une différence.
11 réponses
j'ai posé une question similaire ici:
comment télécharger un fichier avec des métadonnées en utilisant un service REST web?
vous avez essentiellement trois choix:
- Base64 encodent le fichier, au détriment de l'augmentation de la taille des données d'environ 33%.
- envoyez le fichier d'abord dans un
multipart/form-data
POST, et retournez un ID au client. Le client envoie alors les métadonnées avec le ID, et le serveur re-associe le fichier et les métadonnées. - envoyez d'abord les métadonnées et retournez un ID au client. Le client envoie alors le fichier avec L'ID, et le serveur re-associe le fichier et les métadonnées.
vous pouvez envoyer le fichier et les données dans une seule demande en utilisant le multipart / form-data content type:
Dans de nombreuses applications, il est possible pour un utilisateur d'être présenté avec forme. L'utilisateur devra remplir le formulaire, y compris les renseignements est tapé, généré par l'utilisateur, ou inclus dans des fichiers que l' l'utilisateur a sélectionné. Lorsque le formulaire est rempli, les données de la le formulaire est envoyé par l'utilisateur au la réception de la demande.
la définition de MultiPart / Form-Data est dérivée de l'un de ceux application...
de http://www.faqs.org/rfcs/rfc2388.html :
"multipart/form-data" contient une série de parties. Chaque partie est prévu pour contenir un en-tête content-disposition [RFC 2183] où le type de disposition est "form-data" , et où la disposition contenir un paramètre (supplémentaire) de" nom", où la valeur de le paramètre est le nom du champ d'origine dans la forme. Par exemple, une partie peut contenir un en-tête:
Content-Disposition: form-data; name= "user"
avec la valeur correspondant à l'entrée du champ" Utilisateur".
vous pouvez inclure des renseignements sur les fichiers ou les champs dans chaque section entre les limites. J'ai réussi à mis en œuvre un service RESTful qui exigeait de l'utilisateur de soumettre à la fois des données et un formulaire, et multipart/form-data fonctionnait parfaitement. Le service a été construit en utilisant Java/Spring, et le client utilisait C#, donc malheureusement je n'ai pas D'exemples de Graal à vous donner concernant la façon de configurer le service. Vous n'avez pas besoin d'utiliser JSON dans ce cas car chaque section "form-data" vous fournit un endroit pour spécifier le nom du paramètre et sa valeur.
la bonne chose à propos de l'utilisation multipart / form-data est que vous utilisez des en-têtes définis par HTTP, donc vous vous en tenez à la philosophie REST d'utiliser les outils HTTP existants pour créer votre service.
je sais que ce fil est assez ancien, cependant, je manque ici une option. Si vous avez des métadonnées (dans n'importe quel format que vous souhaitez envoyer avec les données à télécharger, vous pouvez faire une seule multipart/related
demande.
le type de support multipartie/apparentée est destiné aux objets composés constitués de plusieurs parties du corps reliées entre elles.
vous pouvez vérifier RFC 2387 spécification pour plus en profondeur les détails.
fondamentalement, chaque partie d'une telle requête peut avoir un contenu de type différent et toutes les parties sont en quelque sorte liées (par exemple une image et des métadonnées informatiques). Les parties sont identifiées par une chaîne de Frontière, et la chaîne de frontière finale est suivie de deux traits d'Union.
exemple:
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--
je sais que cette question Est ancienne, mais dans les derniers jours j'avais cherché toute la toile pour résoudre cette même question. J'ai grails REST webservices et iPhone Client qui envoient des photos, le titre et la description.
Je ne sais pas si mon approche est la meilleure, mais elle est si simple et facile.
je prends une photo en utilisant le Controller UIImagePickerController et j'envoie au serveur les données NSData en utilisant les balises d'en-tête de request pour envoyer les données de l'image.
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];
côté serveur, je reçois la photo en utilisant le code:
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")
}
Je ne sais pas si j'ai des problèmes à l'avenir, mais maintenant ça marche très bien en environnement de production.
Voici mon API d'approche (j'utilise l'exemple) - comme vous pouvez le voir, vous je n'utilise pas de file_id (uploaded file identyicator in server) dans L'API:
1.Créer un objet 'photo' sur le serveur:
POST: /projects/{project_id}/photos
params in: {name:some_schema.jpg, comment:blah}
return: photo_id
2.Upload file (notez que 'file' est au singulier car il n'y en a qu'un par photo):
POST: /projects/{project_id}/photos/{photo_id}/file
params in: file to upload
return: -
et puis par exemple:
3.Lire la liste des photos
GET: /projects/{project_id}/photos
params in: -
return: array of objects: [ photo, photo, photo, ... ]
4.Lire quelques détails de la photo
GET: /projects/{project_id}/photos/{photo_id}
params in: -
return: photo = { id: 666, name:'some_schema.jpg', comment:'blah'}
5.Lire le fichier photo
GET: /projects/{project_id}/photos/{photo_id}/file
params in: -
return: file content
donc la conclusion est que, d'abord vous créez l'Objet (photo) par la poste, puis vous envoyez la demande de secod avec le fichier (POST de nouveau).
puisque le seul exemple manquant est le exemple ANDROID , je vais l'ajouter. Cette technique utilise un AsyncTask personnalisé qui doit être déclaré dans votre classe D'activité.
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);
}
}
donc, quand vous voulez télécharger votre fichier il suffit d'appeler:
new UploadFile().execute();
FormData Objets: Télécharger Des Fichiers À L'Aide D'Ajax
XMLHttpRequest Level 2 ajoute la prise en charge de la nouvelle interface FormData. Les objets FormData fournissent un moyen de construire facilement un ensemble de paires clé/valeur représentant les champs de formulaires et leurs valeurs, qui peuvent ensuite être facilement envoyés en utilisant la méthode 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);
}
@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
}
je voulais envoyer des chaînes pour backend server. Je n'ai pas utilisé json avec multipart, j'ai utilisé 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 ressemblerait à
http://localhost:8080/file/upload?uuid=46f073d0&type=PASSPORT
je passe deux params (uuid et type) avec le téléchargement de fichier. Espérons que cela aidera ceux qui n'ont pas les données complexes json à envoyer.
s'il vous plaît assurez-vous que vous avez l'importation suivante. Bien sûr autres importations standard
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
}
si vous développez un serveur rest vous pouvez le faire
- demandez au client d'exposer le fichier sur HTTP
- le client peut alors envoyer l'url avec vos données json E. G an image file
{"file_url":"http://cockwombles.com/blah.jpg"}
- Le serveur peut alors télécharger le fichier.