My approach for uploading to S3 for my app has been to send a get request to my PHP backend to generate a presigned URL. I know the backend is set up correctly because running the following command successfully uploads an image to the S3 bucket:
curl -v -H "Content-Type: image/jpeg" -T ./test.jpeg '<presignedURL>'
However, I am running into issues when trying to upload the image in Swift. Here is my current implementation (please ignore the garbage, hard-coded, non error checking):
Backend
<?php
require '../vendor/autoload.php';
use Aws\S3\S3Client;
use Aws\Exception\AwsException;
$response = array();
$client = S3Client::factory(array(
'profile' => 'default',
'version' => 'latest',
'region' => 'us-east-2',
'signature' => 'v4'
));
$command = $client->getCommand('PutObject', array(
'Bucket' => 'test',
'Key' => 'test.jpeg',
'ContentType' => 'image/jpeg',
'Body' => ''
));
$signedUrl = $command->createPresignedUrl('+5 minutes');
$response['error'] = false;
$response['url'] = $signedUrl;
echo json_encode($response);
Swift Code
import Foundation
import Alamofire
let getTokenURL = "http://192.168.1.59:8000/v1/upload.php"
func submitImage(image: UIImage, completion: #escaping (NSDictionary) -> Void) {
AF.request(getTokenURL, method: .get).responseJSON { response in
switch response.result {
case.success(let value):
let jsonData = value as! NSDictionary
let url = jsonData.value(forKey: "url") as! String
performUpload(image: image, postURL: url)
case.failure(_):
let error_msg: NSDictionary = [
"error" : true,
"message" : "Unknown error occurred. Please try again",
]
//completion(error_msg)
}
}
}
func performUpload(image: UIImage, postURL: String) {
let imageData = image.jpegData(compressionQuality: 0.50)!
AF.upload(imageData, to: postURL, headers: ["Content-Type":"image/jpeg"]) //likely the culprit line
}
Currently the URL is returned from the get request in submitImage(), and performUpload() is called, making the culprit (likely) the very last lime of my Swift code segment. I'm having trouble figuring out what I should do while reading the documentation, and most guides on this subject are old and outdated because AlamoFire has changed their syntax. Any help would be greatly appreciated. Thank you!
Edit:
I have tweaked the performUpload() function. It now uploads the data to the s3 bucket, however the image is not able to be opened. I suspect this is because of an incorrect header in the request. From debugging I can tell the Content-Type header is "multipart/form-data" no matter what, so I'm not sure if this approach is viable:
struct HTTPBinResponse: Decodable { let url: String }
func performUpload(image: UIImage, postURL: String) {
let imageData = image.jpegData(compressionQuality: 0.50)!
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(imageData, withName: "file", mimeType: "image/jpeg")
}, to: postURL, method: .put, headers: ["Content-Type":"image/jpeg"]).responseDecodable(of: HTTPBinResponse.self) { response in
debugPrint(response)
}
}
For future readers, the key here is to add method: .put! Everything else is fine in this question.
Also I found that you have to use empty content-type headers. S3 is weird.
AF.upload(imageData, to: postURL, method: .put, headers: ["Content-Type": ""])
Related
Here is my swift code that uploads the file from application.
final class UploadService {
// I'm renting a remote server and the php code is on that
private let videoUploadPath = "http://myUploadPath.php"
var uploadsSession: URLSession!
var activeUploads: [URL: Upload] = [:]
func startUpload(medium: Medium) {
guard let url = URL(string: videoUploadPath) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
let upload = Upload(medium: medium)
upload.task = self.uploadsSession.uploadTask(with: request, fromFile: medium.avUrlAsset.url)
upload.task?.resume()
upload.isUploading = true
activeUploads[upload.medium.avUrlAsset.url] = upload
}
}
Note that I have to use
func uploadTask(with request: URLRequest, fromFile fileURL: URL) -> URLSessionUploadTask
// Apple documentation:
Parameters
request
A URL request object that provides the URL, cache policy, request type,
and so on. The body stream and body data in this request object are ignored.
to upload video from phone, since that's the only method that allows background upload (videos are normally pretty big, therefore I HAVE TO make background upload possible). However, this function discard all body data.
Here is the my PHP code that receives the uploaded file and stores it in the desired location:
$directory = "../storage/videos/";
$destination = $directory . basename($_FILES["file"]["name"]);
if (move_uploaded_file($_FILES["file"]["tmp_name"], $destination)) {
$returnArray["status"] = "200";
$returnArray["message"] = "Upload success";
echo json_encode($returnArray);
} else {
$returnArray["status"] = "300";
$returnArray["message"] = "Upload failed" . $_FILES["file"]["error"];
echo json_encode($returnArray);
return;
}
To be honest, I know it won't succeed, because I have no way to specify the file type and name, which are required by $_FILES["file"]["name"] in the PHP code.
Therefore, my question is, how to specify file name and type to be uploaded? Since the function
func uploadTask(with request: URLRequest, fromFile fileURL: URL) -> URLSessionUploadTask
discards all http body data, as I mentioned, perhaps making an http header that contains the file type and name? OR, is there another way for PHP to receive the file I just uploaded? For now, the $_FIELS is completely empty. I'm new to PHP, sorry if I didn't say it clearly enough.
Also, for Content-Type, I was using multipart/form-data when I upload an image to change user's profile image, I append the image data together with other data like uid, then attach those data as the body data in the http body. But now, I need to upload a single file, because all body data in the request will be ignored per iOS requirement. How can I do this?
Many days stuck in this, please help. Many thanks!!
There is a way to keep the URLSession.shared.uploadTask call and use the raw data on the PHP side. Here's an example that worked for me with JSON data:
Using sample code from https://developer.apple.com/documentation/foundation/url_loading_system/uploading_data_to_a_website for the Swift side
SWIFT side:
struct Order: Codable {
let customerId: String
let items: [String]
}
// ...
let order = Order(customerId: "12345",
items: ["Cheese pizza", "Diet soda"])
guard let uploadData = try? JSONEncoder().encode(order) else {
return
}
let url = URL(string: "https://example.com/upload.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
if let error = error {
print ("error: \(error)")
return
}
guard let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode) else {
print ("server error")
return
}
if let mimeType = response.mimeType,
mimeType == "application/json",
let data = data,
let dataString = String(data: data, encoding: .utf8) {
print ("got data: \(dataString)")
}
}
task.resume()
PHP side: (upload.php)
<?php
header('Content-Type: application/json; charset=utf-8');
$response = array();
try {
$uploaddir = './uploads/';
$uploadfile = $uploaddir . 'test.json';
$rawdata = file_get_contents('php://input');
if (!file_put_contents($uploadfile, $rawdata)) {
throw new RuntimeException('Failed to create file.');
}
$response = array(
"status" => "success",
"error" => false,
"message" => "File created successfully"
);
echo json_encode($response);
} catch (RuntimeException $e) {
$response = array(
"status" => "error",
"error" => true,
"message" => $e->getMessage()
);
echo json_encode($response);
}
?>
This will also write binary data into a file. In this simple case, there is no need to know the file name or content type. It may not work for a generic case, but this way of proceeding allows you to keep using URLSession.shared.uploadTask and avoid HTTP Body parameters.
HTH.
I am trying to upload image using below function everything working fine only is i want to send image in post and when i am trying get image getting nothing
This is for call API
Future getUploadImg(access_token,File _image) async {
print("Image: $_image");
String apiUrl = '$_apiUrl/user/upload-profile-image';
final length = await _image.length();
final request = new http.MultipartRequest('POST', Uri.parse(apiUrl));
request.headers['Accesstoken'] = "Bearer $access_token";
request.files.add(new http.MultipartFile('imagefile',_image.openRead(), length));
http.Response response = await http.Response.fromStream(await request.send());
print("Result: ${response.body}");
return json.decode(response.body);
}
My file that is passing to server is:
File: '/storage/emulated/0/Android/data/com.dotsquares.ecomhybrid/files/Pictures/c5df03f7-097d-47ca-a3c5-f896b2a38c086982492957343724084.jpg'
I got the result finally we need to pass string for image sharing my working code if anyone need for help:
Future getUploadImg(access_token,File _image) async {
print("Image: $_image");
var result;
var stream = new http.ByteStream(DelegatingStream.typed(_image.openRead()));
var length = await _image.length();
var uri = Uri.parse('$_apiUrl/user/upload-profile-image');
var request = new http.MultipartRequest("POST", uri);
request.headers['Accesstoken'] = "Bearer $access_token";
var multipartFile = new http.MultipartFile('imagefile', stream, length, filename: basename(_image.path));
request.files.add(multipartFile);
var response = await request.send();
print(" ===================response code ${response.statusCode}");
await response.stream.transform(utf8.decoder).listen((value) {
print(" =====================response value $value");
result = value;
});
return json.decode(result);
}
To avoid the following problems:
write failed
connection closed before ...
Flutter Doctor Error - SocketException: Write failed (OS Error: Broken pipe, errno = 32)
you need to add the correct parameters in the headers.
In my case, these problems occur with uploading images and sending base64 encoded requests. I solved it by adding the following 'connection' header: 'keep-alive':
final response = await this.httpClient.put(
url,
encoding: Utf8Codec(),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Accept': "*/*",
'connection': 'keep-alive',
'Accept-Encoding' : 'gzip, deflate, br',
},
body: body,
);
I tried to solved an issue regarding file upload with Alamofire (Link) (Swift 3.0) and retrieving them server-side using Slim PHP (Link) micro framework.
I'm trying to upload picture taken from the iPhone using
Alamofire.upload(multipartFormData:{
multipartFormData in
multipartFormData.append("value".data(/* ... */)!, withName :"key")
var idx = 1;
for imageRepresentation in imageData {
let pictureName = "pictures[]"
multipartFormData.append(imageRepresentation, withName: pictureName, mimeType: "image/jpeg")
idx += 1
}
},
to: uploadUrl,
method:.post,
headers: httpHeaders,
encodingCompletion: /* ... */
Here i'm almost sure that this script is working fine because when I hit https://httpbin.org/post I get back the encoded data base 64 image I've uploaded, so I'm quite sure that the issue comes from my server side code.
So, as I said, I'm using Slim PHP (Link) server side with this route
$this->post('/upload', function ($request, $response, $args) {
$request->getParsedBody(); //null
$request->getQueryParams(); // []
$request->getBody(); // {}
$request->getUploadedFiles(); // []
return /*Some JSON */
})->setName('upload');
Did I miss something ? Is there something I didn't understand ?
I already tried
Multipart/form-data example in slim micro framework
https://akrabat.com/psr-7-file-uploads-in-slim-3/
And the most weird thing is that the script works like a charm when executed from Paw API Explorer
Any help would be really appreciated ! Thanks.
For Uploading the image selected from photo library
In Swift 3 and Alamofire 4
Here is the Full Implementation of how to upload using Alamofire
Add the Following to your ViewController Class:
UIImagePickerControllerDelegate and UINavigationControllerDelegate
Create A Button:
First Create a button and implement the Following method in it for picker view
#IBAction func btnSelectProfileImageClicked(_ sender: Any) {
let ImagePicker = UIImagePickerController()
ImagePicker.delegate = self
ImagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary
self.present(ImagePicker, animated: true, completion: nil)
}
Then Implement the following UIPicker Methods:
func imagePickerController( _ picker: UIImagePickerController,didFinishPickingMediaWithInfo info:[String : Any] )
{Imgprofile.image = info[UIImagePickerControllerOriginalImage] as? UIImage
self.dismiss(animated: true, completion: nil)}
Make Another Button Which Passes the data to URL using Alamofire and Give an #IBAction outlet to it to it :
Enter Following Data to it
#IBAction func btnUpdateProfileSelected(_ sender: Any) {
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(UIImageJPEGRepresentation(self.Imgprofile.image!, 1)!, withName: "Prescription", fileName: "Profile_Image.jpeg", mimeType: "image/jpeg")
}, to:" Your URL Here where You want to Upload")
{ (result) in
switch result {
case .success(let upload, _, _):
print(result)
upload.uploadProgress(closure: { (progress) in
print(progress)
})
upload.responseJSON { response in
//print response.result
print(response);
}
case .failure(let encodingError):
print(encodingError);
}
}
}
Thats all
Hope This helps
For Full sample code or any doubts please comment. I will provide you
the sample code for this. Which Includes the Fetching as well as
upload data using Alamofire.
Thanks
I am trying to upload file to server. I am using SlimFramework and PHP in my backend code and it is in RESTful form. Image uploading works very well if I upload it through Postman however I am not able to get it to work using Swift and Alamofire library.
In the route I have in backend, I get the file like this, from body parameter:
$image = $request->getUploadedFiles();
and to upload I use the:
$image->moveTo("myPath");
In Swift I have router, which gives .post method and the route URL and I tried like this:
func uploadImage(user_id: Int, image: Data, completion: #escaping (_ error: Error?, _ success: Bool)->Void) {
let parameters = [
"user_id": user_id,
"newFile": image
] as [String : Any]
Alamofire.request(Router.imageUplaod(parameters: parameters))
.validate(statusCode: 200..<300)
.responseJSON { response in
switch response.result{
case .failure(let error):
completion(error, false)
print(error)
case .success(let value):
//Registered sucesfully! let json = JSON(value)
completion(nil, true)
}//Switch case
}//Alamofire
}
And I call it like this:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let chosenProfileImage = info[UIImagePickerControllerEditedImage] as! UIImage
let imageData = UIImageJPEGRepresentation(chosenProfileImage, 0.2)
profileImageView.image = chosenProfileImage
dismiss(animated: true) {
API.uploadImage(user_id: User.sharedInstance.user_id, image: imageData!) { (error, success) in
if success{
print("IMAGE UPLOADDED")
}
}
}
}
This is my backend route:
$this->post('/image/upload', function($request, $response, $args){
global $database;
$data = $request->getParsedBody();
$user_id = $data['user_id'];
$files = $request->getUploadedFiles();
if (empty($files['newfile'])) {
throw new Exception('Expected a newfile');
}
$newfile = $files['newfile'];
if ($newfile->getError() === UPLOAD_ERR_OK) {
$uploadFileName = "profileImage".$user_id.".jpg";
$newfile->moveTo("/path/to/image/$uploadFileName");
}
$database->query("UPDATE `users` SET `profile_image_url` = :image WHERE user_id = :user_id");
$database->bind(':image', $uploadFileName);
$database->bind(':user_id', $user_id);
$database->execute();
$Jsonarray['status'] = "true";
$Jsonarray['message'] = "Image Uploaded Successfully";
return $response->withStatus(200)->withHeader('Content-Type', 'application/json')->write(json_encode($Jsonarray));
});
Weird thing is that Alamofire outputs status code 500 but the server do not tell me nothing about it. The problem must be in the Swift part. So I do not know even how to debug it.
How should I solve the problem?
I solved the problem. I rebuilt it that way:
Convert image to Base64 String on client
Decode it to image on backend
Upload it
So in Swift I Convert it like this:
let chosenProfileImage = info[UIImagePickerControllerEditedImage] as! UIImage
let imageData = UIImageJPEGRepresentation(chosenProfileImage, 0.2)
profileImageView.image = chosenProfileImage
let base64String = imageData?.base64EncodedString(options: .lineLength64Characters)
And backend is pretty much same, just converted it back to file using base64_decode(); and used file_put_contents(); function to move it to server.
I am trying to connect to my localhost API (that I need to build along with the iOS swift app) that returns a json string. The API is written in Laravel 4 framework.
Here is the iOS Swift code to connect and receive the code:
func checkEmail() {
var request = NSMutableURLRequest(URL: NSURL(string: "http://localhost:3306/laravel/rojectapi/checkEmail"))
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var params = ["email":"myemail#me.com", "password":"password"] as Dictionary
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
println("Response: \(response)")
var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error: &err) as NSDictionary
println("hey")
if(err) {
println(err!.localizedDescription)
}
else {
var success = json["success"] as? Int
println("Success: \(success)")
}
})
task.resume()
}
The Laravel PHP Route:
Route::post('/checkEmail', array(
'as' => 'checkEmail',
'uses' => 'FrontEndController#checkEmail'
));
And then the front-end-controller with the checkEmail method:
public function checkEmail() {
$validator = Validator::make(Input::all(), array(
'email' => 'required|unique:users|email'
));
if($validator->fails()) {
return $validator->messages()->toJson();
} else {
return Response::json(array('success' => true));
}
}
Am I not connecting to the server correctly? I need to do so on my iPhone 5s connected to my laptop, as well as in the emulator. I have tried name.local, localhost:<post> and name.local:<post> in the URL.
Update
I got the code from this tutorial
Either pass NSJSONReadingAllowFragments (.AllowFragments in Swift) or format your JSON properly.
A simple google search revealed that error 3840 is caused due to improperly formatted JSON being passed to the iOS JSON parser.
This occurs if the API is expecting some data from you and you are not able to send it or it is not being received by the APi, try to Echo out the parameter which are received by the API as a response to your iOS Request, It may also occur if you APi Implementation has some error in it, though you may be able to see this error in your Web Console.
I have this error, when i add languages to Localizations in project. When i add language, must change from "localizable strings" to "interface builder storyboard". I mean project of iOS app. Could help you.