tutorial - Publication d'un fichier et de données associées dans un WebService RESTful, de préférence en tant que JSON
web service rest tutorial (7)
J'ai posé une question similaire ici:
Comment télécharger un fichier avec des métadonnées à l'aide d'un service Web REST?
Vous avez essentiellement trois choix:
- Base64 encoder le fichier, au prix d'augmenter la taille des données d'environ 33%.
- Envoyez d'abord le fichier dans un POST
multipart/form-data
et renvoyez un ID au client. Le client envoie ensuite les métadonnées avec l'ID et le serveur réassocie le fichier et les métadonnées. - Envoyez d'abord les métadonnées et renvoyez un identifiant au client. Le client envoie ensuite le fichier avec l'ID et le serveur réassocie le fichier et les métadonnées.
Cela va probablement être une question stupide, mais je vais avoir une de ces nuits. Dans une application, je développe une API RESTful et nous voulons que le client envoie des données au format JSON. Une partie de cette application nécessite que le client télécharge un fichier (généralement une image) ainsi que des informations sur l'image.
J'ai du mal à trouver comment cela se passe dans une seule requête. Est-il possible de Base64 les données de fichier dans une chaîne JSON? Est-ce que je vais devoir effectuer 2 messages sur le serveur? Ne devrais-je pas utiliser JSON pour cela?
En guise de remarque, nous utilisons Grails sur le backend et ces services sont accessibles par les clients mobiles natifs (iPhone, Android, etc), si l'un de ces éléments fait une différence.
Je sais que ce fil est assez vieux, cependant, il me manque une option. Si vous avez des métadonnées (quel que soit le format) que vous souhaitez envoyer avec les données à télécharger, vous pouvez effectuer une seule requête en plusieurs parties.
Le type Multipart / Related media est destiné aux objets composés composés de plusieurs parties de corps interdépendantes.
Vous pouvez vérifier la spécification RFC 2387 pour plus de détails.
Fondamentalement, chaque partie d'une telle requête peut avoir un contenu avec un type différent et toutes les parties sont en quelque sorte liées (par exemple une image et des métadonnées). Les parties sont identifiées par une chaîne de délimitation et la chaîne de délimitation 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--
Objets FormData: Télécharger des fichiers à l'aide d'Ajax
XMLHttpRequest Level 2 ajoute le support pour la nouvelle interface FormData. Les objets FormData permettent de créer facilement un ensemble de paires clé / valeur représentant les champs de formulaire et leurs valeurs, qui peuvent ensuite être facilement envoyés à l'aide de 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);
}
Puisque le seul exemple manquant est l' exemple ANDROID , je vais l'ajouter. Cette technique utilise une asyncTask personnalisée qui doit être déclarée dans votre classe 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);
}
}
Donc, quand vous voulez télécharger votre fichier, appelez simplement:
new UploadFile().execute();
Veuillez vous assurer que vous avez l'importation suivante. Ofcourse 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
}
Voici mon API d'approche (j'utilise l'exemple) - comme vous pouvez le voir, je n'utilise aucun fichier file_id (identyicator de fichier uploadé dans le serveur) 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.Télécharger le fichier (notez que 'fichier' est au singulier car il est seulement 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. Lisez 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 un objet (photo) par POST, et ensuite vous envoyez une requête secod avec le fichier (encore POST).
@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
}