📜 ⬆️ ⬇️

How to write your own application with Yandex.Disk REST API

Hello! My name is Clemens ower, I am developing a desktop version of Yandex.Disk. When the new REST API appeared, I was so impressed with the new opportunities that I started writing SDK for it in my spare time for the new Swift language. I decided to talk about my experience with the new API and made a short talk on how easy it is to start writing your own application with it. The report was in English, but I also speak Russian, although not so well. Based on my presentation, I prepared today's post, which was helped by my colleagues.



I would like to start with some general things about Disk - most likely, you have already heard most of this. Then I will tell you why I was so impressed with the new API and how it differs from WebDAV. Well, towards the end, I will share the experience of how to start developing for a new API, and will sort out a few examples of my code. It will be quite a bit, but with the new API, in order to get something to work, there is practically no code to write.

WebDAV vs. REST


Probably many of you have already wondered what is the difference between WebDAV and REST and what are the advantages of the latter? At first glance, there is no difference: both there and there you can upload and download files, create folders, move objects, transfer them to the trash and delete them permanently, create lists, etc.
')

In general, the main functionality is completely the same. If you compare the work with public files, then through WebDAV you can make the files public and make them private again. With folders the same. That is, you can get a link to a file or folder, and later make it invalid. The REST API adds new functionality to this.


For example, you can view public file metadata. Having only a link to the file, in one request to the API you can find out its size and name. If this is a folder, you can even see the structure of its contents. And of course, you can save everything to your disk. It was one of the most popular features after the release of our SDKs. And finally, we implemented it.


The REST API has added support for working with the basket. Now you can view the contents of the basket, clear it or restore the necessary files not only through the web interface.


Yes, something is really missing. For example, we did not add basic authentication and do not plan to do this. In addition, you can access the new API only via HTTPS. This is done for the sake of user security. When I gave a talk, it was impossible to get information about the free and used space on the user's disk through the REST API. Therefore, in the table opposite this item is a cross. However, now this feature has already appeared. Many people know that through WebDAV, you can add files without downloading, through hash sums. On the client side, MD5, SHA are calculated, the file size is added to them, and this information is transmitted to the server. If a file with the same hash-sums and size is already in the repository, the download does not happen, the file is simply added to your Disk. Unfortunately, this is not yet in the REST API.

When you work through OAuth, all that you have from the user is a token. But it is sometimes useful to learn more about the user: what is his login, e-mail, etc. You can get this data through a separate API, the description of which is here . Using this API, you can get much more information than through WebDAV.

Getting to the development


First you need to register the application and create a developer account. You do not want, as a result of your experiments, anything to happen to the files on your personal Disk. Next you need to get a token and a little deal with the site. That's it, you can start writing code.

The process of registering an application is quite simple: you go to https://oauth.yandex.ru/client/new and enter the name and description of the application. The name will later be used as the name of your application folder in the user's Disk. In the same place, you need to specify which access you need: access to the application folder on the Disk, read or write access to the entire User disk. If you tick the “Client for development” checkbox, the callback UI field will be filled with a default URL that does not redirect anywhere, but simply shows you the token. Mobile developers will most likely use a special URL handler and an application scheme. Therefore, in this field you can enter just something like my_application: / authoruze, and Yandex.Disk will target your application, and it will appear with one of the parameters of your token.

After registering the application, everything is quite simple. You get the application ID and password. The most important is the ID, as it is needed to issue tokens to users. The password is also important, but it depends on how you use the API, how you work with OAuth. For web services, the password may even be more important.

Most likely, you will need several such accounts at once, because you will need to test the ability to transfer files between accounts. In addition, today it is desirable to use autotests, they may require a couple more accounts. Just send invites from your personal account. This is a nice bonus, because for each account you get 500 megabytes on your drive.

You can get the token either at the Polygon or via this link: https://oauth.yandex.ru/authorize?response_type_token&client_id=, substituting the ID that you received during registration.

You can experiment on the Polygon with all the features of the new API. At the same time the service is updated simultaneously with it. So, even if the documentation is lagging behind for a week or a month, all new features will already be on the Polygon. You can immediately see how each new feature works in combat conditions. There is also a table with HTTP response codes, which indicate what these or other error messages or successful completion of operations mean. This is much better than even the most detailed documentation.

When all the preparations are over, you can start writing code. As I said, I myself started writing an SDK for Swift.

import Foundation public class Disk { ... } let disk = Disk(token: "d8edc4f3a698473fbc87634c41b2ca81") var fileURL : NSURL disk.uploadURL(fileURL, toPath: fileURL.lastPathComponent, overwrite: true) { // handle errors } disk.deletePath(fileURL.lastPathComponent, permanently: false) { // handle response } 

Talking about it in full does not make sense, so for the demonstration I removed the most difficult and reduced the amount of code to 200 lines. Making something work is very simple.

 public class Disk { public let token : String public let baseURL = "https://cloud-api.yandex.net:443" var additionalHTTPHeaders : [String:String] { return [ "Accept" : "application/json", "Authorization" : "OAuth \(token)", "User-Agent" : "Mobile Camp Demo" ] } ] 

In this example, I use pure JSON, but I can also use JSON + HAL. Be sure to register the authorization. Most questions about Yandex.Disk concern not the API itself, but OAuth. It turns out that this causes much more difficulties when working with Disk than the API itself. So, if you’ve gotten the hang of OAuth, you’re almost there. In the User-Agent, you can register anything, for example, some identifier of your application.

  public lazy var session : NSURLSession = { let config = NSURLSessionConfiguration.defaultSessionConfiguration() config.HTTPAdditionalHeaders = self.additionalHTTPHeaders return NSURLSession(configuration: config) }() public init(token:String) { self.token = token 

In addition, you need to initialize something like a session that can be used for HTTP requests, etc. When you have a more or less serious application ready, you can add another session, for example, for background transfers. But as a matter of fact, all JSON requests do ordinary data requests, so if you wish, you can limit yourself to one session. It will take a small initializer. We work with JSON, so we have to parse and deserialize a lot of JSON objects.

 extension Disk { class func JSONDictionaryWithData(data:NSData!, onError:(NSError!)->Void) -> NSDictionary? { var error: NSError? let root = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? NSDictionary if root == nil { onError(NSError(...)) return nil } return root } } 

Unfortunately, no matter how cool Swift is, it’s still a static-typed language, so when using the Objective-C API you have to do a lot of things. Suppose some data is returned and you want to get it as an NSDictionary. This generates a lot of extra code, so, as the experience tells me, you will start writing all sorts of auxiliary things like JSONDictionaryWithData. Thus, you get the data, make a request, process errors, and then return the object you need. Working with such an API, you need to make a lot of requests. So I also implemented something like a JSON task with methods: GET, POST, DELETE, etc.

 extension NSURLSession { func jsonTaskWithMethod(method:String, url: NSURL!, onError: ((NSError!) -> Void)!, onCompletion: ((NSDictionary, NSHTTPURLResponse) -> Void)!) -> NSURLSessionDataTask! { let request = NSMutableURLRequest(URL: url) request.HTTPMethod = method return dataTaskWithRequest(request) { (data, response, error) -> Void in let jsonRoot = Disk.JSONDictionaryWithData(data, onError: onError) if let jsonRoot = jsonRoot { switch response.statusCode { case 400...599: return onError(...) default: return onCompletion(jsonRoot, response) } } else { return // handler already called from JSONDictionaryWithData } } } } 

And another URL with an error handler and terminations. Swift is very effective when working with closures, there it is one of the basic types. Compared to python, closures are well integrated syntactically, and working with them is much more pleasant. In addition, you can make a special zone for the three hundred and four hundred codes directly in these auxiliary blocks.

Let's see how the file is loaded. I made a couple of screenshots right from the site. The top one is about parameters. Here we have a path on the disk where the files will be saved, as well as a note about whether the file should be overwritten. In response, you will get something like this from Disk:


Working with the REST API, you will encounter such a construction most often.

How is the file loading? You make a request, the service in return sends you this design. You can upload the file to the issued URL using the specified HTTP method. There is no need to contact a WebDAV proxy, you are working directly with the storage backend. In addition, it has a good effect on throughput and speed. If you take measurements, you will see the difference.

 extension Disk { public func uploadURL(fileURL:NSURL, toPath path:String, overwrite:Bool?, handler:(NSError!) -> Void) -> Void { var url = "\(baseURL)/v1/disk/resources/upload?path=\(path.urlEncoded())" if let overwrite = overwrite { url += "&overwrite=\(overwrite)" } let error = { handler($0) } session.jsonTaskWithMethod("GET", url: NSURL(string: url), onError: error) { (jsonRoot, response)->Void in let (href, method, templated) = Disk.hrefMethodTemplatedWithDictionary(jsonRoot) let request = NSMutableURLRequest(URL: NSURL(string: href)) request.HTTPMethod = method self.session.uploadTaskWithRequest(request, fromFile: fileURL) { (data, response, trasferError)->Void in return error(trasferError) }.resume() }.resume() } } 

In JSONDictionary it is useful to have a topper, also a nice type from Swift. That's the whole download. Just build the URL, indicate whether you want to do a rewrite, create an error handler. This is just a closure that a normal handler wraps in the event of an error. It is especially interesting to create such things when you have more than one parameter. The types of return values ​​are a bit more complicated in this case.


Downloading files is almost the same. You just need to replace the upload with the download.

We proceed to delete the files. Turn again to Polygon. Again we see the path, some parameter and the answer in the form of the same construction. The difference is in another HTTP code.


Deletion does not always happen instantly. If the process is running, but not yet completed, you will receive error 202. You can check the execution status by a separate request. I forgot to mention that asynchronous operations are provided in the REST API. So many operations such as move and delete can be performed asynchronously by the server, since they can take several seconds or even more to complete. WebDAV in such cases gives only error messages or the need to wait.

 public enum DeletionResult { case Done case InProcess(href:String, method:String, templated:Bool) case Failed(NSError!) } extension Disk { public func deletePath(path:String, permanently:Bool?, handler:(result:DeletionResult) -> Void) -> Void { var url = "\(baseURL)/v1/disk/resources?path=\(path.urlEncoded())" if let permanently = permanently { url += "&permanently=\(permanently)" } let error = { handler(result: .Failed($0)) } session.jsonTaskWithMethod("DELETE", url: NSURL(string: url), onError: error) { (jsonRoot, response)->Void in switch response.statusCode { case 202: return handler(result: .InProcess(Disk.hrefMethodTemplatedWithDictionary(jsonRoot))) case 204: return handler(result: .Done) default: return error(NSError(...)) } }.resume() } } 

In essence, the delete function is no different from upload and download. It is very convenient that we have an enum type that can accept additional objects. Using enum as the return type for a handler provides a good advantage. You write code, use the enum return type, attach some objects to it. What is good? The only way to work with enum is to use switch, which means that you cover all possible scenarios in Swift. Thus, using enum, you automatically force you to handle most possible events.

findings


What did I learn from doing all this, what experience did I get? First of all, I realized that Swift is a really cool language with a great future. Especially the enum type with switch. It is convenient to use basic types, such as closures. In fact, these are unnamed functions that can be created anywhere in the code. You can simply embed such a function in the pyramid of functions, return it, etc. With it, you can even apply the techniques of functional programming. One of the main problems with Swift today is strictness with regard to type safety. For example, when you start calling Objective-C code from Swift, the id type can cause you problems. It can not be used just like that, you need to explicitly indicate how to use it: like a Dictionary, myType or something else. So, if you use such structures, you will have to pay a lot of attention to type casting, which will make the code a bit more voluminous. On the other hand, today there are no native libraries for Swift, only a small base is available. Most likely, the fact is that Apple’s resources are limited, they cannot repeat what Microsoft did when launching .NET: huge libraries, ten thousand objects and implementation of everything that they already had in the system. I hope that over time the situation will change. By and large type safety is good, the compiler understands better what can go wrong. Another nuance is language variability. Those who have already tried to work with it, probably noticed that something changes with every Xcode update. The old code stops compiling, even the code samples that Apple demonstrated. Get out strange errors, falls. Sometimes it's a little frustrating. Even the development environment itself crashes. It makes no sense to google or search in Yandex, you will only find outdated information. Now one of the main skills when working with Swift is the habit of typing devforums.apple.com . There are open discussions, no one is afraid to reveal any secrets, etc. There really is a chance to get an answer to your question. If you did not find the answer in the already existing topics, just create your own, somebody will answer. Maybe not immediately, but for a couple of days for sure. This is the most useful resource at the moment.

As for the REST API, the main advice here is to use Polygon, it is beautiful. It took a lot of effort to create documentation, but keeping it up to date is even more difficult. A polygon is always relevant. As soon as a new version comes out, all information about it is already there. In addition, the REST API is fast enough. It really is noticeably faster than WebDAV. And working with OAuth is always nice. Yes, it requires writing more code, but, most likely, you already have some implementation, so it doesn't matter much. REST API has a lot ahead. The API still has white spots, but we have a lot of ideas, the implementation of which my colleagues are actively engaged in.

To summarize, I want to advise you to develop through testing. This will save you a lot of time. Especially when you are writing something like an SDK. It is much easier to write a small test for the function you are implementing than to try to call it from an application. Just write tests, call your functions, check the results. Nowadays, even the development of asynchronous API does not cause problems. Xcode allows you to create asynchronous tests: you set the conditions and run the test, and the handler tells you if everything’s done successfully. Testing asynchronous APIs is very simple there. In addition, it helps to find bugs. Mistakes do everything, and we are no exception, so we welcome every bug report. You can use WCF on the Disk API page. Usually, we all quickly repair - depending on the complexity and criticality of the problem, of course.

Source: https://habr.com/ru/post/241223/


All Articles