I want to request the youtube-data-api v3 from my tvOS App. To do so I wrote the following class:
class YoutubeSearch {
static let sharedClient = YoutubeSearch()
private var task: NSURLSessionDataTask!
func getVideoNSURLsForTitle(title: String, completionHandler: ([String], NSError?) -> Void ) -> NSURLSessionTask {
// URL
let urlString = makeURL(title)
let url = NSURL(string: youtubeApi)
// cancel task, if there is already one
task?.cancel()
// setup new request
let request = NSURLRequest(URL: url!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// No errors occured
do {
// some JSON processing
// complitionHandler is returned here
} catch let parseError {
// Some other error
print(parseError)
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("YoutubeSearch Error: '\(jsonStr)'")
}
}
// execute request
task.resume()
return task
}
}
The code works fine in the simulator. I get a valid JSON. But on tvOS it throws an error telling me the certificate is invalid:
NSUnderlyingError=0x13e207d70 {Error Domain=kCFErrorDomainCFNetwork Code=-1202 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=, _kCFNetworkCFStreamSSLErrorOriginalValue=-9814, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9814, kCFStreamPropertySSLPeerCertificates={type = immutable, count = 3, values = (
0 :
1 :
2 :
)}}}, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “www.googleapis.com” which could put your confidential information at risk., NSErrorFailingURLKey=https://www.googleapis.com/youtube/v3/search?videoEmbeddable=true&videoType=any&videoDefinition=any&order=relevance&part=snippet&videoDimension=any&q=lets+play+berlin&videoCaption=closedCaption&videoLicense=any&videoSyndicated=true&type=video&videoDuration=short&maxResults=10&key={API-KEY}, NSErrorFailingURLStringKey=https://www.googleapis.com/youtube/v3/search?videoEmbeddable=true&videoType=any&videoDefinition=any&order=relevance&part=snippet&videoDimension=any&q=lets+play+berlin&videoCaption=closedCaption&videoLicense=any&videoSyndicated=true&type=video&videoDuration=short&maxResults=10&key={API-KEY}, NSErrorClientCertificateStateKey=0})
Yet I wasn't able to nail down the problem. I found Apples Technical Note TN2232 on HTTPS Server Trust Evaluation, but frankly said much to work through while I'm not sure if I just forgot about a simple additional thing.
I've learned that I could disable server trust. But this is not an option for me as the app is supposed to apply at the Apple Store.
Does anybody faced the same problem? Or does anybody have a clue on what to do in order to solve the problem?
Kind regards!
Related
I am attempting to write a simple scraper with AlamoFire, vis a vis a server-side Vapor back end. AlamoFire appears to be initiated properly, but I'm not getting any action from the callback handler.
import Routing
import Vapor
import Alamofire
public func routes(_ router: Router) throws {
router.get("scrape") { req -> String in
let stuff = Stuff(id: nil, sourcecode: "This saves to the database.")
stuff.save(on: req)
let q = Alamofire.request("http://sigh-fi.com/test.txt").responseString { response in
// None of this prints to the terminal.
print("Success: \(response.result.isSuccess)")
print("Request: \(String(describing: response.response))")
print("Result: \(String(describing: response.result))")
print("String: \(String(describing: response.result.value))")
// ideally I'd like to run...
// let morestuff = Stuff(id: nil, sourcecode: response.result.value)
let morestuff = Stuff(id: nil, sourcecode: "This doesn't save to the database, so I'm not even getting that far.")
morestuff.save(on: req)
}
print(q) // prints "GET http://sigh-fi.com/test.txt" as expected
return "okay"
}
}
Unfortunately I can't tell if this is a Vapor problem, an Alamofire or Swift problem. Any suggestions would be greatly appreciated.
So it turns out that Vapor has its own HTTP client library, and it seems to work just fine. Still unsure why Alamofire fell apart, but it's moot.
Thank you for your assistance, Nick.
https://docs.vapor.codes/3.0/http/client/
So the API I'm working with will sometimes send an error message in the response body when a request fails. This is located in response.data. Sometimes it's JSON, sometimes it's a string. I'm using the validate method so result.value is nil when an error occurs.
Is there a way of having Alamofire serialize the data from NSData to a string or for JSON to [ String : AnyObject ] like it would if the response was successful?
I would like to keep using the validate method.
EDIT:
Here's a link to a feature request I started on the Alamofire GitHub project.
https://github.com/Alamofire/Alamofire/issues/1459
There is not currently. I'm actually working on this very feature in Alamofire 4 right now. In Alamofire 3, you'll have to parse the response.data yourself if you get that validation error. In Alamofire 4, you'll at least have access to the response.data at the time of validation as well as be able to customize the Error that is generated by validation.
Most likely what the final solution will be is the ability to check in validation if you know there's going to be an error (checking response status code and headers). Then based on the type of error, you could parse the response.data to extract the error message from the server and throw a VERY SPECIFIC error from validation. This is most likely what the new system will allow. This way you could identify OAuth2 access token errors right in validation and throw your own custom error rather than having to use a convoluted system of response serializers to do it.
Swift 4
If you get an error, you can try parsing the response data as a string or as json.
import Alamofire
import SwiftyJSON
Alamofire.request("http://domain/endpoint", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
.validate()
.responseJSON(completionHandler: { response in
if let error = response.error {
if let data = response.data {
if let errorString = String(bytes: data, encoding: .utf8) {
print("Error string from server: \(errorString)")
} else {
print("Error json from server: \(JSON(data))")
}
} else {
print("Error message from Alamofire: \(error.localizedDescription)")
}
}
guard let data = response.result.value else {
print("Unable to parse response data")
return
}
print("JSON from server: \(JSON(data))")
})
I'm using OS X Server 4.1.3 and an iPhone 6 with iOS9 Beta-4.
When I try to connect to the OS X Server ("skw.local") with
let urlPath: String = "https://skw.local/"
let url: NSURL = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let sesh = NSURLSession(configuration: sessionConfig)
let task = sesh.dataTaskWithRequest(request) {(data, response, error) -> Void in
if error == nil {
print("Response: \(response)")
}
else {
print(error, appendNewline: true)
}
}
task.resume()
I get the following error:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
You might be connecting to a server that is pretending to be “skw.local” which could put your confidential information at risk."
I have tried bypassing the NSAppTransportSecurity requirements by adding the following to my Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
but I get the same errors.
Anyone else having similar trouble?
edit: I also posted this over on apple's dev forum
In Google documentation (https://developers.google.com/url-shortener/v1/getting_started), to use Google URL shortener, I should make a request as below:
POST https://www.googleapis.com/urlshortener/v1/url
Content-Type: application/json
{"longUrl": "http://www.google.com/"}
They also stated that I will have to authenticate:
"Every request your application sends to the Google URL Shortener API
needs to identify your application to Google. There are two ways to
identify your application: using an OAuth 2.0 token (which also
authorizes the request) and/or using the application's API key."
I chose public API key as a method to authenticate: I create a public key for my iOS app. Then I use the following code to POST (AFNetworking, using Swift):
func getShortURL(longURL: String){
let manager = AFHTTPRequestOperationManager()
let params = [
"longUrl": longURL
]
manager.POST("https://www.googleapis.com/urlshortener/v1/url?key={my_key_inserted}", parameters: params, success: {
(operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
println("JSON: " + responseObject.description)
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
println("Error while requesting shortened: " + error.localizedDescription)
})
}
However, I got the log: Error while requesting shortened: Request failed: bad request (400).
Please tell me how to fix it.
What you are missing is setting the right AFNetworking serializer for this request.
Since the Google response is in JSON, you should use AFJSONRequestSerializer.
Add manager.requestSerializer = AFJSONRequestSerializer() like this:
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer = AFJSONRequestSerializer()
let params = ["longUrl": "MYURL"]
manager.POST("https://www.googleapis.com/urlshortener/v1/url?key=MYKEY", parameters: params, success: {(operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
println("JSON: " + responseObject.description)
}, failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
println("Error while requesting shortened: " + error.localizedDescription)
})
I have an issue with AFNetworking and AFJSONRequestSerializer. I try to access an API, and the request contains a text/plain header. Here's my code :
class BaseService {
var manager: AFHTTPRequestOperationManager!
init() {
manager = AFHTTPRequestOperationManager()
manager.responseSerializer = AFJSONResponseSerializer()
manager.requestSerializer = AFJSONRequestSerializer(writingOptions: NSJSONWritingOptions.allZeros)
}
}
class UserService: BaseService {
func startNewEntry(name: String) {
let params = [
"time_entry": [
"description": name,
"created_with": "fooBar"
]
]
manager.POST(
"endpoint",
parameters: params,
success: { (operation, response) -> Void in
let json = JSON(response)
println("OK")
println(json)
Context.shared.entries.getFromJSON(json)
}) { (operation, error) -> Void in
println("-- ERROR --")
println(operation)
println(error)
}
}
Do you know this issue ?
No, this code will create a request with a content type of application/json. But I wonder if you perhaps mislead by an error message that said:
Request failed: unacceptable content-type: text/html
If you got that, that's not telling you that that the request had an unacceptable content type, but rather that the request failed because the response was text/html. And this is a very common issue: If server code that is attempting to create a JSON response fails for some reason, sometimes the error message isn't JSON, but rather it's HTML.
I would suggest adding the following inside the failure block of your POST method in order to see what this text/html response was:
if operation.responseData != nil {
println(NSString(data: operation.responseData, encoding: NSUTF8StringEncoding))
}
This way, if you get a text error message from the server (e.g. the request was malformed or what have you), you'll be able to read the HTML response you got back.