Skip to content

Commit

Permalink
Uses new HTTPClient for background fetch jobs
Browse files Browse the repository at this point in the history
See #783
Signed-off-by: Dan Cunningham <[email protected]>
  • Loading branch information
digitaldan committed Jul 6, 2024
1 parent 4bde454 commit d4df2e8
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 6 deletions.
149 changes: 149 additions & 0 deletions OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) 2010-2024 Contributors to the openHAB project
//
// See the NOTICE file(s) distributed with this work for additional
// information.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0
//
// SPDX-License-Identifier: EPL-2.0

import Foundation
import os.log

// Define a custom log object
extension OSLog {
private static var subsystem = Bundle.main.bundleIdentifier!
static let networking = OSLog(subsystem: subsystem, category: "networking")
}

public class HTTPClient: NSObject, URLSessionDelegate {
// MARK: - Properties

private var session: URLSession!
private let username: String
private let password: String
private let certManager: ClientCertificateManager
private let alwaysSendBasicAuth: Bool

public init(username: String, password: String, alwaysSendBasicAuth: Bool = false) {
self.username = username
self.password = password
certManager = ClientCertificateManager()
self.alwaysSendBasicAuth = alwaysSendBasicAuth
super.init()

let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60

session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}

// MARK: - Basic Authentication

private func basicAuthHeader() -> String {
let authString = "\(username):\(password)"
let authData = authString.data(using: .utf8)!
return "Basic \(authData.base64EncodedString())"
}

// MARK: - URLSessionDelegate for Client Certificates

public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
let serverDistinguishedNames = challenge.protectionSpace.distinguishedNames
let identity = certManager.evaluateTrust(distinguishedNames: serverDistinguishedNames ?? [])

if let identity {
let credential = URLCredential(identity: identity, certificates: nil, persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let serverTrust = challenge.protectionSpace.serverTrust!
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
let credential = URLCredential(user: username, password: password, persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}

// Perform an HTTP request
public func performRequest(request: URLRequest, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
var request = request
if alwaysSendBasicAuth {
request.setValue(basicAuthHeader(), forHTTPHeaderField: "Authorization")
}
let task = session.dataTask(with: request, completionHandler: completion)
task.resume()
}

// General function to perform HTTP requests
private func doRequest(baseURLs: [String], path: String, method: String, body: String? = nil, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
var urls: [URL] = []
for urlString in baseURLs {
if var urlComponent = URLComponents(string: urlString) {
urlComponent.path = path
if let url = urlComponent.url {
urls.append(url)
}
}
}

func sendRequest() {
guard !urls.isEmpty else {
os_log("All URLs processed and failed.", log: .networking, type: .error)
completion(nil, nil, NSError(domain: "HTTPClient", code: -1, userInfo: [NSLocalizedDescriptionKey: "All URLs processed and failed."]))
return
}

let url = urls.removeFirst()
var request = URLRequest(url: url)
request.httpMethod = method
if let body {
request.httpBody = body.data(using: .utf8)!
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
}

performRequest(request: request) { data, response, error in
if let error {
os_log("Error with URL %{public}@ : %{public}@", log: .networking, type: .error, url.absoluteString, error.localizedDescription)
// Try the next URL
sendRequest()
} else if let response = response as? HTTPURLResponse {
if (400 ... 599).contains(response.statusCode) {
os_log("HTTP error from URL %{public}@ : %{public}d", log: .networking, type: .error, url.absoluteString, response.statusCode)
// Try the next URL
sendRequest()
} else {
os_log("Response from URL %{public}@ : %{public}d", log: .networking, type: .info, url.absoluteString, response.statusCode)
if let data {
os_log("Data: %{public}@", log: .networking, type: .debug, String(data: data, encoding: .utf8) ?? "")
}
completion(data, response, nil)
}
}
}
}

sendRequest()
}

public func doGet(baseURLs: [String], path: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
doRequest(baseURLs: baseURLs, path: path, method: "GET", completion: completion)
}

public func doPost(baseURLs: [String], path: String, body: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
doRequest(baseURLs: baseURLs, path: path, method: "POST", body: body, completion: completion)
}

public func baseURLs(baseURLs: [String], path: String, body: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
doRequest(baseURLs: baseURLs, path: path, method: "PUT", body: body, completion: completion)
}
}
2 changes: 1 addition & 1 deletion openHAB/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
var userInfo = response.notification.request.content.userInfo
let actionIdentifier = response.actionIdentifier
os_log("Notification clicked: action %{PUBLIC}@ userInfo %{PUBLIC}@", log: .notifications, type: .info, actionIdentifier, userInfo)
os_log("Notification clicked: action %{public}@ userInfo %{public}@", log: .notifications, type: .info, actionIdentifier, userInfo)
if actionIdentifier != UNNotificationDismissActionIdentifier {
if actionIdentifier != UNNotificationDefaultActionIdentifier {
userInfo["actionIdentifier"] = actionIdentifier
Expand Down
14 changes: 9 additions & 5 deletions openHAB/OpenHABRootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,16 @@ class OpenHABRootViewController: UIViewController {
if components.count == 2 {
let itemName = String(components[0])
let itemCommand = String(components[1])
OpenHABItemCache.instance.getItem(name: itemName) { item in
guard let item else {
os_log("Could not find item %{PUBLIC}@", log: .notifications, type: .info, itemName)
return
let client = HTTPClient(username: Preferences.username, password: Preferences.username)
client.doPost(baseURLs: [Preferences.localUrl, Preferences.remoteUrl], path: "/rest/items/\(itemName)", body: itemCommand) { data, _, error in
if let error {
os_log("Could not send data %{public}@", log: .default, type: .error, error.localizedDescription)
} else {
os_log("Request succeeded", log: .default, type: .info)
if let data {
os_log("Data: %{public}@", log: .default, type: .debug, String(data: data, encoding: .utf8) ?? "")
}
}
OpenHABItemCache.instance.sendCommand(item, commandToSend: itemCommand)
}
}
}
Expand Down

0 comments on commit d4df2e8

Please sign in to comment.