I'm trying to send a simple request with established VPN connection (NEPacketTunnelProvider) and I try to use URLSession.shared which sends traffic outside of the tunnel. But that resource is only available inside the tunnel.
Is there a possible way how to send it via VPN?
P.S. I use WireGuard sdk to establish vpn connection, I as I can see there isn't any api to make such a request.
Here is a request that I sent,instead of ifconfig.co I use real domain which is accessible inside the tunnel
let request = URLRequest(url: .init(string: "https://ifconfig.co/json")!)
URLSession.shared.dataTask(with: request) { data, response, error in
log("Requestion Status FINISHED")
if let data = data {
let dataString = String(data: data, encoding: .utf8)
log("DataString - \(dataString ?? "NULL")")
}
if let httpResponse = response as? HTTPURLResponse {
log("httpResponse status code - \(httpResponse.statusCode)")
}
if let error = error {
log("Error - \(error.localizedDescription)")
}
}
.resume()
Related
Does anyone have experience opening HTTP stream on iOS? I have tried multiple solutions without any luck (examples bellow).
For better context, here's example of endpoint that will stream values (as ndjson) upon opening connection:
GET /v2/path/{id}
Accept: application/x-ndjson
Attempt #1:
Issue: The completion handler is never called
let keyID = try keyAdapter.getKeyID(for: .signHash)
let url = baseURL.appendingPathComponent("/v2/path/\(keyID)")
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
urlRequest.setValue("application/x-ndjson", forHTTPHeaderField: "Accept")
session.dataTask(with: urlRequest) { data, response, error in
// This never gets called.
// I would expect that the completion is called every time backend emits new value.
}.resume()
Attempt #2:
Issue: Debugger displays this message: Connection 0: encountered error(12:1)
private var stream: URLSessionStreamTask? = nil
func startStream() {
let keyID = try keyAdapter.getKeyID(for: .signHash)
let url = baseURL.appendingPathComponent("/v2/path/\(keyID)")
let stream = session.streamTask(withHostName: url, port: 443)
// Not sure how to set headers.
// Header needs to be set so backend knows client wants to connect a stream.
self.stream = stream
stream.startSecureConnection()
startRead(stream: stream)
}
private func startRead(stream: URLSessionStreamTask) {
stream.readData(ofMinLength: 1, maxLength: 4096, timeout: 120.0) { data, endOfFile, error in
if let error = error {
Logger.shared.log(level: .error, "Reading data from stream failed with error: \(error.localizedDescription)")
} else if let data = data {
Logger.shared.log(level: .error, "Received data from stream (\(data.count)B)")
if !endOfFile {
self.startRead(stream: stream)
} else {
Logger.shared.log(level: .info, "End of file")
}
} else {
Logger.shared.log(level: .error, "Reading stream endup in unspecified state (both data and error are nil).")
}
}
}
Does anyone have experience with this? How can I keep HTTP connection open and listen to a new values that backend is streaming?
iOS can connect to HTTP stream using now deprecated API URLConnection. The API was deprecated in iOS 9, however it's still available for use (and will be in iOS 16 - tested).
First you need to create URLRequest and setup the NSURLConnection:
let url = URL(string: "\(baseURL)/v2/path/\(keyID)")!
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/x-ndjson", forHTTPHeaderField: "Accept")
let connnection = NSURLConnection(request: urlRequest, delegate: self, startImmediately: true)
connnection?.start()
Notice that the argument for delegate in the code above is of type Any which doesn't help to figure out what protocol(s) to implement. There are two - NSURLConnectionDelegate and NSURLConnectionDataDelegate.
Let's receive data:
public func connection(_ connection: NSURLConnection, didReceive data: Data) {
let string = String(data: data, encoding: .utf8)
Logger.shared.log(level: .debug, "didReceive data:\n\(string ?? "N/A")")
}
Then implement a method for catching errors:
public func connection(_ connection: NSURLConnection, didFailWithError error: Error) {
Logger.shared.log(level: .debug, "didFailWithError: \(error)")
}
And if you have custom SSL pinning, then:
public func connection(_ connection: NSURLConnection, willSendRequestFor challenge: URLAuthenticationChallenge) {
guard let certificate = certificate, let identity = identity else {
Logger.shared.log(level: .info, "No credentials set. Using default handling. (certificate and/or identity are nil)")
challenge.sender?.performDefaultHandling?(for: challenge)
return
}
let credential = URLCredential(identity: identity, certificates: [certificate], persistence: .forSession)
challenge.sender?.use(credential, for: challenge)
}
There is not much info on the internet, so hopefully it will save someone days of trial and error.
I am trying to make a simple prototype with both a server (EC2 dev server) and a client. Where the server just has a hard IPv4 address (numbers) and send http results upon requests and the client is a prototype iOS 12+ app where I simply want to retrieve data from the server that is using http. How can I do so? Thanks in advance!
Read up on URLSession and how to fetch webside data into memory.
func startLoad() {
let url = URL(string: "http://IPADDRESS/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
// Data is in the data-variable.
if let mimeType = httpResponse.mimeType, mimeType == "text/html",
let data = data,
let string = String(data: data, encoding: .utf8) {
// Avoid using the main-thread to return any data.
DispatchQueue.main.async {
// Return data to use in view/viewcontroller
self.webView.loadHTMLString(string, baseURL: url)
}
}
}
task.resume()
}
Other things you probably will want to get into is MVVM/MVC for managing data between views and Codables.
Hai I am trying to pass some parameters of string in Http post request. I have created a dictionary and then converted that dictionary to data and set as httpBody.But when I looked on our server nothing has been passd I mean parameters are empty.Why? What mistake i am doing?Please help me to find out.Thanks in advance.
func receiptValidation(productId:String,requestFrom:String)
{
let SUBSCRIPTION_SECRET = "mySecretKey"
let defaults = UserDefaults.standard
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do {
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
//let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
print(base64encodedReceipt!)
let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let requestDataString=String(describing: requestData)
let URLForApplication:String = String(format:"%#/api/validate-receipt-data",opcodeDetails["apiProxyBaseUrl"]!) // this works but as noted above it's best to use your own trusted server
SwiftyBeaver.info("URLForApplication Path:\n\(URLForApplication)")
let url:URL! = URL.init(string: URLForApplication)
var request = URLRequest.init(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let configure = URLSessionConfiguration.background(withIdentifier: Bundle.main.bundleIdentifier!)
session1=URLSession(configuration: .default, delegate: applicationDelegate.application, delegateQueue: OperationQueue.main)
var postString =
["receiptData":requestDataString,
"deviceType":"IOS",
"subscriberId":encodeString(normalString: defaults.array(forKey: "userDetails")?.first as! String),
"password":encodeString(normalString: defaults.array(forKey: "userDetails")?.last as! String),
"productId":encodeString(normalString: productId ),
"code":opcodeDetails["opCode"]!]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: postString, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session1?.dataTask(with: request) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
// if you are using your server this will be a json representation of whatever your server provided
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task?.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
}
and what error i am getting is Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
You don’t say, but I’m assuming you’re getting this error where you print “json serialization failed with error”. If so, CZ54 is right, that your response is obviously not JSON. So, where you print that error, also print the header and body to see what the server actually returned, if anything:
print("response header:", response ?? "No response")
print("response body:", String(data: data, using: .utf8) ?? "No body")
The response header will include the status code (which should be something in the 200...299 range). If it’s not in that range, the status code will tell you the broad nature of the problem.
And regarding the response body, Sometimes (esp in development environments) if the server choked on something, it may return a HTML page outlining the nature of the problem (though, admittedly, in other cases, it only outputs the fact that there was an error, but not the details, and you’ll need to go into the server error logs to figure out what went wrong).
Looking at the specifics of the response, like above, is is your first step. Or you can achieve this by running the app on a simulator, and watching the request and the response in a tool like Charles or Wireshark. Once you get them up and running, these are great tools for inspecting requests and responses.
The next question is why the server generated the response that it did. As a general rule, while these sorts of problems can be a result of some server mistake, the more likely scenario is that the request wasn’t formed correctly and the server didn’t know how to handle it. Looking at the response (or looking at your server’s error logs) often provides good clues. But there’s no way anyone can help you on the basis of the information provided.
I am new to the Alamofire which I am using to make a request to rest api.
Now while making request there can be two issues and I want to know how to handle those issues using Alamofire.
1) What if user have submit data and Internet is slow and it takes longer to get response from server. In such case how one should insure that whether a request is successful or not. Or we can show some message to user that Internet is slow so he can wait for long response.
2) What if internet speed is ok but the server is down or it is taking longer to send a response how should we handle these situations in our application. And maintain integrity of data.
Following is an example of how I am using Alamofire to make request.
static func getUserListFromServer(completion: #escaping(Bool,[Users]?,String?)->() ){
Alamofire.request(APPURL.getFeedbackUserList, method: .get, parameters: nil, encoding: URLEncoding.queryString, headers: nil).responseJSON { (responseJson) in
responseJson.result.ifSuccess {
do {
// Decode data to object
let jsonDecoder = JSONDecoder()
let root = try jsonDecoder.decode(Root.self, from: responseJson.data!)
if let users = root.users{
completion(true,users,nil)
}else{
completion(false,nil,"No users found.")
}
}
catch let err {
print(err.localizedDescription)
completion(false,nil,err.localizedDescription)
}
}
responseJson.result.ifFailure {
completion(false,nil,responseJson.result.error?.localizedDescription)
}
}
}
You're actually implementing alamofire correctly I would already be about the connection and server problem if network speed is slow. Try using a different endpoint from a different server to test the speed.If its faster then the problem is on your server. Since your implementation is correct then definitely it is server problem. alamofire has nothing to do on handling the issue if the problem is on the server or connection.
You can increase timeout for slow response API calls.
static func getUserListFromServer(completion: #escaping(Bool,[Users]?,String?)->() ){
let request = NSMutableURLRequest(url: APPURL.getFeedbackUserList)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = 10 // change time out according to your need
let values = ["key": "value"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values, options: [])
Alamofire.request(request as! URLRequestConvertible).responseJSON { (responseJson) in
responseJson.result.ifSuccess {
do {
// Decode data to object
let jsonDecoder = JSONDecoder()
let root = try jsonDecoder.decode(Root.self, from: responseJson.data!)
if let users = root.users{
completion(true,users,nil)
}else{
completion(false,nil,"No users found.")
}
}
catch let err {
print(err.localizedDescription)
completion(false,nil,err.localizedDescription)
}
}
responseJson.result.ifFailure {
completion(false,nil,responseJson.result.error?.localizedDescription)
}
}
}
I'm having a scenario in which
1) I'm registering my mobile number using post request.
2) On successfull registration will get OTP on my registered mobile number.
3) After receiving the OTP, I have to verify this OTP on the server.
4) And then server will send me the pin on my registered mobile number,using this pin will able to login my app.
Now coming to the issue after on successfull registration I received an OTP but how to verify this otp and get the pin on my number?.
I have used below code for this. 0123456789 is my registered mobile number and 3588 is the code have received on successfull registration.
#IBAction func verifyotp(_ sender: Any) {
var request = URLRequest(url: URL(string: "myurl/verifyotp?mobileno=0123456789&otp=3588")!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
DispatchQueue.main.async {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.present(vc, animated: true)
}
}
task.resume()
}
But somehow It didnt match the scenario and m receiving the response like:
Optional("{\"Success\":\"No Active account found.\"}")
Looking for a help.Thanks in advance.