Question:
We make a REST service, JSON is used as a transport (if specifics are required, the service is done in Java / Spring). There was a need to upload files, and I do not understand how the request should look right. At the moment, all data is transferred as a JSON array inside the request body without any parameters, and I don’t understand if it’s possible to organize file uploads inside this concept – at the moment you just need to transfer the file, but you may need to transfer some – or data – name, purpose, etc. How is it organized in a human way?
Answer:
A bit of brainstorming.
Option 1
We upload the file by POST, we apply the metadata as query parameters.
POST /someurl/upload?date=...&comment=...
Option 2
-
Allocate a resource that describes the metadata of the file:
/someurl/document/{id}
and the subresource is the file itself:
/someurl/document/{id}/data
-
We create an instance, passing metadata.
POST /someurl/document { 'date':'2007-03-01T13:00:00Z', 'comment':'....' }
-
We receive an identifier in the response
HTTP/1.1 201 Created { 'id': 123456, 'date':'2007-03-01T13:00:00Z', 'comment':'....' }
At this point, we can already query the file's metadata:
GET /someurl/document/123456 HTTP/1.1 200 OK { 'id': 123456, 'date':'2007-03-01T13:00:00Z', 'comment':'....' }
But the file itself is not yet available (no subresource created)
GET /someurl/document/123456/data HTTP/1.1 404 Not Found
Status 404 is not scary, it suggests that the resource may still appear along this path.
-
Upload the POST file as a subresource:
POST /someurl/document/123456/data
Now no problem, the file is available:
GET /someurl/document/123456/data HTTP/1.1 200 OK
If you want 100% bold REST with HATEOAS, then at step 3 you need to include a hypertext link to yourself in the response and you can get rid of the identifier:
{
'date':'2007-03-01T13:00:00Z',
'comment':'....',
'_links':{
'self': { 'href': '/someurl/document/123456' }
}
}
And after step 4, when requesting metadata, return a link to the file:
{
'date':'2007-03-01T13:00:00Z',
'comment':'....',
'_links':{
'self': { 'href': '/someurl/document/123456' },
'data': { 'href': '/someurl/document/123456/data' },
}
}
This way, the client side will be less tempted to access a file that has not yet been downloaded.
Option 3
We upload everything with json. We pack the file in base64. Good for small files.
POST /someurl/document
{
'date':'2007-03-01T13:00:00Z',
'comment':'....',
'file-data':'SGVsbG8gd29ybGRIZWxsbyB3b3JsZEhlbGxvIHdvcmxkSGVsbG8gd29ybGRIZWxsbyB3b3JsZEhlbGxvIHdvcmxkSGVsbG8gd29ybGRIZWxsbyB3b3JsZEhlbGxvIHdvcmxk'
}
Option 4
We use a multipart request. The description in json and the file itself are two parts.
POST /someurl/document
Content-Type: multipart/form-data; boundary=spacer
--spacer
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
{
'date':'2007-03-01T13:00:00Z',
'comment':'....'
}
--spacer
Content-Disposition: form-data; name="file-data"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
<...байты в base64...>
I am impressed by the 2nd option, as the most ideologically correct.