Using PUT to RESTfully upload an image to a server with jQuery AJAX and PHP

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']);
		}
	}
}
Commenting rules: Politeness is the only rule. We respect your right to disagree with anything we say. But comments with profanity and insults will be deleted.
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jamil
Jamil
5 years ago

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.