Will Roscoe Will Roscoe - 3 months ago 17
iOS Question

HTTP post sent twice using both JustHTTP and Alamofire

I'm trying to HTTP upload (to an Windows IIS server) an image and form parameters using Swift 2 in iOS 9 and for some reason it's sending the post twice even though I'm only calling the function one on a button click.

I have checked that the button is not firing more than once.

I'm posting to HTTPS (I have tried with HTTP with the same result) with basic auth. It is happening using both JustHttp (version 0.3) and Alamofire 3.

Watching the upload progress in Alamofire (and JustHTTP) I can see it updating the

totalBytesExpectedToWrite
and then at some point through the upload,
totalBytesExpectedToWrite
doubles in value.

I have tried debugging both JustHTTP and Alamofire to see why this is happening but can't find where it is happening.

The JustHttp code I'm using is:

Just.post(
"https://my.url.com",
auth:("xxxxxxxx", "xxxxxxxxx"),
timeout: 20,
data:["Name": tfName.text! as AnyObject,
"Email": tfEmail.text! as AnyObject,
files:[
"file":HTTPFile.Data("photo.jpg", imagedata!, nil)
],
asyncProgressHandler: {(p) in
print(p.type) // either .Upload or .Download
if (p.type == ProgressType.Upload)
{
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.textView.titleLabel.text = "Sending Entry...\(Int(p.percent*100))%"
}
}
},
asyncCompletionHandler: {(r) in
dispatch_async(dispatch_get_main_queue()) {
print(r.statusCode)
}
})


The Alamofire code is:

Alamofire.upload(Method.POST, "https://the.url.com", multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: imagedata!, name: "file", fileName: "photo.jpg", mimeType: "image/png")
multipartFormData.appendBodyPart(data: "My Name".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name: "Name")
multipartFormData.appendBodyPart(data: "My Email address".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name: "Email")
},
encodingCompletion: { encodingResult in
print("encoding done")
switch encodingResult {
case .Success(let upload, _, _):
upload.authenticate(user: "xxxxxx", password: "xxxxxxxxxx")
.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
print("a: \(bytesWritten) b: \(totalBytesWritten) c: \(totalBytesExpectedToWrite)")

}
upload.responseString { request, response, responseBody in
print(request)
print(response!.statusCode)
switch responseBody {
case Result.Success(let responseValue):
print(responseValue)

case Result.Failure(_, let error as NSError):
print(error)

default: break

}
}
case .Failure(let encodingError):
print(encodingError)
}
})


Update:

I have now tried it using Alamofire without the authentication and it is working as expected i.e. only sending only the whole request once.

Answer

Ok, I have solved the issue. I needed to add to following headers in addition to the auth parameters (in both Just and Alamofire). I got the code from the Alamofire docs but didn't realise I needed to add the header and include the auth property.

let user = "xxxxxxx"
let password = "xxxxxxx"
let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)!
let base64Credentials = credentialData.base64EncodedStringWithOptions([])
let headers = ["Authorization": "Basic \(base64Credentials)"]

Then for Alamofire:

Alamofire.upload(Method.POST, "https://my.url.com", headers: headers, multipartFormData: [...]

For JustHttp:

Just.post("https://my.url.com",
        auth:(user, password),
        headers: headers,
        [...]

For completeness sake, the remote webserver is IIS 7.5 running Asp.Net C# MVC 5 with the following Auth action filter:

public class BasicAuthenticationAttribute : ActionFilterAttribute
{
    public string BasicRealm { get; set; }
    protected string Username { get; set; }
    protected string Password { get; set; }

    public BasicAuthenticationAttribute(string username, string password)
    {
        this.Username = username;
        this.Password = password;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        var auth = req.Headers["Authorization"];
        if (!String.IsNullOrEmpty(auth))
        {
            var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
            var user = new { Name = cred[0], Pass = cred[1] };
            if (user.Name == Username && user.Pass == Password) return;
        }
        var res = filterContext.HttpContext.Response;
        res.StatusCode = 401;
        res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "content.imaginecruising.co.uk"));
        res.End();
    }
}

C# MVC usage:

[BasicAuthenticationAttribute("myusername", "mypassword", BasicRealm = "my.domain.com")]
public ActionResult MyAction()
{