David Seek
David Seek

Reputation: 17132

How to use a Proxy Server with Alamofire 4 and Swift 3

I'm trying to use my Proxy for an API request that needs a specified IP. To debug my issue, I'm requesting the IP from a webservice.

This is my current code:

import UIKit
import Alamofire

class ViewController: UIViewController {

    var requestManager = Alamofire.SessionManager.default
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)
        
        var proxyConfiguration = [NSObject: AnyObject]()
        proxyConfiguration[kCFNetworkProxiesHTTPProxy] = "http://[email protected]" as AnyObject?
        proxyConfiguration[kCFNetworkProxiesHTTPPort] = "9293" as AnyObject?
        proxyConfiguration[kCFNetworkProxiesHTTPEnable] = 1 as AnyObject?
        
        let cfg = Alamofire.SessionManager.default.session.configuration
        cfg.connectionProxyDictionary = proxyConfiguration

        let ip = URL(string: "https://api.ipify.org?format=json")
        
        requestManager = Alamofire.SessionManager(configuration: cfg)
        requestManager.request(ip!).response { response in
            print("Request: \(response.request)")
            print("Response: \(response.response)")
            print("Error: \(response.error)")
            
            if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                print("Data: \(utf8Text)")
            }
        }
    }
}

The problem: the responded IP is the same with or without the proxyConfiguration.

PS: physical device used.

Upvotes: 16

Views: 4555

Answers (3)

Vasily  Bodnarchuk
Vasily Bodnarchuk

Reputation: 25294

Base on Fraser answer and How can I get my external IP address in a shell script? question

Details

  • Swift 5.3
  • Version 12.4 (12D4e)
  • Alamofire 5

Full Sample

NetworkService.swift

import Foundation
import Alamofire

struct Proxy {
    let host: String
    let port: Int
}

class NetworkService {
    
    // Pick free some proxies from here https://free-proxy-list.net if current do not work
    private let listOfProxies = [
        Proxy(host: "67.43.224.131", port: 3128), // Canada
        Proxy(host: "167.172.180.40", port: 44129), // Germany
        Proxy(host: "185.236.202.205", port: 3128), // Austria
    ]
    
    private(set) var session: Session!
    private var proxy: Proxy?
    init () { resetSessionManager() }

    func setRandomProxy() {
        self.proxy = listOfProxies.randomElement()
        resetSessionManager()
    }

    private
    func resetSessionManager() {
        let config = Session.default.session.configuration
        if let proxy = proxy {
            config.connectionProxyDictionary = [
                "HTTPEnable": 1,
                "HTTPProxy": proxy.host ,
                "HTTPPort": proxy.port,
                "HTTPSEnable": true,
                "HTTPSProxy": proxy.host,
                "HTTPSPort": proxy.port
            ]
        } else {
            config.connectionProxyDictionary = [:]
        }
        self.session = Session(configuration: config)
    }
    
    func request(url: URLConvertible,
                 method: Alamofire.HTTPMethod = .get,
                 parameters: Parameters? = nil,
                 encoding: ParameterEncoding = URLEncoding.default,
                 headers: HTTPHeaders? = nil,
                 completion: @escaping (Result<Data, Error>) -> Void) {
        let request = session.request(url, method: method,
                                      parameters: parameters,
                                      encoding: encoding,
                                      headers: headers)
        request.response { respionse in
            if let error = respionse.error {
                completion(.failure(error))
            } else if let data = respionse.data {
                completion(.success(data))
            }
        }
    }
    func request<T: Decodable>(url: URLConvertible,
                               method: Alamofire.HTTPMethod = .get,
                               parameters: Parameters? = nil,
                               encoding: ParameterEncoding = URLEncoding.default,
                               headers: HTTPHeaders? = nil,
                               completion: @escaping (Result<T, Error>) -> Void) {
        request(url: url, method: method,
                parameters: parameters,
                encoding: encoding,
                headers: headers) { result in
            switch result {
            case .failure(let error): completion(.failure(error))
            case .success(let data):
                do {
                    let object = try JSONDecoder().decode(T.self, from: data)
                    completion(.success(object))
                } catch let error {
                    completion(.failure(error))
                }
            }
        }
    }
}

GeoIP.swift

import Foundation

struct GeoIP: Codable {
    let lat: Double
    let lon: Double
    let zip: String
    let query: String
    let city: String
    let regionName: String
    let country: String
    let timezone: String
}

extension GeoIP: CustomStringConvertible {
    var description: String {
        "\(city), \(regionName), \(country), \(zip)\nIP:\(query)\nCoordinates:(\(lat),\(lon))\nTimezone: \(timezone)"
    }
}

ViewController.swift

class ViewController: UIViewController {
    
    private let networkService = NetworkService()
    private weak var button: UIButton!
    private weak var activityIndicatorView: UIActivityIndicatorView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: view.frame)
        button.setTitleColor(.blue, for: .normal)
        button.setTitle("Pick random proxy and get my ip", for: .normal)
        button.addTarget(self, action: #selector(buttonTouchedUpInside(source:)), for: .touchUpInside)
        view.addSubview(button)
        self.button = button
        
        let activityIndicatorView = UIActivityIndicatorView(frame: view.frame)
        view.addSubview(activityIndicatorView)
        self.activityIndicatorView = activityIndicatorView
    }
    
    @objc func buttonTouchedUpInside(source: UIButton) {
        source.isHidden = true
        activityIndicatorView.startAnimating()
        networkService.setRandomProxy()
        showMyGeoIpInfo()
    }
}

extension ViewController {
    
    private func showMyGeoIpInfo() {
        let group = DispatchGroup()
        group.enter()
        group.enter()
        group.enter()
        var httpIP: String?
        var httpsIP: String?
        var geoIp: GeoIP?
        group.notify(queue: .main) { [weak self] in
            guard let self = self else { return }
            let _httpIP = httpIP ?? "nil"
            let _httpsIP = httpsIP ?? "nil"
            let geoIpDescription = geoIp?.description ?? "nil"
            let message = "HTTP request IP: \(_httpIP)\nHTTPS request IP: \(_httpsIP)\n GeoIP: \(geoIpDescription)"
            self.showAlert(title: "GeoIP", message: message)
            self.button.isHidden = false
            self.activityIndicatorView.stopAnimating()
        }
        
        // Get my IP on http request
        getSimpleText(from: "http://ipecho.net/plain") { text in
            httpIP = text
            group.leave()
        }
        
        // Get my IP on https request
        getSimpleText(from: "https://icanhazip.com") { text in
            httpsIP = text
            group.leave()
        }
        

        // Get my GeoIp info
        networkService.request(url: "http://ip-api.com/json/",
                               encoding: JSONEncoding.default) { (response: Result<GeoIP, Error>) in
            defer { group.leave() }
            switch response {
            case .failure(let error): print("Error: \(error)")
            case .success(let value): geoIp = value
            }
        }
    }
    
    private func getSimpleText(from url: String, completion: @escaping (String?) -> Void) {
        networkService.request(url: "https://icanhazip.com") { result in
            switch result {
            case .failure(let error):
                print("Error: \(error)")
                completion(nil)
            case .success(let data):
                let text = String(decoding: data, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)
                completion(text)
            }
        }
    }

    private func showAlert(title: String?, message: String?) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
        present(alertController, animated: true, completion: nil)
    }
}

Screenshots

enter image description here enter image description here enter image description here

Upvotes: 0

Fraser
Fraser

Reputation: 998

Based off Manishg's answer I use the following to avoid warnings

let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

var proxyConfiguration = [String: AnyObject]()
proxyConfiguration.updateValue(1 as AnyObject, forKey: "HTTPEnable")
proxyConfiguration.updateValue("eu-west-static-01.quotaguard.com" as AnyObject, forKey: "HTTPProxy")
proxyConfiguration.updateValue(9293 as AnyObject, forKey: "HTTPPort")
proxyConfiguration.updateValue(1 as AnyObject, forKey: "HTTPSEnable")
proxyConfiguration.updateValue("eu-west-static-01.quotaguard.com" as AnyObject, forKey: "HTTPSProxy")
proxyConfiguration.updateValue(9293 as AnyObject, forKey: "HTTPSPort")
configuration.connectionProxyDictionary = proxyConfiguration

sharedManager = Alamofire.SessionManager(configuration: configuration)

The https constants have been deprecated and could be removed at anytime. By using the string values, the code might break but it won't crash

Upvotes: 5

manishg
manishg

Reputation: 9828

I think the working (supposed to be deprecated) keys are:

kCFStreamPropertyHTTPSProxyHost
kCFStreamPropertyHTTPSProxyPort

Could you try this code?

import UIKit
import Alamofire

class ViewController: UIViewController {

    var requestManager = Alamofire.SessionManager.default

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)

         var proxyConfiguration = [NSObject: AnyObject]()
         proxyConfiguration[kCFNetworkProxiesHTTPProxy] = "eu-west-static-01.quotaguard.com" as AnyObject?
         proxyConfiguration[kCFNetworkProxiesHTTPPort] = "9293" as AnyObject?
         proxyConfiguration[kCFNetworkProxiesHTTPEnable] = 1 as AnyObject?
         proxyConfiguration[kCFStreamPropertyHTTPSProxyHost as String] = "eu-west-static-01.quotaguard.com"
         proxyConfiguration[kCFStreamPropertyHTTPSProxyPort as String] = 9293
         proxyConfiguration[kCFProxyUsernameKey as String] = xxx
         //proxyConfiguration[kCFProxyPasswordKey as String] = "pwd if any"
        let cfg = Alamofire.SessionManager.default.session.configuration
        cfg.connectionProxyDictionary = proxyConfiguration

        let ip = URL(string: "https://api.ipify.org?format=json")

        requestManager = Alamofire.SessionManager(configuration: cfg)
        requestManager.request(ip!).response { response in
            print("Request: \(response.request)")
            print("Response: \(response.response)")
            print("Error: \(response.error)")

            if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                print("Data: \(utf8Text)")
            }
        }
    }
}

Also please make sure your proxy server is configured to handle https requests.

Note: It might give deprecated warning for those keys but keys are still working (see https://forums.developer.apple.com/thread/19356#131446)

Note: I posted the same answer here. Posting the same here as it was applicable here as well. Alamofire is using same URLSessionConfiguration.

Upvotes: 15

Related Questions