Swift Alamofire discover in LAN - request

In my LAN, I run the following Terminal command on 6 MAC computers to host a folder in which is discover.xml:
python -m SimpleHTTPServer 17171
On my app having Alamofire installed, I run this code in order to get discover.xml content:
extension String {
func url(file: String) -> URL {
return URL(string: "http://\(self)/\(file)?rand=\(arc4random())")! // `rand` to make sure no cached content
}
}
func discover(from ip: String, completion: #escaping () -> Void) {
let url = ip.url(file: "discover.xml") // ip passed in: "192.168.1.15:17171" for ex
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.timeoutInterval = 6.0 // !
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 6.0
manager.session.configuration.timeoutIntervalForResource = 6.0
manager.request(request).responseData { (response) in
switch response.result {
case .success(let d):
// do my stuff on `multiX`
case .failure(let error):
print("failed with IP: \(ip) => \(error)")
}
}
}
Below is how I discover:
private func discover(on subnet: String) { // subnet: 192.168.1
let group = DispatchGroup()
for i in 1..<256 {
let ip = subnet + ".\(i):17171"
group.enter()
discover(from: ip) {
group.leave()
}
}
group.notify(queue: DispatchQueue.main) { [weak self] in
guard let strongSelf = self else { return }
print("all done: \(strongSelf.multiX.count)") // just can discover 2, or 3, or 4 !!
strongSelf.tableView.reloadData()
}
}
Why most of the time, I cannot get 6 discover.xml from the 6 MAC computers? (I can only get 2 or 3, or 4).
The result that I get is not persistent. Say I have 6 MAC computers running python -m ... named: A, B, C, D, E, F. Sometimes I can only get: A, B, D, sometimes C, F, ...
Requesting the remaining computers which host discover.xml always have error failed with IP: x.x.x.x:17171 => Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." (I'm sure those missing computers have acquired the IP successfully)
Is it ok to discover the whole subnet using my method above? (i.e. using for loop to iterate each ip). Is 6 seconds a reasonable timeout?
EDIT:
I have tried to allow all in ATS, but things are not better
What actually blocks me from requesting discover.xml on the MAC computers?
Thanks,

Related

How to convert string containing array to array in Swift?

I have a string in Swift that contains an array in it. Is it possible to convert the string into an array? All I have found on the internet is converting "abc" to ["a","b","c"], which I would not like to do.
String: "[\"value1\",\"value2\",\"value3\"]"
Result: ["value1","value2","value3"]
I am getting the string from a web request. The code for the request is here:
func webRequest(uri:String)->String{
var value = "";
let request = URLRequest(url: NSURL(string: uri)! as URL)
do {
let response: AutoreleasingUnsafeMutablePointer<URLResponse?>? = nil
let data = try NSURLConnection.sendSynchronousRequest(request, returning: response)
value = String(data: data, encoding: .utf8)!;
} catch _ {
}
return value;
}
First off, the problem here is not converting your string into an array. The problem is getting the array from the web request in the first place.
Let me update your web request function.
func webRequest(url: URL, completion: ([String]?) -> () { // I have updated this function to be asynchronous
let dataTask = URLSession.shared.dataTask(with: url) {
data, urlResponse, error in
// you might want to add more code in here to check the data is valid etc...
guard let data = data,
let arrayOfStrings = JSONDecoder().decode([String].self, from: data) else {
// something went wrong getting the array of strings so return nil here...
completion(nil)
return
}
completion(arrayOfStrings)
}
dataTask.resume()
}
Using this code instead of the code in your question you now have an asynchronous function that will not block the app and one that will pass your array of strings into the completion.
You can now run it like this...
webRequest(url: someURL) { strings in
guard let strings = strings else {
// strings is nil because something went wrong with the web request
return
}
print(strings)
}
Creating the URL
In your question you have this code... NSURL(string: someString)! as URL
You can change this to... let url = URL(string: someString)
Quick side note
Careful where you find tutorials and using code you find on the web. The code used in this question is very old. (at least 4 or 5 years "out of date").
If you're looking for tutorials to help with Swift then some recommendations are...
Ray Wenderlich
Hacking with swift

Loading page faster by storing keys into sessions golang

I am trying to load dynamic page faster. I am making twitter clone as a learning assignment. I am following below approach
When somebody tweets, store the tweet in datastore and safe the same in memcache { key.string(), json.Marshal(tweet) }
I push the tweet in User home time line. The home time line is a []*datastore.Key, which is stored in user session (which get copied in memcache and then in DB).
When user open her homepage, the homepage try to get the Keys from session, if not found then it make a datastore query.
Once i get the keys I fetch the tweets from memcache (if not then from db)
I am stuck at step 3.
In first case I am getting the correct information but in string slices (not in []*datastore.Key).
In second case I getting this error
2016/09/03 17:23:42 http: panic serving 127.0.0.1:47104: interface
conversion: interface is []interface {}, not []datastore.Key
Kindly help me where I am going wrong and is there a better way.
case 1
func GetKeys(req *http.Request, vars ...string) []interface{} {
//GetKeys - get the keys
s, _ := GetGSession(req)
var flashes []interface{}
key := internalKey
if len(vars) > 0 {
key = vars[0]
}
if v, ok := s.Values[key]; ok {
// Drop the flashes and return it.
// delete(s.Values, key)
flashes = v.([]interface{})
}
return flashes
}
Case2
//GetHTLKeys - get the hometimeline keys
func GetHTLKeys(req *http.Request, vars ...string) []datastore.Key {
s, _ := GetGSession(req)
var keyList []datastore.Key
key := internalKey
if len(vars) > 0 {
key = vars[0]
}
if v, ok := s.Values[key]; ok {
keyList = v.([]datastore.Key)
}
return keyList
}
Your issue is that you can't assert an []interface{} is a []datastore.Key. This is because they are different.
What you can do at this point, is type assert v to a []interface{} then loop through the slice and type assert each element.
Here is an example demonstrating this (playground version):
type I interface {
I()
}
type S struct{}
func (s S) I() {
fmt.Println("S implements the I interface")
}
// Represents you getting something out of the session
func f(s S) interface{} {
return []interface{}{s}
}
func main() {
v := f(S{})
//v2 := v.([]I) would panic like in your example.
v2, ok := v.([]interface{})
if !ok {
// handle having a non []interface{} as a value
}
for _, v3 := range v2 {
v4, ok := v3.(I)
if !ok {
// handle having a non-I in the slice
}
v4.I() //you definitely have an I here
}
}

Swift array empty on return [duplicate]

I am trying to get learn how to use AlamoFire and I am having trouble.
My method so far is as follows:
func siteInfo()->String?{
var info:NSDictionary!
var str:String!
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {(request, response, JSON, error) in
info = JSON as NSDictionary
str = info["access_key"] as String
//return str
}
return str
}
This returns nil which is a problem. From what I have read here, this is because the request can take a while so the closure doesn't execute till after the return. The suggested solution of moving the return into the closure does not work for me and the compiler just yells (adding ->String after (request,response,JSON,error) which gives "'String' is not a subtype of void"). Same goes for the other solution provided.
Any ideas? Even some source code that is not related to this problem, that uses AlamoFire, would be helpful.
Thanks!
One way to handle this is to pass a closure (I usually call it a completionHandler) to your siteInfo function and call that inside Alamofire.request's closure:
func siteInfo(completionHandler: (String?, NSError?) -> ()) -> () {
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let str = info?["access_key"] as? String // str will be nil if info is nil or the value for "access_key" is not a String
completionHandler(str, error)
}
}
Then call it like this (don't forget error handling):
siteInfo { (str, error) in
if str != nil {
// Use str value
} else {
// Handle error / nil value
}
}
In the comments you asked:
So how would you save the info you collect from the get request if you
can only do stuff inside the closure and not effect objects outside of
the closure? Also, how to keep track to know when the request has
finished?
You can save the result of the get request to an instance variable in your class from inside the closure; there's nothing about the closure stopping you from doing that. What you do from there really depends on, well, what you want to do with that data.
How about an example?
Since it looks like you're getting an access key form that get request, maybe you need that for future requests made in other functions.
In that case, you can do something like this:
Note: Asynchronous programming is a huge topic; way too much to cover here. This is just one example of how you might handle the data you get back from your asynchronous request.
public class Site {
private var _accessKey: String?
private func getAccessKey(completionHandler: (String?, NSError?) -> ()) -> () {
// If we already have an access key, call the completion handler with it immediately
if let accessKey = self._accessKey {
completionHandler(accessKey, nil)
} else { // Otherwise request one
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let accessKey = info?["access_key"] as? String // accessKey will be nil if info is nil or the value for "access_key" is not a String
self._accessKey = accessKey
completionHandler(accessKey, error)
}
}
}
public func somethingNeedingAccessKey() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Use accessKey however you'd like here
println(accessKey)
} else {
// Handle error / nil accessKey here
}
}
}
}
With that setup, calling somethingNeedingAccessKey() the first time will trigger a request to get the access key. Any calls to somethingNeedingAccessKey() after that will use the value already stored in self._accessKey. If you do the rest of somethingNeedingAccessKey's work inside the closure being passed to getAccessKey, you can be sure that your accessKey will always be valid. If you need another function that needs accessKey, just write it the same way somethingNeedingAccessKey is written.
public func somethingElse() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Do something else with accessKey
} else {
// Handle nil accessKey / error here
}
}
}

Load numeric array from text file in Swift

I'm using a Swift function that successfully loads data from a text file into an Double array, but it is slow. Is there a way to load numeric data directly without using the String initializer that may be faster? Or any other suggestions to speed this up?
func arrayFromContentsOfFileWithPath(path: String) -> [Double]? {
do {
let content = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding)
let stringArray = content.componentsSeparatedByString("\n").map{
$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
}
return stringArray.map{Double($0)}.flatMap{$0}
} catch _ as NSError {
return nil
}
}
EDIT:
To quantify things a bit, the data file is 10000 samples and the load time is 0.183 s for a single load (according to a measureBlock in my unit tests). In comparison, MATLAB loads the file in 0.033 s. Here are the first few samples of the data:
8.1472369e-01
9.0579194e-01
1.2698682e-01
9.1337586e-01
6.3235925e-01
9.7540405e-02
2.7849822e-01
5.4688152e-01
9.5750684e-01
9.6488854e-01
UPDATE:
Following #appzYourLife's advice to combine the mappings (I used .flatMap{Double($0)}) and to use a Release build, the load time is now 0.119 s. Much better, but still about 4x the time of MATLAB, which was very unexpected.
You can read data quite fast with NSScanner(). The scanDouble()
method skips leading whitespace, so no intermediate strings or arrays
are needed:
func arrayFromContentsOfFileWithPath(path: String) -> [Double]? {
do {
let content = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding)
let scanner = NSScanner(string: content)
var doubleArray = [Double]()
var value = 0.0
while scanner.scanDouble(&value) {
doubleArray.append(value)
}
return doubleArray
} catch _ as NSError {
return nil
}
}
In my test, reading 10,000 samples in Release configuration is
done in 0.0034 seconds, compared to 0.077 seconds with your code,
that is an improvement of more than factor 20.
Update for Swift 3:
func arrayFromContentsOfFileWithPath(path: String) -> [Double]? {
guard let content = try? String(contentsOfFile:path, encoding: .utf8) else {
return nil
}
let scanner = Scanner(string: content)
var doubleArray = [Double]()
var value = 0.0
while scanner.scanDouble(&value) {
doubleArray.append(value)
}
return doubleArray
}

CookieJar does not catch incoming cookies

I'm trying to have Go submit a form on a webpage for me to simulate a login. From there I'm trying to use the cookies to keep a persistent session for one more call to a sub-page.
I'm able to successfully do the log in with no issues, I'm just having issues catching the cookies being set by the returning server. I'm wondering if it's because their login script does several redirects? (I am getting an output).
Any ideas why I'm not catching the cookies being returned?
Here is the code I'm using:
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
)
type Jar struct {
lk sync.Mutex
cookies map[string][]*http.Cookie
}
var CookieJar *Jar
func NewJar() *Jar {
jar := new(Jar)
jar.cookies = make(map[string][]*http.Cookie)
return jar
}
// SetCookies handles the receipt of the cookies in a reply for the
// given URL. It may or may not choose to save the cookies, depending
// on the jar's policy and implementation.
func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
jar.lk.Lock()
jar.cookies[u.Host] = cookies
jar.lk.Unlock()
}
// Cookies returns the cookies to send in a request for the given URL.
// It is up to the implementation to honor the standard cookie use
// restrictions such as in RFC 6265.
func (jar *Jar) Cookies(u *url.URL) []*http.Cookie {
return jar.cookies[u.Host]
}
func NewClient() *http.Client {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
}
CookieJar = NewJar()
client := &http.Client{
Transport: tr,
CheckRedirect: nil,
Jar: CookieJar,
}
return client
}
func Login() {
client := NewClient()
api := "https://www.statuscake.com/App/"
uri, _ := url.Parse("https://www.statuscake.com")
fmt.Printf("uri: %s\n", uri)
values := url.Values{}
values.Add("username", username)
values.Add("password", password)
values.Add("Login", "yes")
values.Add("redirect", "")
str := values.Encode()
req, err := http.NewRequest("POST", api, strings.NewReader(str))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "text/html")
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.65 Safari/537.36")
cookies := CookieJar.Cookies(uri)
for i := 0; i < len(cookies); i++ {
fmt.Printf("Cookie[%d]: %s", i, cookies[i])
req.AddCookie(cookies[i])
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Printf("Response: %v\n", resp)
fmt.Printf("Response.Cookies: %v\n", resp.Cookies())
cookies = resp.Cookies()
CookieJar.SetCookies(uri, cookies)
defer resp.Body.Close()
if resp.StatusCode == 200 {
fmt.Printf("\n\n-----\n")
fmt.Println("HTTP Code: ", resp.StatusCode)
fmt.Println("Response Cookies: ", resp.Cookies())
fmt.Println("Request Headers: ", req.Header)
fmt.Println("Request Cookies: ", req.Cookies())
fmt.Println("Response Headers: ", resp.Header)
fmt.Printf("-----\n\n")
}
}
Not sure where the problem really is, but two notes:
There is no need to populate the request manually with cookies if your Client has a working Jar. The client should handle all cookie stuff transparently for you: It extracts cookies from the response and store in the jar and populates the response with cookies from the jar (even during redirects). Maybe this overwrites your manually set cookies.
Do not roll your own CookieJar implementation. Cookie handling is awful (you may believe me). Just use http://golang.org/pkg/net/http/cookiejar/ maybe in combination with code.google.com/p/go.net/publicsuffix

Resources