REST Drupal
Upcoming SlideShare
Loading in...5
×
 

REST Drupal

on

  • 4,779 views

Talk given at Drupalcamp Arad 2012 about ways of connecting to a REST Drupal service using iOS and Android.

Talk given at Drupalcamp Arad 2012 about ways of connecting to a REST Drupal service using iOS and Android.

Statistics

Views

Total Views
4,779
Views on SlideShare
4,779
Embed Views
0

Actions

Likes
6
Downloads
29
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

REST Drupal Presentation Transcript

  • 1. REST Drupal Talking to Drupal from Javascript, iOS and Android Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 1Sunday, November 18, 12
  • 2. About me I pity the fool who doesn’t use Drupal Hi, I’m Alexandru Badiu. I’m a software engineer, amateur game developer and part time Mister T impersonator. Drupal user for 9 years, board member in Drupal Romania. I work for Demotix. Twitter @voidberg Web ctrlz.ro Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 2Sunday, November 18, 12
  • 3. REST Drupal Mobile is pretty big now days Drupal 8 Web services initiative For Drupal 7 there’s the Services module Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 3Sunday, November 18, 12
  • 4. YASS Oh boy, yet another Services session! Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 4Sunday, November 18, 12
  • 5. Nope Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 5Sunday, November 18, 12
  • 6. In this session Doing “web services” without Services Doing “web services” without Drupal’s slowness Using Services ... With Javascript ... With Objective-C ... With Java Uploading files and cursing at Apple and Google Some memes Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 6Sunday, November 18, 12
  • 7. Sorry :( Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 7Sunday, November 18, 12
  • 8. 1 Services without services Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 8Sunday, November 18, 12
  • 9. Why would you do that? One word: Drupal is slow Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 9Sunday, November 18, 12
  • 10. Services without services Sometimes you really don’t need services Getting a list of news and showing some details about them. Showing nearby businesses on a map. Users submitting anonymous tips or comments. Use JSON and don’t shoot yourself in the foot by using XML or plists. Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 10Sunday, November 18, 12
  • 11. However Services is not the slowest thing, Drupal is Shameless plug: Drupal NoBootstrap Small php library which allows you to be able to use some of Drupals functions without bootstrapping Drupal. Used it for Solr powered instant Google like map of businesses Used it for lightning fast autocomplete Used it for coupon claiming app https://github.com/voidberg/Drupal-NoBootstrap Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 11Sunday, November 18, 12
  • 12. 2 Services Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 12Sunday, November 18, 12
  • 13. Services A long time ago in a Drupalcamp far, far away Last year I was complaining about Services Clunky way of connecting (session, nonce, timestamp) Inconsistent responses Bugs in json_server Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 13Sunday, November 18, 12
  • 14. Services 3 Services 3 is much much nicer REST based No more nonce and other crap Session is kept in the HTTP calls Consistent responses Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 14Sunday, November 18, 12
  • 15. Services 3 REST in a nutshell Resource based HTTP based CRUD GET /endpoint/resource/id POST /endpoint/resource + (post data) DELETE /endpoint/resource/id PUT /endpoint/resource/id + (post data) GET /endpoint/resource GET /services/comment?parameters[nid]=123&parameters[timestamp]=123456:123600 Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 15Sunday, November 18, 12
  • 16. Services 3 REST in a nutshell Actions Do not target a specific resource POST /services/apachesolr/reindex Targeted actions Target a specific resource POST /services/node/123/unpublish Relationships GET /services/node/123/comments Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 16Sunday, November 18, 12
  • 17. 3 Drupanium Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 17Sunday, November 18, 12
  • 18. Drupanium Distribution targeted at building mobile apps For Titanium Studio Should be possible to use with Cordova Looks abandoned Simple js code to grab Decent list of core services resources http://drupanium.org/api Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 18Sunday, November 18, 12
  • 19. 4 iOS Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 19Sunday, November 18, 12
  • 20. Android Drupal iOS SDK The library formerly known as KBDrupalConnect Uses AFNetworking Implements all core service resources Can use OAuth Uses blocks https://github.com/workhabitinc/drupal-ios-sdk Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 20Sunday, November 18, 12
  • 21. Drupal iOS SDK Copy AFNetworking code and Drupal iOS SDK code in project Update Settings.m with url, endpoint etc Profit! Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 21Sunday, November 18, 12
  • 22. Drupal iOS SDK DIOSUser *user = [[DIOSUser alloc] init]; [user userLoginWithUsername:username andPassword:password success:^(AFHTTPRequestOperation *operation, id responseObject) { // Login ok DIOSSession *session = [DIOSSession sharedSession]; [session setUser:[responseObject objectForKey:@"user"]]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { // Login failed [MBProgressHUD hideHUDForView:self.view animated:YES]; } ]; Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 22Sunday, November 18, 12
  • 23. Drupal iOS SDK DIOSNode *node = [[DIOSNode alloc] init]; NSMutableDictionary *nodeData = [NSMutableDictionary new]; [nodeData setValue:[@"Node title" text] forKey:@"title"]; NSDictionary *bodyValues = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Node title", nil] forKeys:[NSArray arrayWithObjects:@"value", nil]]; NSDictionary *languageDict = [NSDictionary dictionaryWithObject:[NSArray arrayWithObject:bodyValues] forKey:@"und"]; [nodeData setValue:languageDict forKey:@"body"]; NSDictionary *fieldValue = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Field value", nil] forKeys:[NSArray arrayWithObjects:@"value", nil]]; NSDictionary *fieldLangDict = [NSDictionary dictionaryWithObject:[NSArray arrayWithObject:sessionValue] forKey:@"und"]; [nodeData setValue:fieldLangDict forKey:@"field_foo_bar"]; [nodeData setValue:@"nodetype" forKey:@"type"]; [nodeData setValue:@"und" forKey:@"language"]; [node nodeSave:nodeData success:^(AFHTTPRequestOperation *operation, id responseObject) { // Node was saved } failure:^(AFHTTPRequestOperation *operation, NSError *error) { // Node save failed }]; Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 23Sunday, November 18, 12
  • 24. Extending it @interface DemotixServices: NSObject - (void)userRegister:(NSDictionary *)user success:(void (^)(AFHTTPRequestOperation *operation, id responseObject)) success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error)) failure; @end #import "DemotixServices.h" #import "DIOSSession.h" @implementation DemotixServices #pragma mark userRegister - (void)userRegister:(NSDictionary *)user success:(void (^)(AFHTTPRequestOperation *operation, id responseObject)) success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error)) failure { [[DIOSSession sharedSession] postPath:[NSString stringWithFormat:@"%@/%@", kDiosEndpoint, @"demotix_services_user/register"] parameters:user success:success failure:failure]; } @end; Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 24Sunday, November 18, 12
  • 25. Saving the session // After a login NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSArray *cookies = [cookieStorage cookies]; for (NSHTTPCookie *cookie in cookies) { if ([cookie.name hasPrefix:@"SESS"]) { [keychainS setObject:cookie.name forKey:(__bridge id)kSecAttrAccount]; [keychainS setObject:cookie.value forKey:(__bridge id)kSecValueData]; } } // On next app start NSString *sessionName = [keychainS objectForKey:(__bridge id)kSecAttrAccount]; NSString *sessionValue = [keychainS objectForKey:(__bridge id)kSecValueData]; if (![sessionName isEqualToString:@""] && ![sessionValue isEqualToString:@""]) { DIOSSession *session = [DIOSSession sharedSession]; [session setDefaultHeader:@"Cookie" value:[NSString stringWithFormat:@"%@=%@", sessionName, sessionValue]]; DIOSSystem *system = [[DIOSSystem alloc] init]; [system systemConnectwithSuccess: ^(AFHTTPRequestOperation *operation, id responseObject) { } failure:^(AFHTTPRequestOperation *operation, NSError *error) { }]; } Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 25Sunday, November 18, 12
  • 26. File upload NSMutableURLRequest *request = [[DIOSSession sharedSession] multipartFormRequestWithMethod:@"POST" path:[NSString stringWithFormat:@"%@/%@", kDiosEndpoint, @"demotix_services_story/upload"] parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData> formData) { NSError *error; if (![formData appendPartWithFileURL:[NSURL fileURLWithPath:filePath] name:@"joined" error:&error]) { NSLog(@"An error occured: %@", error); } else { NSLog(@"No error"); } }]; AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; [operation setUploadProgressBlock:^(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { [delegate updateUploadProgress:totalBytesWritten from:totalBytesExpectedToWrite]; }]; [operation setCompletionBlockWithSuccess:success failure:failure]; [operation start]; Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 26Sunday, November 18, 12
  • 27. File upload Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 27Sunday, November 18, 12
  • 28. File upload AFNetworking uses the iOS API for http calls The API has a bug since iOS 3 When uploading files on 3G you get a OOM error Solution is to throttle the upload which the API has no support for AFNetworking is still working on this I ended up using ASIHttpRequest just for file upload with throttled upload on 3G Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 28Sunday, November 18, 12
  • 29. File upload [ASIHTTPRequest setShouldThrottleBandwidthForWWAN:YES]; [ASIHTTPRequest setMaxBandwidthPerSecond:ASIWWANBandwidthThrottleAmount*throttle_amount]; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@/%@", kDiosBaseUrl, kDiosEndpoint, @"demotix_services_story/upload"]]; __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; [request setCompletionBlock:^{ success(responseString); }]; [request setFailedBlock:^{ failure(responseString, error); }]; for (NSString * fkey in files) { NSDictionary *file = [files objectForKey:fkey]; NSString *fileName = [file valueForKey:@"name"]; [request setFile:[file valueForKey:@"fileUrl"] forKey:fileName]; } [request setShouldContinueWhenAppEntersBackground:YES]; [request setTimeOutSeconds:999]; [request setUploadProgressDelegate:self]; [request startAsynchronous]; Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 29Sunday, November 18, 12
  • 30. 5 Android Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 30Sunday, November 18, 12
  • 31. Android DrupalCloud is not working anymore Hasn’t been updated to Services 3 Never really liked the architecture Dandy might work, I was scared No documentation at all Source code seems over engineered Not sure it works with Services 3 (issue queue says it doesn’t) Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 31Sunday, November 18, 12
  • 32. Android So I built my own Android Drupal SDK https://github.com/voidberg/ Android-Drupal-Sdk Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 32Sunday, November 18, 12
  • 33. First step Let’s choose a HTTP Client It’s a mess HttpUrlConnection Been since the dark ages Had some serious bugs before Froyo Still behind the apache lib Apache HTTP Client Best option so far IMHO Has been deprecated Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 33Sunday, November 18, 12
  • 34. We have a winner Android Asynchronous Http Client http://loopj.com/android-async-http/ You’ll be in select company - Instagram HTTP requests happen outside the UI thread Requests use a threadpool to cap concurrent resource usage Multipart file uploads with no additional third party libraries Automatic smart request retries optimized for spotty mobile connections Automatic gzip response decoding support for super-fast requests Built-in response parsing into JSON with JsonHttpResponseHandler Persistent cookie store, saves cookies into your app’s SharedPreferences Uses the Apache HTTP Client Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 34Sunday, November 18, 12
  • 35. Using it ServicesClient client; client = new ServicesClient("http://www.example.com", "api/ mobile"); client.setBasicAuth("username","password/token"); PersistentCookieStore cookieStore; cookieStore = new PersistentCookieStore(this); client.setCookieStore(cookieStore); Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 35Sunday, November 18, 12
  • 36. Using it SystemServices ss; ss = new SystemServices(client); JsonHttpResponseHandler connectHandler = new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject response) { JSONObject user = response.getJSONObject("user"); int uid = user.getInt("uid"); } @Override public void onFailure(Throwable e, JSONObject response) { // System.Connect call failed } @Override public void onFinish() { } }; ss.Connect(connectHandler); Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 36Sunday, November 18, 12
  • 37. Using it UserServices us; us = new UserServices(client); JsonHttpResponseHandler loginHandler = new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject response) { boolean error = false; JSONObject user = response.getJSONObject("user"); } @Override public void onFailure(Throwable e, JSONObject response) { // Username or password were incorrect } @Override public void onFinish() { activity.hideProgressDialog(); } }; activity.showProgressDialog("Logging you in"); us.Login("username", "password"); Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 37Sunday, November 18, 12
  • 38. Extending it public class DemotixServices { private ServicesClient client; public DemotixServices(ServicesClient c) { client = c; } public void Register(String fname, String lname, String name, String email, String password, AsyncHttpResponseHandler responseHandler) { JSONObject params = new JSONObject(); try { params.put("name", name); params.put("mail", email); params.put("pass", password); params.put("field_firstname", fname); params.put("field_lastname", lname); params.put("legal_accept", "1"); params.put("from_mobile", "1"); } catch (JSONException e) { e.printStackTrace(); } client.post("demotix_services_user/register", params, responseHandler); } } Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 38Sunday, November 18, 12
  • 39. File upload InputStream myInputStream = blah; RequestParams params = new RequestParams(); params.put("secret_passwords", myInputStream, "passwords.txt"); File myFile = new File("/path/to/file.png"); RequestParams params = new RequestParams(); try { params.put("profile_picture", myFile); } catch(FileNotFoundException e) {} client.post(...) Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 39Sunday, November 18, 12
  • 40. File upload Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 40Sunday, November 18, 12
  • 41. File upload Turns out that the library tries to read all data before uploading For large files this is a problem as you’ll run out of memory immediately Solution: go low level and build the multi part yourself by attaching FileInputStreams Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 41Sunday, November 18, 12
  • 42. File upload HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, 900 * 1000); HttpConnectionParams.setSoTimeout(httpParams, 900 * 1000); HttpClient httpclient = ServicesClient.client.getHttpClient(); HttpContext httpContext = ServicesClient.client.getHttpContext(); HttpPost httppost = new HttpPost("http://www.example.com/api/mobile/service/upload"); httppost.setParams(httpParams); MultipartEntity entity = new MultipartEntity(); // Add parameters to the post body entity.addPart("param1", new StringBody(param1.toString(),"application/json", Charset.forName("UTF-8"))); // Add file to post body InputStream istream1 = new FileInputStream("file1.jpg"); entity.addPart("file1", new InputStreamBody(istream1, "file1")); httppost.setEntity(entity); HttpResponse response = httpclient.execute(httppost, httpContext); Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 42Sunday, November 18, 12
  • 43. File upload Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 43Sunday, November 18, 12
  • 44. File upload Nope There is no easy way of getting the progress of an upload We fake it: use CustomMultiPartEntity instead of MultiPartEntity This class notifies a listener of the bytes read so far Calculate an approximation of the size of the post data Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 44Sunday, November 18, 12
  • 45. File upload CustomMultiPartEntity postEntity = new CustomMultiPartEntity(new CustomMultiPartEntity.ProgressListener() { @Override public void transferred(long num) { updateUploadProgress((int) ((num / (float) totalUploadSize) * 100)); } }); try { postEntity.addPart("param1", new StringBody(param1.toString(),"application/json", Charset.forName("UTF-8"))); totalUploadSize = postEntity.getContentLength(); for (Map.Entry<String, String> entry : files.entrySet()) { try { InputStream istream = new FileInputStream(entry.getValue()); postEntity.addPart(entry.getKey(), new InputStreamBody(istream, entry.getKey())); totalUploadSize += new File(entry.getValue()).length(); } catch (FileNotFoundException e) {} } httppost.setEntity(postEntity); } catch (UnsupportedEncodingException e) {} HttpResponse response = httpclient.execute(httppost, httpContext); Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 45Sunday, November 18, 12
  • 46. Another caveat Varnish has issues by default You have a rule that does “return post” for, well, POST calls Upload will fail with a socket exception Add another rule that does “return stream” for POST calls made to your upload url Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 46Sunday, November 18, 12
  • 47. Thanks! Questions? Alexandru Badiu. Twitter @voidberg Web http://ctrlz.ro Email andu@ctrlz.ro D.O http://drupal.org/user/8662 Drupalcamp Arad 2012 - Alexandru Badiu - REST Drupal 47Sunday, November 18, 12