When uploading a single image to a known location, for example a user uploading an image to use as their profile image, using a PUT request for the upload makes more sense than POST if you are trying to follow REST principles.
I couldn’t find a complete solution online, so the following is what I came up with. Due to the complexities of real life, the request also has to send the file name and mime type to the server with the image, which is not something a PUT request can do. For this reason the file name and mime type are attached to the request URL, in this way making use of GET parameters and the PUT body to send data. I cannot say if this will be considered an abuse of REST principles or a good use of them, but this is simpler than doing two requests, the first one to tell the server what to expect, perhaps a PUT request that fills out the expected image’s attributes, and the second one to send the actual binary data.
Below is an everyday event listener that fires when the user chooses a file in the file upload input:
$("input:file").change(function (){ sendImageToServer(); });
And here is sendImageToServer()
:
function sendImageToServer() { if ($('input:file').val().length > 0) { var file = $('input:file')[0].files[0]; $.ajax({ url: '/profiles/' + user.id + '/image?filename=' + file.name + '&mimetype=' + file.type, type: 'PUT', data: file, contentType: false, processData: false, cache: false, error: function (data) { /*alert(data);*/ }, success: function (response) { // do stuff } }); } }, }
And below is the back-end code, which uses Silex. If you are not using Silex, you’d use a typical AJAX handler and use $_GET
and fopen("php://input", "r")
to get the needed data.
Here is the Silex route:
$app->put('/profiles/{user_id}/image', 'Controller\ProfileController::profileImage');
And here is the controller. It writes the PUT data to a temporary file, then moves it to a permanent place. There might be a more elegant way of doing this.
public class ProfileController { public function profileImage(Application $app, Request $request, $user_id) { // permission and validity checks if ($request->getMethod() == Request::METHOD_PUT) { // if this is a PUT request $temp_file = '/some/path/temp_profile_image' . $user_id; touch($temp_file); // create file $fp = fopen($temp_file, 'w'); // open file handler /* in a non-Silex environment, instead of using $request->getContent(), you'd probably use fopen("php://input", "r") */ fwrite($fp, $request->getContent()); $profile_images_dir = '/some/path/profile_images/'; $file_type = $request->query->get('mimetype'); if (!$file_type) { $file_type = Util::getMimeBasedOnExtension($request->query->get('filename')); } if ($file_type == 'image/png' || $file_type == 'image/jpg' || $file_type == 'image/gif' || $file_type == 'image/jpeg' ) { $filename = $user_id . '.jpg'; $full_file_save_path = $dir . $filename; $image = new \SimpleImage(); $image->load($temp_file); $image->save($full_file_save_path, 'image/jpg'); } unlink($temp_file); return new JsonResponse(['Successfully updated.', 'success']); } } }
Thanks so much for publishing this. I was having a hard time PUT’ing a file to an AWS API Gateway endpoint with their Javascript SDK, I figured it might be easier to just do it in jQuery.