I am working on writing Rust bindings for an API that takes in two API keys. There are numerous ways to accomplish this. I specifically do not want to burden the user with making requests like
myapi::requestThing(firstApiKey, SecondApiKey,...)
I would like to have the user only pass in the API keys once and have it remember it. The problem is I'm trying to do this functionally, and cramming everything into a struct also doesn't seem like the best way.
You absolutely do not want to have some sort of global configuration that is magically stored. That would prevent the API being used by multiple users concurrently in the same process.
I would construct a builder for the API endpoint. This can provide defaults for the API URL and can also grab API keys from the environment variables. You can also override the URL or keys programmatically.
use std::collections::HashMap;
struct ApiEndpoint {
url: String,
api_key_1: String,
api_key_2: String,
}
impl ApiEndpoint {
fn add_money_to_account(&self, cents: u64) {
println!("Adding {} cents. Making a request to {} ({}, {})", cents, self.url, self.api_key_1, self.api_key_2);
}
}
struct ApiBuilder {
url: Option<String>,
api_key_1: Option<String>,
api_key_2: Option<String>,
}
impl ApiBuilder {
fn new() -> ApiBuilder {
ApiBuilder {
url: None,
api_key_1: None,
api_key_2: None,
}
}
fn url(mut self, url: &str) -> ApiBuilder {
self.url = Some(url.into());
self
}
fn api_key_1(mut self, api_key_1: &str) -> ApiBuilder {
self.api_key_1 = Some(api_key_1.into());
self
}
fn api_key_2(mut self, api_key_2: &str) -> ApiBuilder {
self.api_key_2 = Some(api_key_2.into());
self
}
fn build(self) -> ApiEndpoint {
let mut env_vars: HashMap<_, _> = std::env::vars().collect();
ApiEndpoint {
url: self.url.unwrap_or_else(|| "http://example.com/default".into()),
api_key_1: self.api_key_1.or_else(|| env_vars.remove("MYLIB_ENV_VAR_1")).unwrap(),
api_key_2: self.api_key_2.or_else(|| env_vars.remove("MYLIB_ENV_VAR_2")).unwrap(),
}
}
}
fn main() {
let endpoint =
ApiBuilder::new()
.url("https://test.example.com")
.api_key_1("SEEKRET")
.api_key_2("PASSWORD")
.build();
endpoint.add_money_to_account(500);
}
cramming everything into a struct also doesn't seem like the best way
I don't see why not.
Related
I have "val" values from api response call which I need to provide to onPageFinished.
How can I make that?
override fun onResponse(call: Call, response: Response) {
//getting info from response to json
val app json.app ?: ""
view.loadUrl(json.url)
}
view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
//and now I need to get this app variable
}
}
I have an array of A objects. I would like to transform it into an array with objects of type B. But the tricky part is to download images in the meantime and do everything using RxSwift or ReactiveSwift. Do you have any tips how could I do it?
struct A {
let name: String
let imageURL: URL
let thumbnailURL: URL
}
struct B {
let name: String
let image: UIImage?
let thumbnail: UIImage?
}
So notwithstanding my comment about the context for this potentially mattering a great deal, here's how I would asynchronously convert [A] to [B] using ReactiveSwift. Note that I haven't had a chance to test this code, but it should get across the basic idea:
// This function takes an `NSURL` and creates an asynchronous `SignalProducer` to
// download an image from that URL or yields `nil` if there is an error.
func downloadImage(url: URL) -> SignalProducer<UIImage?, NoError> {
let request = URLRequest(url: url)
return URLSession.shared.reactive.data(with: request)
.map { (data, response) -> UIImage? in UIImage(data: data) }
.flatMapError { _ in SignalProducer<UIImage?, NoError>(value: nil) }
}
func convertAToB(_ a: A) -> SignalProducer<B, NoError> {
let imgDownload = downloadImage(url: a.imageURL)
let thumbDownload = downloadImage(url: a.thumbnailURL)
return SignalProducer<UIImage?, NoError>.combineLatest(imgDownload, thumbDownload)
.map { images in
return B(name: a.name, image: images.0, thumbnail: images.1)
}
}
func convertAllAsToBs(_ inputs: [A]) -> SignalProducer<[B], NoError> {
return SignalProducer<A, NoError>(values: inputs)
.flatMap(.concat, convertAToB)
.collect()
}
let inputs: [A] = ...
convertAllAsToBs(inputs).startWithValues { outputs in
// `outputs` is [B]
}
Edit:
To bring this answer to parity with #PhilippeC's RxSwift answer, here's a summary of what each ReactiveSwift operator is doing:
SignalProducer.init(values:) is the equivalent of RxSwift's Observable.from. It creates a producer that sends each value of the sequence as a separate event.
collect is the equivalent of RxSwift's toArray. It collects each value from the source producer and sends them along in a single array once the source producer completes.
flatMap starts the convertAToB producer for each incoming A, and merges the result according to to specified FlattenStrategy. In this case, I used .concat, which makes it equivalent to RxSwift's concatMap which concatenates each result and preserves the order as #PhilippeC describes in his answer.
With RxSwift, this can be done using a couple of operators:
the Observable.from and toArray() operators that convert an Array to a sequence of Events, and back and forth.
the concatMap operator which transforms each individual A items, then concatenates each individual result into one observable sequence, and respects the original ordering.
Here is a Swift code:
// choose the implementation you prefer for this function
// N.B. : if using RxCocoa, prefer Single<UIImage?> as return type
func downloadImage(url: URL) -> Observable<UIImage?> {
return URLSession.shared.rx
.data(URL)
.map { data in UIImage(data: data) }
.catchErrorJustReturn(nil)
}
let arrayOfA: [A] = []; // your input array goes here.
let arrayOfB: Observable<[B]> =
Observable
// Convert each array element to an item
.from(arrayOfA)
// concatMap preserves the order
.concatMap { a in
Observable.zip(downloadImage(a.imageURL), downloadImage(a.thumbnailURL))
.map { image, thumbnail in
B(name: a.name, image: image, thumbnail: thumbnail)
}
}
.toArray()
// do some stuff with the result: arrayOfB
At the end, you get the resulting array as an Observable<[B]> that is expected to a single event. To use it, just subscribe to this Observable, or bind it directly to your UI or your DataSource.
N.B. in case of a long array, I also suggest running the downloaded on a background queue: thanks to Rx, this can be easily done just by adding something like .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) after .toArray().
I am going to say in advance i don't know too much about cryptography (Basics only). I am trying to Implement a Credential OpenHome Service and I want to encrypt a password to send it to the device.
The device provides a function written in C that returns a public key String that looks like that:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzjFGuEKD0uWxzb47oRbiSP2uDwVJPeWU7m9VXi626V6lameTzdtwj2eYVZTIAsAW7yW4or2skn7oHqFG4GvhMzgMwoQjKFxeCPPFXRSotnt26AN1DhvFJp3V/d+MpmkzI07iWcD5eNe4EVNK9GSE4JOEHhJ/JYBVMiu04XE5aqwIDAQAB
The Android implementation has been already done and the specs given are
RSA/ECB/OAEPWithSHA-256AndMGF1Padding
also there is a web site that gives "instructions" when encrypting
http://wiki.openhome.org/wiki/Av:Developer:CredentialsService
I have tried so far these libraries:
SwiftyRSA, Heimdall, SwCrypt
I really thing that one of my main failures are I don't understand what I have, what do I need and finally how to achieve it using swift.
ideally at the end i will have a functions like
func encryptMessage(message:String, whithPublicKey key:String)->String
thank you very much.
After a long research i have just implemented my own solution rather than using libraries and not understanding what was going on. It is always good to know what happens and it this case it is not rocket science.
On iOS if you want to encrypt/decrypt you need to use a key stored on the keychain. If, in my case, i have been given the public key i can import it and also I can do the same with the private key. Please see my HelperClass Here.
Then, and from only from iOS 10, you can call this 2 methods for encrypting and decrypting
let error:UnsafeMutablePointer<Unmanaged<CFError>?>? = nil
let plainData = "A Plain text...".data(using: .utf8)
if let encryptedMessageData:Data = SecKeyCreateEncryptedData(publicSecKey, .rsaEncryptionOAEPSHA256, plainData! as CFData,error) as Data?{
print("We have an encrypted message")
let encryptedMessageSigned = encryptedMessageData.map { Int8(bitPattern: $0) }
print(encryptedMessageSigned)
if let decryptedMessage:Data = SecKeyCreateDecryptedData(privateSecKey, .rsaEncryptionOAEPSHA256, encryptedMessageData as CFData,error) as Data?{
print("We have an decrypted message \(String.init(data: decryptedMessage, encoding: .utf8)!)")
}
else{
print("Error decrypting")
}
}
else{
print("Error encrypting")
}
Also, if you want before iOS 10 you have the functions:
func SecKeyEncrypt(_ key: SecKey,
_ padding: SecPadding,
_ plainText: UnsafePointer<UInt8>,
_ plainTextLen: Int,
_ cipherText: UnsafeMutablePointer<UInt8>,
_ cipherTextLen: UnsafeMutablePointer<Int>) -> OSStatus
And
func SecKeyDecrypt(_ key: SecKey,
_ padding: SecPadding,
_ cipherText: UnsafePointer<UInt8>,
_ cipherTextLen: Int,
_ plainText: UnsafeMutablePointer<UInt8>,
_ plainTextLen: UnsafeMutablePointer<Int>) -> OSStatus
But these give less options and They are quite resticted.
Worth mentioning that my public and private key where generate on android using
public static String createStringFromPublicKey(Key key) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key.getEncoded());
return new String(Base64.encode(x509EncodedKeySpec.getEncoded(), Base64.NO_WRAP), "UTF-8");
}
and
public static String createStringFromPrivateKey(Key key) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key.getEncoded());
return new String(Base64.encode(pkcs8EncodedKeySpec.getEncoded(), Base64.NO_WRAP), "UTF-8");
}
I’m developing iOS Apps for quite a long time now. But in the end I was never satisfied with the architecture design for my network layer. Especially when it goes about connecting an API.
There exists a possible duplicate here, but I think my question is more specific as you will see.
Best architectural approaches for building iOS networking applications (REST clients)
I’m not looking for answers like "use AFNetworking/Alamofire". This question is regardless of which 3rd party framework is used.
I mean, often we have the scenario:
"Develop an app X that uses API Y"
And this includes mainly the same steps - everytime.
Implement login / registration
You get an authentication token, have to save it in the keychain and append it in every API call
You have to re-authenticate and re-send the API request which failed with a 401
You have error codes to handle (how to handle them centralized?)
You implement the different API calls.
One problem with 3)
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request.
In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1.
I like more the second approach - but is there a better solution?
Regarding 4)
How do you handle http status codes? Do you use many different classes for every error? Do you centralize general error handling in one class? Do you handle them all at the same level or do you catch server errors earlier? (Maybe in your API Wrapper of any 3rd party lib)
How are you developers trying to solve this problems? Have you figured out a "best match" design?
How do you test your APIs? Especially how do you do this in Swift (with no real mocking possibility?).
Of course: Every use case, every app, every scenario is different - there is no "One solution fits them all". But I think these general problems re-appear so often, so I’m tempted to say "Yes, for these cases - there could be one and more solutions - which you can reuse every time".
Looking forward to interesting answers!
Cheers
Orlando 🍻
But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
I am using a few layers for authenticating with an API.
Authentication Manager
This manager is responsible for all authentication related functionality. You can think about authentication, reset password, resend verification code functions, and so on.
struct AuthenticationManager
{
static func authenticate(username:String!, password:String!) -> Promise<Void>
{
let request = TokenRequest(username: username, password: password)
return TokenManager.requestToken(request: request)
}
}
In order to request a token we need a new layer called the TokenManager, which manages all things related to a token.
Token Manager
struct TokenManager
{
private static var userDefaults = UserDefaults.standard
private static var tokenKey = CONSTANTS.userDefaults.tokenKey
static var date = Date()
static var token:Token?
{
guard let tokenDict = userDefaults.dictionary(forKey: tokenKey) else { return nil }
let token = Token.instance(dictionary: tokenDict as NSDictionary)
return token
}
static var tokenExist: Bool { return token != nil }
static var tokenIsValid: Bool
{
if let expiringDate = userDefaults.value(forKey: "EXPIRING_DATE") as? Date
{
if date >= expiringDate
{
return false
}else{
return true
}
}
return true
}
static func requestToken(request: TokenRequest) -> Promise<Void>
{
return Promise { fulFill, reject in
TokenService.requestToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
let today = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)
userDefaults.setValue(tomorrow, forKey: "EXPIRING_DATE")
fulFill()
}.catch { error in
reject(error)
}
}
}
static func refreshToken() -> Promise<Void>
{
return Promise { fulFill, reject in
guard let token = token else { return }
let request = TokenRefresh(refreshToken: token.refreshToken)
TokenService.refreshToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
fulFill()
}.catch { error in
reject(error)
}
}
}
private static func setToken (token:Token!)
{
userDefaults.setValue(token.toDictionary(), forKey: tokenKey)
}
static func deleteToken()
{
userDefaults.removeObject(forKey: tokenKey)
}
}
In order to request a token we'll need a third layer called TokenService which handles all the HTTP calls. I use EVReflection and Promises for my API calls.
Token Service
struct TokenService: NetworkService
{
static func requestToken (request: TokenRequest) -> Promise<Token> { return POST(request: request) }
static func refreshToken (request: TokenRefresh) -> Promise<Token> { return POST(request: request) }
// MARK: - POST
private static func POST<T:EVReflectable>(request: T) -> Promise<Token>
{
let headers = ["Content-Type": "application/x-www-form-urlencoded"]
let parameters = request.toDictionary(.DefaultDeserialize) as! [String : AnyObject]
return POST(URL: URLS.auth.token, parameters: parameters, headers: headers, encoding: URLEncoding.default)
}
}
Authorization Service
I am using an Authorisation Service for the problem you are describing here. This layer is responsible for intercepting server errors such as 401 (or whatever code you want to intercept) and fix them before returning the response to the user. With this approach everything is handled by this layer and you don't have to worry about an invalid token anymore.
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request. In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
struct AuthorizationService: NetworkService
{
private static var authorizedHeader:[String: String]
{
guard let accessToken = TokenManager.token?.accessToken else
{
return ["Authorization": ""]
}
return ["Authorization": "Bearer \(accessToken)"]
}
// MARK: - POST
static func POST<T:EVObject> (URL: String, parameters: [String: AnyObject], encoding: ParameterEncoding) -> Promise<T>
{
return firstly
{
return POST(URL: URL, parameters: parameters, headers: authorizedHeader, encoding: encoding)
}.catch { error in
switch ((error as NSError).code)
{
case 401:
_ = TokenManager.refreshToken().then { return POST(URL: URL, parameters: parameters, encoding: encoding) }
default: break
}
}
}
}
Network Service
The last part will be the network-service. In this service layer we will do all interactor-like code. All business logic will end up here, anything related to networking. If you briefly review this service you'll note that there is no UI-logic in here, and that's for a reason.
protocol NetworkService
{
static func POST<T:EVObject>(URL: String, parameters: [String: AnyObject]?, headers: [String: String]?, encoding: ParameterEncoding) -> Promise<T>
}
extension NetworkService
{
// MARK: - POST
static func POST<T:EVObject>(URL: String,
parameters: [String: AnyObject]? = nil,
headers: [String: String]? = nil, encoding: ParameterEncoding) -> Promise<T>
{
return Alamofire.request(URL,
method: .post,
parameters: parameters,
encoding: encoding,
headers: headers).responseObject()
}
}
Small Authentication Demo
An example implementation of this architecture would be a authenticate HTTP request to login a user. I'll show you how this is done using the architecture described above.
AuthenticationManager.authenticate(username: username, password: password).then { (result) -> Void in
// your logic
}.catch { (error) in
// Handle errors
}
Handling errors is always a messy task. Every developer has it's own way of doing this. On the web there are heaps of articles about error handling in for example swift. Showing my error handling will be of not much help since it's just my personal way of doing it, it's also a lot of code to post in this answer, so I rather skip that.
Anyway...
I hope I've helped you back on track with this approach. If there is any question regarding to this architecture, I'll be more than happy to help you out with it. In my opinion there is no perfect architecture and no architecture that can be applied to all projects.
It's a matter of preference, project requirements and expertise in within your team.
Best of luck and please do no hesitate to contact me if there's any problem!
I am still a beginner in Reactive programming, and RxSwift in general.
I want to chain two different operation. In my case I simply want to download a zip file from a web server, and then unzip it locally.
I also want, at the same time to show the progress of the downloaded files.
So I started creating the first observable:
class func rx_download(req:URLRequestConvertible, testId:String) -> Observable<Float> {
let destination:Request.DownloadFileDestination = ...
let obs:Observable<Float> = Observable.create { observer in
let request = Alamofire.download(req, destination: destination)
request.progress { _, totalBytesWritten, totalBytesExpectedToWrite in
if totalBytesExpectedToWrite > 0 {
observer.onNext(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
else {
observer.onNext(0)
}
}
request.response { _, response, _, error in
if let responseURL = response {
if responseURL.statusCode == 200 {
observer.onNext(1.0)
observer.onCompleted()
} else {
let error = NSError(domain: "error", code: responseURL.statusCode, userInfo: nil)
observer.onError(error)
}
} else {
let error = NSError(domain: "error", code: 500, userInfo: nil)
observer.onError(error)
}
}
return AnonymousDisposable () {
request.cancel()
}
}
return obs.retry(3)
}
After that, I create a similar function for the unzip
class func rx_unzip(testId:String) -> Observable<Float> {
return Observable.create { observer in
do {
try Zip.unzipFile(NSURL.archivePath(testId), destination: NSURL.resourceDirectory(testId), overwrite: true, password: nil)
{progress in
observer.onNext(Float(progress))
}
} catch let error {
observer.onError(error)
}
observer.onCompleted()
return NopDisposable.instance
}
}
For now I have this logic on the "View model layer", so I download-> subscribe on completed-> unzip
What I want is to combine the two Observable in one, in order to perform the download first, then on completed unzip the file. Is there any way to do this?
Concat operator requires the same data type
Indeed, the concat operator allows you to enforce the sequence of observables, however an issue you might encounter with using concat is that the concat operator requires that the Observables have the same generic type.
let numbers : Observable<Int> = Observable.from([1,2,3])
let moreNumbers : Observable<Int> = Observable.from([4,5,6])
let names : Observable<String> = Observable.from(["Jose Rizal", "Leonor Rivera"])
// This works
numbers.concat(moreNumbers)
// Compile error
numbers.concat(names)
FlatMap operator allows you to chain a sequence of Observables
Here's an example.
class Tag {
var tag: String = ""
init (tag: String) {
self.tag = tag
}
}
let getRequestReadHTML : Observable<String> = Observable
.just("<HTML><BODY>Hello world</BODY></HTML>")
func getTagsFromHtml(htmlBody: String) -> Observable<Tag> {
return Observable.create { obx in
// do parsing on htmlBody as necessary
obx.onNext(Tag(tag: "<HTML>"))
obx.onNext(Tag(tag: "<BODY>"))
obx.onNext(Tag(tag: "</BODY>"))
obx.onNext(Tag(tag: "</HTML>"))
obx.onCompleted()
return Disposables.create()
}
}
getRequestReadHTML
.flatMap{ getTagsFromHtml(htmlBody: $0) }
.subscribe (onNext: { e in
print(e.tag)
})
Notice how getRequestReadHTML is of type Observable<String> while the function getTagsFromHtml is of type Observable<Tag>.
Using multiple flatMaps can increase emission frequency
Be wary however, because the flatMap operator takes in an array (e.g. [1,2,3]) or a sequence (e.g. an Observable) and will emit all of the elements as an emission. This is why it is known to produce a transformation of 1...n.
If you defined an observable such as a network call and you are sure that there will only be one emission, you will not encounter any problems since its transformation is a 1...1 (i.e. one Observable to one NSData). Great!
However, if your Observable has multiple emissions, be very careful because chained flatMap operators will mean emissions will exponentially(?) increase.
A concrete example would be when the first observable emits 3 emissions, the flatMap operator transforms 1...n where n = 2, which means there are now a total of 6 emissions. Another flatMap operator could again transform 1...n where n = 2, which means there are now a total of 12 emissions. Double check if this is your expected behavior.
You can use concat operator to chain these two Observables. The resulting Observable will send next values from the first one, and when it completes, from the second one.
There is a caveat: you will get progress values ranging from 0.0 to 1.0 from rx_download and then again the progress from rx_unzip will start with 0.0. This might be confusing to the user if you want to show the progress on a single progress view.
A possible approach would be to show a label describing what is happening along with the progress view. You can map each Observable to a tuple containing the progress value and the description text and then use concat. It can look like that:
let mappedDownload = rx_download.map {
return ("Downloading", $0)
}
let mappedUnzip = rx_download.map {
return ("Unzipping", $0)
}
mapped1.concat(mapped2)
.subscribeNext({ (description, progress) in
//set progress and show description
})
Of course, there are many possible solutions, but this is more of a design problem than a coding one.