Using self-signed certificates and resolving SSL version with iOS5 SDK
November 16th, 2011
Using Self-signed certificates with iOS5
There have been a few changes to SSL in iOS5 SDK, which caused some snafus to our iPhone app. We have been using ASIHTTPRequest library for accessing our web services that require SSL and we use self-signed certificates in test environment. I noticed that the iPhone app wasn’t able to connect to the server after upgrading to iOS5 SDK. The original code in ASIHTTPRequest.m that accepted self-signed certificates looked like:
NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
[NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
kCFNull,kCFStreamSSLPeerName, nil];
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, (CFTypeRef)sslProperties);
However, the iOS5 SDK deprecated kCFStreamPropertySSLPeerCertificates, kCFStreamSSLAllowsExpiredCertificates, kCFStreamSSLAllowsExpiredRoots, kCFStreamSSLAllowsAnyRoot and requires using kCFStreamSSLValidatesCertificateChain to disable certificates, e.g.:
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings,
[NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
[[self readStream] setProperty:[NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, kCFStreamSSLValidatesCertificateChain, nil] forKey:(NSString *)kCFStreamPropertySSLSettings];
Here is a complete example if you need to skip validation without using ASIHTTPRequest:
- (void)start {
CFStringRef bodyString = CFSTR("xml....");
CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, bodyString, kCFStringEncodingUTF8, 0);
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("POST"), (CFURLRef) [NSURL URLWithString:@"https://optionshouse.com:443/m?"], kCFHTTPVersion1_1);
CFHTTPMessageSetBody(request, bodyData);
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(NULL, request);
self.inputStream = (NSInputStream *) readStream;
[self.inputStream setProperty:[NSDictionary dictionaryWithObjectsAndKeys:(id) kCFBooleanFalse, kCFStreamSSLValidatesCertificateChain, nil ] forKey:(NSString *) kCFStreamPropertySSLSettings];
[self.inputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
CFRelease(readStream);
CFRelease(request);
}
- (void)stop {
[self.inputStream setDelegate:nil];
[self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream close];
self.inputStream = nil;
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted: {
NSLog(@"open");
} break;
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t junk[1024];
NSLog(@"has bytes");
bytesRead = [self.inputStream read:junk maxLength:sizeof(junk)];
if (bytesRead == 0) {
NSLog(@"read end");
[self stop];
} else if (bytesRead < 0) {
NSLog(@"read error");
[self stop];
} else {
NSString *string = [[[NSString alloc] initWithBytes:junk
length:bytesRead
encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"Read %@", string);
}
} break;
case NSStreamEventErrorOccurred: {
NSError * error = [self.inputStream streamError];
NSLog(@"error %@ / %zd", [error domain], (ssize_t) [error code]);
[self stop];
} break;
case NSStreamEventEndEncountered: {
NSLog(@"end");
[self stop];
} break;
default: {
assert(NO);
[self stop];
} break;
}
}
Using SSL with old F5 load balancers
Another issue I found with iOS5 was that despite using valid certificate in production environment, the SSL was still not working. Based on Apple's documentation, it turned out that the default version for SSL has been changed to TLS 1.0 and our production environment used F5 load balancer, which required SSLv3. So, I had to explicitly specify SSL version in ASIHTTPRequest.m
const void* keys[] = { kCFStreamSSLLevel };
// kCFStreamSocketSecurityLevelTLSv1_0SSLv3 configures max TLS 1.0, min SSLv3
// (same as default behavior on versions before iOS 5).
// kCFStreamSocketSecurityLevelTLSv1_0 configures to use only TLS 1.0.
// kCFStreamSocketSecurityLevelTLSv1_1 configures to use only TLS 1.1.
// kCFStreamSocketSecurityLevelTLSv1_2 configures to use only TLS 1.2.
const void* values[] = { CFSTR("kCFStreamSocketSecurityLevelTLSv1_0SSLv3") };
CFDictionaryRef sslSettingsDict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslSettingsDict);
CFRelease(sslSettingsDict);
With these changes, our iPhone app was working as expected.
Acknowledgement
Many thanks for Apple Engineer "Quinn", who helped resolving the SSL issues.
Implementing Application Search and Custom Suggestions in Android
August 17th, 2011
Being a search company, Google provides search button on most Android devices and allows a simple API to create search bar in the Android application. In addition, you can also use suggestions API to add your own suggestions either from the database or remote service. In this blog, I will demonstrate how to add the search and suggestions to your Android application.
Searchable configuration
The first thing you would need to configure is searchable.xml in res/xml directory. Here is an example file:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name" android:hint="@string/search_hint"
android:searchMode="showSearchLabelAsBadge"
android:searchSettingsDescription="suggests symbols"
android:queryAfterZeroResults="true"
android:searchSuggestAuthority="com.plexobject.service.SearchSuggestionsProvider"
android:searchSuggestSelection=" ? " android:searchSuggestIntentAction="android.intent.action.VIEW">
>
</searchable>
Search Activity
Next you would create your search activity, which would be automatically started when a user hits the hard search button or search option from menu/actionbar.
package com.plexobject.activity;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import com.plexobject.R;
public class SearchActivity extends Activity {
private static final String TAG = SearchActivity.class.getSimpleName();
public SearchActivity() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_activity);
this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_LOCAL);
final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.ACTION_SEARCH.equals(queryAction)) {
this.doSearchQuery(queryIntent);
} else if (Intent.ACTION_VIEW.equals(queryAction)) {
this.doView(queryIntent);
} else {
Log.d(TAG, "Create intent NOT from search");
}
}
@Override
public void onNewIntent(final Intent queryIntent) {
super.onNewIntent(queryIntent);
final String queryAction = queryIntent.getAction();
if (Intent.ACTION_SEARCH.equals(queryAction)) {
this.doSearchQuery(queryIntent);
} else if (Intent.ACTION_VIEW.equals(queryAction)) {
this.doView(queryIntent);
}
}
private void doSearchQuery(final Intent queryIntent) {
String queryString = queryIntent.getDataString(); // from suggestions
if (query == null) {
query = intent.getStringExtra(SearchManager.QUERY); // from search-bar
}
// display results here
bundle.putString("user_query", queryString);
intent.setData(Uri.fromParts("", "", queryString));
intent.setAction(Intent.ACTION_SEARCH);
queryIntent.putExtras(bundle);
startActivity(intent);
}
private void doView(final Intent queryIntent) {
Uri uri = queryIntent.getData();
String action = queryIntent.getAction();
Intent intent = new Intent(action);
intent.setData(uri);
startActivity(intent);
this.finish();
}
}
Note that you would need to implement viewing logic after the search in doSearchQuery method. Also, if your search service is being called from the search bar, you would get query string from SearchManager.QUERY parameter in the intent and if your search service is being called from the suggestion, the query string would be in getData or getDataString. You would then declare the activity in your AndroidManifest.xml such as:
<activity android:name=".activity.SearchActivity"
android:configChanges="orientation|keyboardHidden" android:label="@string/app_name"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
For all the activities that would be using search activity would declare SearchActivity in their definition inside AndroidManifest.xml such as:
<activity android:name=".activity.MyActivity"
android:configChanges="orientation|keyboardHidden" android:label="@string/app_name"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.default_searchable"
android:value=".activity.SearchActivity" />
</activity>
In order to support soft search, I added an option for search to the action-bar as well as menu item and then invoked following method when the search menu was clicked:
@Override
public boolean onSearchRequested() {
startSearch("default-search", true, null, false);
return true;
}
This would start your search activity described above.
Suggestions Provider
I already added com.plexobject.service.SearchSuggestionsProvider in the searchable.xml file above, which provides custom implementation of suggestions.
package com.plexobject.service;
import java.util.List;
import android.app.SearchManager;
import android.content.ContentValues;
import android.content.SearchRecentSuggestionsProvider;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.util.Log;
public class SearchSuggestionsProvider extends SearchRecentSuggestionsProvider {
static final String TAG = SearchSuggestionsProvider.class.getSimpleName();
public static final String AUTHORITY = SearchSuggestionsProvider.class
.getName();
public static final int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;
private static final String[] COLUMNS = {
"_id", // must include this column
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID };
public SearchSuggestionsProvider() {
setupSuggestions(AUTHORITY, MODE);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String query = selectionArgs[0];
if (query == null || query.length() == 0) {
return null;
}
MatrixCursor cursor = new MatrixCursor(COLUMNS);
try {
List list = callmyservice(query);
int n = 0;
for (MyObj obj : list) {
cursor.addRow(createRow(new Integer(n), query, obj.getText1(),
obj.getText2()));
n++;
}
} catch (Exception e) {
Log.e(TAG, "Failed to lookup " + query, e);
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException();
}
private Object[] createRow(Integer id, String text1, String text2,
String name) {
return new Object[] { id, // _id
text1, // text1
text2, // text2
text1, "android.intent.action.SEARCH", // action
SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT };
}
}
You may store your suggestions in the database and may need to query the database and return database cursor. However, above provider makes a remote call and then populate Cursor using MatrixCursor from the objects that are returned from the remote service. I omitted actual remote calls that return your suggestions as it can be very application specific. As our implementation does not add anything in the database, it throws exceptions in the insert, update and delete methods. The suggestions provider can support one-line or two-line suggestions and above implementation is using two-line suggestions.
Finally, you will need to add provider and default search activity to your AndroidManifest.xml:
<provider android:name=".service.SearchSuggestionsProvider"
android:authorities="com.plexobject.service.SearchSuggestionsProvider"></provider>
<meta-data android:name="android.app.default_searchable"
android:value=".activity.SearchsActivity" />
As you would be calling remote service you would need following permission in your AndroidManifest.xml: :
<uses-permission android:name="android.permission.INTERNET" />
If you were using database suggesgtion provider, you can add suggestions to the database as:
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchSuggestionsProvider.AUTHORITY, SearchSuggestionsProvider.MODE);
suggestions.saveRecentQuery(queryString, null);
and remote suggestions from the database as:
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SearchSuggestionsProvider.AUTHORITY,
SearchSuggestionsProvider.MODE);
suggestions.clearHistory();
Summary
As you can see with a few line of configuration and code you can create application wide search. You can also expose your search to default Android search and you can read Android documentation for more information.
Overview of Trading Floor Game for iOS
June 21st, 2011
Introduction
The Trading Floor is a social game based on auction-styled stock trading where you trade virtual stocks and options with your friends and family. Each game lasts for a day, where you join a game as a stock broker with objective of buying stocks at low-price and selling at high-price, thus making most money from the trades. Unlike a real-world stock market, each game is limited to a single exchange, which can be public or private. In addition to trading, the Trading Floor brings a lot social aspects to the game such as building private exchanges, which can be only joined by your friends and expressing like-ability for a company, which determines a Buzz for the company. Also, the members can view activities for other players in a game, view their gain/loss from the trading, view ranking of the players in terms of gain/loss and comment on their trades. At the end of a game, the top players for each game are awarded badges, which are permanently visible from their profile.
Getting Started
You can download Trading Floor from Apple AppStore.
| iPad | iPhone |
|---|---|
|
|
You would see listing o active games that you can join. A game is started for each exchange in the system which can be public or privately created by the users.
Joining a game
When you click on games, you would see listing of all games such as:

From there you can select the game that you wish to join and you would be prompted to enter a nick name that you can use to join the game such as:
| iPad | iPhone |
|---|---|
|
|
If an exchange is private, you would require a password to join and would need to contact the owner of the exchange. After clicking the Join button the Trading Floor would allocate a virtual cash of $10,000 to you and would create a portfolio with stocks and options for ten companies, where five of the companies are chosen from your favorite companies and five companies are randomly chosen. It takes about a minute to populate all the portfolio and then you can start trading. After joining the game you can also browse other players, view activities, portfolio, capital gains, orders and badges. There are also buttons below to quickly select between players, view rankings, floor bids, comments and lookup quotes, e.g.
| iPad | iPhone |
|---|---|
|
|
Browsing Players
You can see list of players that have joined the game by touching “Browse Players” option, e.g.
| iPad | iPhone |
|---|---|
|
|
When you select the player, you will see more details about the player and view activities, orders, portfolio, capital gains, badges, etc. as displayed above.
Selling Stocks/Options from Portfolio
When you select “Portfolio” option, you would see summary of your available cash and list of all stocks/options you hold such as:
| iPad | iPhone |
|---|---|
|
|
When you select “Sell” link, you would be taken to the order screen such as:
| iPad | iPhone |
|---|---|
|
|
You can specify your selling price and then select “Sell” button to submit the bid. It would take you to the Floor Bid, which lists all bids for buying and selling stocks/options.
Floor Bids
The Floor Bids show all companies available for buy or sell. When you post bids to sell or buy, the go to the floor bids. You can accept the bids that other players have posted for stock/option sell or buy on the Floor Bids. Note that when you post bids you specify the price for the stock but when you accept bids you don’t negotiate. If you need to change the price for your bids, you can edit your open orders. Also, when you trade stocks or options, you are only trading to the people who are in the same exchange.
| iPad | iPhone |
|---|---|
|
|
When another user accepts your bid, the sale is completed and you would collect gain/loss from the trade.
Capital Gains
You can view your overall capital gains as result of trading or portfolio gain because the market price for your stocks went up from the player details, orders or portfolio screen. You can drill deep into your capital gain and find out how much profit/loss you made for each order by selecting Capital Gains option from the home screen or selecting Capital Gains row from the player details, orders or portfolio screen.
| iPad | iPhone |
|---|---|
|
|
Quote Lookup
You can lookup stock/option quotes as well as latest news for the companies from Quote-Lookup menu option. You can also buy or sell stocks from the Quote-Lookup screen based on your portfolio.
| iPad | iPhone |
|---|---|
|
|
In addition to buying and selling stocks/options, you can express how you feel about a company by liking/disliking/loving/hating. This in turn determines the buzz quote for that company. The more players like a company, the higher the buzz quote value that company would have. Unlike buying and trading stocks/options, which are limited to a single exchange (both public and private), the buzz quote is calculated across all exchanges. However, buzz quote is reset when the Trading Floor market is closed (11pm EST). Here is another benefit of Buzz Quote, the BOT player would buy stocks from you at the Buzz Quote price. So, if a stock becomes popular you can make more profit by selling it to the BOT.
Orders history
You can view your past orders for selling or buying stocks/options by selecting “Orders” option, which would show something like:
| iPad | iPhone |
|---|---|
|
|
You can also drill into details for each order by selecting the row for the order such as:
| iPad | iPhone |
|---|---|
|
|
Player Rankings
You can view your the rankings of players for each game by selecting “Rankings” option which lists players sorted by the capital gains they earned, e.g.
| iPad | iPhone |
|---|---|
|
|
Comments
You can view comments on the game by selecting “Comments” option, e.g.
You can add your own comment by selecting “+” icon and typing your message, e.g.
My Badges
The top players for each game get badges at the end of the game, which are permanently visible under the “Badges” option, e.g.

When you select your badge, you will be taken to the old game that you played and can go back to your portfolio, orders and other details.
Exchanges
The exchanges are places where public companies are listed for trading. You can join exchanges created by your friends or build your exchanges. A unique game is started for each exchange, where member players trade against other member players. You can view both public and private exchanges by selecting “Exchanges” from main menu . You would see three types of exchanges: mine, public and private. Mine would list all exchanges that you have created, public would list all public exchanges and private would list top private exchanges. You can also search private exchanges.
| iPad | iPhone |
|---|---|
|
|
You can create your own exchange by selecting “+” icon and typing in the exchange symbol, name and password, e.g.

You can then let your friends know about the exchange symbol and password so that they can join them.
Industries
You can view top level industries by selecting “Industries” from the main menu, e.g.
| iPad | iPhone |
|---|---|
|
|
When you select an industry, it would list all companies that belong to that industry.
Companies
You can browse or search over 20,000 companies by selecting “Companies” option from the main menu, e.g.
| iPad | iPhone |
|---|---|
|
|
When you select a company, it would show latest quote, news and buzz quote for that company.
Companies Around Me
Trading Floor takes advantage of your geo-location capabilities and finds companies that are located near your location, e.g.
Leader Board
The leader board shows top players with most capital gains for all the games that they have played over last month. This option uses Game Center and requires login to the Game Center. You can post your scores to the leader board by selecting the \”Post Capital Gains\” option from the main menu. Also, you can send match invites to your friends at Game Center.
|
|
|
In the end, I hope you find Trading Floor, a fun game for your friends and family and you may learn a few things about the stock market as well. Enjoy!.
Deploying Rails 3.0 App on Amazon EC2
June 1st, 2011
It’s been a few years since I wrote a short HOW-TO on working with EC2 , but recently I tried to migrate the backend of Trading Floor – Facebook and iOS game I have been developing to EC2. So I am documenting the steps for setting up the EC2 for Rails 3.0.
Pre-requisites
I assume you already signed up for EC2, otherwise go to http://aws.amazon.com/ec2/ to signup. Also, you will need Java 5.0 or above, which you can download it from Oracle.
Download EC2 API Tools
First, download EC2 from http://developer.amazonwebservices.com/connect/entry.jspa?externalID=351 and uncompress it in your root directory.
Create a X.509 Certificate
Next, create a X.509 certificate from the AWS Account section. You can then download your certificate and key safely, e.g. I saved them in .ec2 directory under my home directory. Note that you will not be able to download the key again, so don’t lose it.
Environment Variables
Next, I changed my shell as:
export EC2_HOME=~/ec2-api-tools-1.4.2.4 export PATH=$PATH:$EC2_HOME/bin export EC2_KEY_DIR=~/.ec2 export EC2_PRIVATE_KEY=$EC2_KEY_DIR/pk-HFG55OCFPZARA6YHW5JGIE6JFD7EQE72.pem export EC2_CERT=$EC2_KEY_DIR/cert-HFG55OCFPZARA6YHW5JGIE6JFD7EQE72.pem
Where EC2_PRIVATE_KEY and EC2_CERT points to the X.509 key and certificate I downloaded from the Amazon.
Create a Key-Pair
Then I created a pair of keys as:
ec2-add-keypair plexobject
Create a Security Group
I then created a security group for the server
ec2-add-group web -d 'Web Server' ec2-authorize web -P tcp -p 22 -s 0.0.0.0/0 ec2-authorize web -P tcp -p 80 -s 0.0.0.0/0 ec2-authorize web -P tcp -p 443 -s 0.0.0.0/0
Finding a basic Ubuntu based AMI
Previously I used S3 based AMI, but Amazon now supports EBS based AMIs that has advantage that any changes to the root survive instances of EC2. I launched EC2 instance with basic Ubuntu 11.0 Natty from http://alestic.com/ as:
ec2-run-instances ami-06ad526f --instance-count 1 --instance-type m1.small \ --key plexobject --group web -z us-east-1d -m
Where -z describes the availability zone and -m turns on monitoring.
Installing Ubuntu Packages
I then proceeded to install basic packages such as Java, Curl, Git, Build, Ruby (1.8.7) and Rails (3.0.3) based on Rails/Ubuntu docs such as:
sudo apt-get install openjdk-6-jdk sudo apt-get install mercurial sudo apt-get install curl git-core build-essential zlib1g-dev libssl-dev libreadline5-dev sudo apt-get install libcurl4-openssl-dev sudo apt-get install ruby sudo apt-get install rubygems1.8 sudo gem install rubygems-update sudo update_rubygems sudo gem install rails
I then edited /etc/profile /etc/bash.bashrc and added environment variables
export PATH=$PATH:/var/lib/gems/1.8/bin/ export JAVA_HOME=/usr/lib/jvm/java-1.6.0-openjdk/
Next, I installed Sqlite and Mysql:
sudo apt-get install sqlite3 libsqlite3-dev sudo gem install sqlite3-ruby sudo apt-get install mysql-server mysql-client sudo apt-get install libmysql-ruby libmysqlclient-dev
Next, I installed Apache and Passenger:
sudo apt-get install apache2 apache2-mpm-prefork apache2-prefork-dev sudo apt-get install apache2-dev libapr1-dev libaprutil1-dev sudo gem install passenger sudo /var/lib/gems/1.8/gems/passenger-3.0.7/bin/passenger-install-apache2-module*
I then edited /etc/apache2/apache2.conf and added:
LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-3.0.7/ext/apache2/mod_passenger.so PassengerRoot /var/lib/gems/1.8/gems/passenger-3.0.7 PassengerRuby /usr/bin/ruby1.8
and then restarted apache
/etc/init.d/apache2 restart
Creating EBS Volume for Data
Next, I created an EBS volume to store all data such as database tables and Rails application from the AWS Console and then attached it to the instance as:
ec2-stop-instances i-73ab181d ec2-attach-volume vol-612eaa0a -i i-73ab181d -d /dev/sdf ec2-start-instances i-73ab181d
Note that you have to create the EBS volumen in the same availability zone as your instance. I then logged into my machine using
ssh -i plexobject.pem ubuntu@ec2-50-19-134-251.compute-1.amazonaws.com
and then formatted the newly built EBS volume as:
sudo fdisk -l sudo mkfs -t ext4 /dev/xvdf
I then edited /etc/fstab and added
/dev/xvdf /data auto defaults,nobootwait,noatime 0 0
and then rebooted machine
sudo reboot
Moving Mysql Data Directory
Mysql installs data directory on the root volume in /var/lib/mysql directory, which I wanted to move to newly created volume. So I created a directory /data/mysql so I stopped mysql:
sudo /etc/init.d/mysql stop
I then copied mysql data directory such as:
sudo cp -R -p /var/lib/mysql/mysql /data/mysql sudo chown -R mysql:mysql /data/mysql/
I didn’t copy entire mysql directory, only mysql subdirectory. Next I edited /etc/mysql/my.cnf and changed datadir to /data/mysql directory and then edited /etc/apparmor.d/usr.sbin.mysqld and changed all /var/lib/mysql to /data/mysql. Finally I restarted AppArmor profiles as:
sudo /etc/init.d/apparmor reload
Then restarted mysql:
sudo /etc/init.d/mysql restart
I changed my root password and created a local mysql user as
mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('mypass');
mysql> GRANT ALL PRIVILEGES ON *.* TO 'tfuser'@'localhost' IDENTIFIED BY 'mypass' WITH GRANT OPTION;
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON dbname.* TO 'tfuser'@'localhost' IDENTIFIED BY 'mypass';
I copied my app to /data/trading_floor and changed permissions of all files to www-data
sudo chown -R www-data:www-data /data/trading_floor
Then created /etc/apache2/sites-available/tf with
ServerAdmin shahbhat@gmail.com ServerName tf.plexobject.com DocumentRoot /data/trading_floor/public/ AllowOverride all Options -MultiViews RailsEnv production
Finally, I restarted apache
/etc/init.d/apache2 restart
Creating Elastic IP address
I wanted a permanent IP address for the server so I created EIP using AWS Console. Then associated my instance with the new IP address:
ec2-associate-address 50.19.248.7 -i i-73ab181d
It rebooted your machine with the new IP address. I then changed DNS zone and pointed tf.plexobject.com to 50.19.248.7 (this may take hours to propagate). Next, I changed my Facebook app’s configuration and iOS app’s configuration to point to tf.plexobject.com.
Creating my EBS Image
Once I was happy with the server configuration, I created EBS image for future use. First, I detached data volume and then created image as follows:
ec2-stop-instances i-f97ca197 ec2-detach-volume vol-3f65d954 ec2-create-image i-f97ca197 -n tf-20110601 -d 'Trading Floor Application Server'
I terminated my previous instance as
ec2-terminate-instances -i i-f97ca197
and created instance with the new image
ec2-run-instances ami-80837ae9 --instance-count 1 --instance-type m1.small --key tf --group web -z us-east-1d -m
After the launch, you would have to reattach the data volume
ec2-stop-instances i-73ab181d ec2-attach-volume vol-612eaa0a -i i-73ab181d -d /dev/sdf ec2-start-instances i-73ab181d
Summary
Voila, I had my game application running on the cloud. In order to cut per/hour cost I reserved instances for entire year. I am not quite done with my server and am now working on application specific configuration and adding better monitoring/backup. As, we have learned from recent Amazon Cloud outage that deploying your app on the cloud is only half the work, making it performant, scalable and fault tolerant is other half which is still manual work. Finally, I plan to release the Facebook app for Trading Floor and submit iOS app in a couple of weeks, be sure to try it and send me your suggestions.
Review of Guy Kawasaki’s book – “Enchantment: The Art of Changing Hearts, Minds, and Actions”
April 25th, 2011
I recently read Guy Kawasaki’s book Enchantment: The Art of Changing Hearts, Minds, and Actions. This book shows how to engage with other people and build better relationships similar to Dale Carnegie’s book How to Win Friends & Influence People. Though, this book covers these topics in more professional context and it includes advice from several other business and management books. As, Guy is also a very savvy social media user, this book covers several tips on using modern networking tools to build personal relationships with others.
Enchantments
Guy describes enchantments as a way of delighting people with a product, service or organization, which is similar to the concept of Customer Delight popular in business literature. Guy suggests to start with a good product or service and fill people with the delight. This also reminded me Tony Hsieh’s book Delivering Happiness: A Path to Profits, Passion, and Purpose.
Likable and Trustworthy
Once you have a good product, you build the enchantments by being likable and trustworthy. The likability chapter covers several pointers such as smile, dress appropriately, firm handshake, accept others, yes attitude, and work in open environment. Guy encourages finding shared interests with other party and creating win-win situation when negotiating. On being trustworthy, Guy suggests giving people benefit of doubt, disclosing interests and positioning yourself. Some of these techniques seemed similar to what I have read from Stephen Covey’s book The 7 Habits of Highly Effective People and from agile development gurus.
Pre-Launch/Premortem
Guy gives a great set of tips on preparation before launching a product and suggests the product should be:
- great
- deep
- intelligent
- complete
- empower
- elegant
Product Launch
On launching a new product, Guy suggests telling personal stories, showing courage, planting many seeds and aspiring people by promising a better world. This chapter reminded me of how Steve Jobs promotes Apple products by promising better future, giving great demo, and simplifying the interface.
Overcoming Resistance
On overcoming resistance, Guy suggests creating perception of ubiquity and scarcity and finding a way to agree, which enhances your chances of being likable. I found the chapter on overcoming resistance a bit weak and encourage readers to look at Switch: How to Change Things When Change Is Hard and Fearless Change: Patterns for Introducing New Ideas.
Enchanting Influencers
Guy offers a great practical guidance on enchanting influencers such as working on grassroots, creating intrinsic motivation, paying it forward, and reciprocity. I liked his advice of saying “I know you’d do same for me” instead of saying “You’re welcome” in response to thank-you”.
Ecosystems
In order to create a grassroots support of your products or services, Guy recommends creating a product worthy of ecosystem and then lists several tools, which encourage exchange of ideas and collaboration such as user-groups, blogs, conferences, reward system, open architecture. Another key factor for ecosystem is having a diversified team, which different roles such as advocate, skeptic, visionary, adult, evangelist and rain maker.
Push Technology
This is one of best chapters in the book and shows how to use modern push technologies such as Presentations, Email, and Twitter. Guy recommends engaging many people fast and often. He also recommends giving them credit and providing a value for them. On presentations, Guy recommends customizing intro based on audience, selling dreams, dramatizing and rehearsing it. He suggests keeping the presentation short with 10-20-30 rule, where presentation has no more than 10 slides, takes 20 minutes and uses no less than 30-size font. For email, Guy suggests keeping it short (under six sentences) and asking for a specific action.
Pull Technology
On pull technologies, Guy suggests creating a website/blog with good content, refreshing contents frequently and having an about page. On Facebook, Guy suggests having a good landing page and being helpful. On Linked-in, Guy suggests having a great profile and reaching out to others actively.
Enchanting Employees
Guy also provides useful set of pointers on being a good employer such as engaging employees by providing MAP (Mastery, Autonomy, Purpose) and empowering employees to do the right things. He recommends instead of judging actions of others against their intentions, be harsh on yourself and judge your results against their intentions. He also suggests celebrating success and includes tips from Good Boss, Bad Boss: How to Be the Best… and Learn from the Worst such as protecting people from intrusions. Guy cites Michael Lopp’s advice from Managing Humans: Biting and Humorous Tales of a Software Engineering Manager such as setting ambitious goals, enabling, appreciating and providing feedback to the employees.
Enchanting Boss
On enchanting boss, Guy recommends:
- make your boss look good
- drop everything when boss asks for something
- under-promise and over-deliver
- prototype work by completing part of assignment and asking for feedback
- show and broadcast progress while giving credit to colleagues who helped
- form friendship
- ask for mastership
- deliver bad news early
Resist Enchanters
Finally on resisting enchanters, Guy suggests looking far in future, knowing your limits, having a skeptic attitude and not falling for example of one.
Summary
In this book, Guy Kawasaki provided a good collection of practical advice on building better interpersonal relationships and using tools from social media effectively. It shows that in order to build long lasting relationships, you have to be sincere and always be willing to help others. I found Guy’s pointers on push and pull technologies most helpful as he has created cult of followers on Twitter and Facebook and provided a number of tips from his personal experience.
Tips and Idioms for Developing Network based iOS Applications
February 9th, 2011
Over the last year, I built a number of iOS applications such as yogaspot, iPlexLotto and OptionsHouse (still in development), which are backed by the network services. I am documenting a few idioms and best practices that I have used in these applications along with some lessons learned:
Separation of concerns
The iOS APIs are based on MVC design pattern, which encourages separating the non-GUI code such as Models, Services, Helper classes from your views and uses Controllers to communicate between GUI and non-GUI code. As with building any other kind of application, keeping your design and code clean would make your application more maintainable and extendable. The iOS APIs make heavy use of protocols for interface segaration principle. Here are some of the usages of protocols in iOS APIs:
Data Sources
The iOS API use protocols for data sources, which then populate views such as table views, pickers and other views. In most application, your controller will be the data source but I used the data source protocols to create Wizard like behavior by implementing the data source interface in multiple classes and displaying different contents based on the state of application.
Delegates
The delegates are callback interface that are generally used for communication between classes or threads. As, protocols in Objective C can define both mandatory and optional behavior, delegates are also used to provide customized UI behavior. The iOS SDK uses delegates for customizing UI in a number of classes such as UISearchDisplayDelegate, UISearchBarDelegate, UIGestureRecognizerDelegate, UITableViewDelegate, etc. Here is an example of delegate for communication between classes:
@protocol OrderCallbackDelegate@required - (void) addedOrder: (Order *)order; @end
Implementing Delegate
@protocol OrderCallbackDelegate The iOS provide several options for hiding private details or extending existing classes. For example, you can use @private keyword in the class declaration to enforce encapsulation such as: The myVar attribute can only be accessed by the instance of MyClass.
Objective C supports inheritance and composition as other object-oriented languages, which I won't cover here. However, the Categories feature of Objective C language is a more powerful way to extend an existing class without modifying it. For example, you can extend UIColor to provide additional colors as follows: Other examples include JSON parsing library, which extends NSString to support JSON parsing. As with any other application, keeping your code organized in your iOS project helps readability and maintenance. One thing I don't like about XCode is that when you create groups for organizing classes in your XCode project, it doesn't create folders, so you will find all classes in Classes directory. So, I prefer creating folders on the file system and then add folders as groups to XCode. Here is an example of different groups I used in my projects: Objective C supports both static and dynamic language syntax and has powerful support of reflection. This gives you powerful tool to write concise code. For example, I could not find any XML or JSON binding library for Objective C, so I built helper classes that parsed XML and JSON and automatically bound the properties to the classes that used same names, e.g. And I am skipping more details but you can download the implementation from Parser.zip. Any network based iPhone app would rely on the backend network services and you would make these easier if these services are REST based. I recommend ASIHTTPRequest library, which supports asynchronous communication and concurrency. When designing an modal dialog that interacts with the backend, it helps if you show loading message or use UIActivityIndicator when making network request (even though the network request would be asynchronous). For example, the OptionsHouse app shows loading status when executing trades as it may take a couple of seconds to communicate with the server. When you are developing an iPhone application, you will learn that despite the increasing powerful hardware it's not same as desktop, laptops or servers we have in the office. Thus, you will have to learn how to optimize every aspect of the application and one of the key factor for network application is the protocol for the payload. I used both XML and JSON based services and found that JSON parsing was at least twice as fast compare to XML. Optionally, you can use Binary format such as binary plist, which further improves the performance of parsing. An iPhone user might be connected on Edge, 3G, 4G or WiFi network and the network speed would vary. Though, you can make a general assumption that WiFi would be faster than 3G/4G, which would be faster than Edge, but I suggest that you test the speed within the app instead of relying on the connection. For example, you might be using WiFi at a local coffee shop or a conference, where the speed is slower than the edge or 3G. On websites, the network services are often behind the web server so when they are down it's easy to post a page for network outage. You will have to handle such outages in the application and notify the users about the outages. A network based application requires fetching the data from the server and it's best that you communicate with the services without blocking user interface. Both ASIHTTPRequest and NSURL supports asynchronous communcationions, where you register callback methods when the data arrives or network failure occurs. I built a thin layer on top of ASIHTTPRequest so that I could use blocks: Here is how you can use ASIHTTPRequest to make REST POST and GET requests: Above methods setup downloadCompleted as a callback handler for successful completion of the request and downloadFailed for service failure, which are defined as follows: Both methods lookup the user's block that was passed upon request and then callback the proper block. In certain cases, I extended downloadFailed method to automatically retry the request upon certain failure types such as timeout errors. I used Gateway and Proxy patterns to wrap remote services in a local class that defined methods using local Model classes and automatically converted Models into proper XML/JSON format for requests and responses. This kept the code clean and in a few cases, I had to switch from XML to JSON, which was made in these classes and rest of application remained unchanged. As both iPlexLotto and OptionsHouse refreshed data continuously in background, I needed an efficient way to check if the data is updated on the server. HTTP provides a great way to reduce bandwidth by using E-Tag or Last-Modified headers, and we used similar techniques to send modified timestamp as part of the response and used it in future requests. The server then compared the timestamp in the request with local state and sent response only if the data was modified. A critical aspect of network based applications is handling network errors. There can be various reasons for the network errors such as phone might be in Airplane mode so no network requests should be made. In other cases network connection might be too slow or the service is down. Also, you may be continuously updating data in background and in some cases occasional network interruptions can be ignored. This requires careful consideration of the UI so that the application is smart about the environment and retries when possible instead of annoying users with popup alerts. A quick way to improve performance is to cache contents for a certain period of time depending on the data. As iOS devices have limited memory, I used in-memory cache for small objects and used file based cache for larger objects. For example, I cached lottery results for a couple of hours, on the other hand for trading application I cached orders and positions for a few seconds. I didn't' cache quotes data as it changed frequently and showing stale quotes can result in financial loss. In addition to the user data, I also cached chart images or other meta-data that is not frequently changed. Here is a simple implementation of file based cache: Both iPlexLotto and OptionsHouse continuously updated the data in background. For example, the OptionsHouse application fetched stock/option quotes, order transactions, positions in background. I used GCD's based Timers to schedule periodic download of the data: Above timer runs every 5 seconds and polls for new data and download updates for the user. In my applications, I activated the timer when the application comes in foreground (applicationDidBecomeActive method) and disable it when it goes in background (applicationWillResignActive method). Note that this syntax is much simpler compared to prior syntax of NSTimer and NSRunLoop. Apple added support for multi-tasking in iOS SDK 4.0, which means an application can now be in one of these states When an application is in foreground and interacting with the user. When an application is on its way to background and stops receiving the events. When an application is running in background. When application is still in memory but not executing. The iOS SDK added a number of callback methods for the state transfer such as: On the older devices without multi-tasking support, applicationWillTerminate is called after applicationDidEnterBackground. The iOS SDK provides multiple ways to add concurrency support to your application. You can create NSThread as follows However, there is an easier way to create a thread by invoking a custom method on an object as follows: Alternatively, you can start a background task as follows: You will then define your method for doing the work as follows: Note that you will have to create an auto-release pool before doing the actual work. Though, these APIs are simple to use but managing threads manually is harder and you have to synchronize shared data, cleanup upon termination or when the app goes in background. See Apple's documentation for more details. NSOperationQueue is much easier way to manage concurrency as you don't have to create threads manually. For example, you can create an operation to save data asynchronously as follows: Instead of using NSInvocationOperation, you can define an operation class as follows: You will then add the operation as follows: You can also control the number of concurrent operations via: By far, the easiest way to add concurrency is to use Grand Central Dispatch (GCD) syntax and libraries. The GCD allows you to define tasks via function or blocks and schedules them to the available processors and automatically uses threads behind the scene. Though, it's similar to NSOperation, but the syntax is very terse and the performance is much better than using threads directly. You can also nested blocks such as In addition to using predefined queues, you can also create your own queues, which execute the tasks in serial order such as: Note that if we need to delete the queue, we must do it after all tasks are finished executed. See Apple's documenation for more details on queues. When using multi-threading explicitly or implicitly, you have to synchronize shared data. The IOS SDK provides several constructs such as @synchronized keyword, mutexes, locks, etc. to protect shared data. Nevertheless, this can be error-prone and can slowdown your application, so eliminate shared data if possible. Also, pay special attentions to exceptions within the thread method and cleanup threads upon app termination or when it becomes inactive. See Apple's documentation for more details. Also, any operations that require UI interaction must be run via main thread, though there are some exceptions such as some QuickTime APIs can be run in background. The iOS supports notifications for intercommunication between classes and threads, which promote loosely coupled design. In my applications, I extended notification by creating my own events such as: @end
Note that the event is being fired on the main thread so that UI controllers can respond to the event and update UI without any changes. The controller class would register for the event (typically in viewDidLoad) as follows: Similarly, controller would unregister for the event in viewDidUnload as follows: The controller would then define the method handler for the event as follows: You would then generate the event as follows: Though, you can download crash reports from the Apple website, I added automatic reporting mechanism for bug reports and crashes. I used UncaughtExceptionHandler class to handle unexpected exceptions and automatically send bug reports. I don't try to recover from unexpected exceptions as often the memory corruption leave the application in such unstable condition that it's better to restart the application. The iOS SDK provides several options for persistence such as user preferences, file I/O, serialization, sqlite and CoreData. I used file based serialization for caching resources and objects that implemented NSCoding protocol and used CoreData for more complexed data. I will skip file based serialization as it's fairly straightforward, but would list some tips on CoreData. I ended up creating transient copy of the object for caching purpose. Another factor you will have to consider is what information should be kept in memory and what information can be stored in the database. In general, if the data is small or frequently updated such as stock quote, I kept it in memory, however if the data is large and less often updated such as stock positions, I kept it in the database. In addition, I used the database as interface between UI and network, so the data would get updated asynchronously in background and stored in the database. The network code would then fire an event, which the UI subscribes and would then update the view from the database. This provided fast and efficient way to view large amount of data without waiting. Performance is a key part of usage experience. Apple provides a number of tools such as Shark and Instruments to measure performance bottlenecks as well as investigate memory leaks and CPU activity, file I/O, database and other issues. You need to start measuring the performance on real devices early in the application lifecycle so that you can design the application properly. As, Apple does not support garbage collection on iOS devices, the memory management is one of most painful aspects of the development. I felt that pain as I have been using language such as Java, Ruby, Erlang and Python for past many years that come with garbage collection. Here are some of the tips for memory management: Use Memory-Leak detection feature of Instruments to find the objects that are not properly released. You can set NSZombieEnabled flag to true on your executable, which can help track the memory leaks when the app crashes. Finally, multi-threading further complicates the memory management. For example, you may pass auto-released object to another thread and by the time that the thread uses it, the first thread's auto-release pool released it. So, you must retain the object in separate thread before processing it. Though, iPhone Simulator is fast and easy way to test the application during the development, but there are significant difference between the iPhone Simulator and real devices. First, many of the APIs such as Location, Apple Store, Accelerator, Email are not supported on the Simulator, but most importantly the Simulator runs on blazing fast MacPro machine, whereas real devices are slow and limited. I highly recommend start your testing with real devices early in your app lifecycle. There is also a big difference in hardware between different generation of iPod touch, iPhone and iPad. For example, iPhone 3G has 620Mhz processor with 128M memory, iPhone 3GS has 833Mhz with 256M memory and latest iPhone 4 has 1Ghz with 512M memory. I suggest using older devices to really find performance and stability issues with the application. Also, you want it to run on most devices so you should target lowest device that you can, which you can configure it by setting target OS property of the build. Though, Apple provides basic Unit Test support, but I found it very painful to use. We have used the UI Automation tool for functional testing with some success, but it lacks good documentation. In this blog, I listed several techniques for designing and developing iOS applications, though most of the techniques can be easily applied to other mobile platforms. Though, hardware on mobile devices is becoming more powerful, but it is still constrained with limited memory. You have to pay an exceptional attention to the memory usage, performance and stability of the app and users are going to hate if it's slow or crashes. The network apps have to further address the availability and speed of the network so that apps is not completely unusable in airport mode and with slow connection. In the end, testing on real devices and real environment is the key to create effective network based applications.
@interface OrderCallbackDelegate : NSObject
}
@end
Referencing Delegate
@interface SomeObject : NSObject {
id
Encapsulation and Extensions
@interface MyClass : NSObject {
@private
int myVar;
}
@end
@interface UIColor(MyBlueColor)
+(UIColor *)myBlueColor;
@end
@implementation UIColor(MyBlueColor)
+(UIColor *)myBlueColor {
return [UIColor colorWithRed:0.2274 green:0.4666 blue:0.8235 alpha:1.000];
}
@end
In addition to extension, you can also use categories for encapsulation by using categories syntax in the implementation file and adding private method and properites, e.g.
@interface MyClass(private)
//my private methods only visible in the implementation file
@end
Code Structure
Dynamic Language Support
typedef id (^objectFactoryBlk_t)();
@interface JsonParser : NSObject {
}
+ (void)bindJson:(NSData *)data toObject:(NSObject *) object;
+ (void)bindJson:(NSData *)data toArray:(NSMutableArray *) array mandatoryFields:(NSArray *)fields withFactory:(objectFactoryBlk_t) objectFactoryBlk;
@end
@interface XmlParser : NSObject {
xmlDocPtr doc;
}
- (id)initWithXML:(NSData *) document;
+ (void)bindXml:(NSData *)xml toObject:(NSObject *) object;
+ (void)bindXml:(NSData *)xml toArray:(NSMutableArray *) array mandatoryFields:(NSArray *)fields withFactory:(objectFactoryBlk_t) objectFactoryBlk;
@end
Also, as Objective-C is dynamic languages, the compiler won't complain if you call methods that are not defined, so you need to pay special attention to warnings when using dynamic language constructs or better turn warnings into errors by configuring your build. You can also use CLANG static analyzer to find improper dynamic method.Network Services
Loading Status
XML vs JSON vs Binary
Network Latency
Network Outages
Network Data Requests
typedef void (^requestCompletedBlk_t)(id payload);
typedef void (^requestFailedBlk_t)(NSError *error);
@interface ServiceBlocks : NSObject {
requestCompletedBlk_t requestCompletedBlk;
requestFailedBlk_t requestFailedBlk;
ASIHTTPRequest *request;
NSTimeInterval started;
int retries;
}
@property (copy, nonatomic) requestCompletedBlk_t requestCompletedBlk;
@property (copy, nonatomic) requestFailedBlk_t requestFailedBlk;
@property (retain, nonatomic) ASIHTTPRequest *request;
@property (assign, nonatomic) NSTimeInterval started;
@property (assign, nonatomic) int retries;
@end
@implementation ServiceBlocks
@synthesize requestCompletedBlk, requestFailedBlk, request, started, retries;
-(id)initWithRequest:(ASIHTTPRequest *) arequest completedBlock:(requestCompletedBlk_t) arequestCompletedBlk failedBlock:(requestFailedBlk_t) arequestFailedBlk {
if(self = [super init]) {
self.requestCompletedBlk = arequestCompletedBlk;
self.requestFailedBlk = arequestFailedBlk;
self.request = arequest;
self.started = [[NSDate date] timeIntervalSince1970];
self.retries = 0;
}
return self;
}
- (void)dealloc {
[requestCompletedBlk release];
[requestFailedBlk release];
[request release];
[super dealloc];
}
@end
- (void)postJson:(NSData *) json completedBlock:(requestCompletedBlk_t)requestCompletedBlk failedBlock:(requestFailedBlk_t) requestFailedBlk {
NSURL *url = [NSURL URLWithString:[[Configuration sharedConfiguration] getJsonServerUrl]];
NSString *jsonBuffer = [[[NSString alloc] initWithData:json encoding:NSASCIIStringEncoding] autorelease];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
request.connectionCanBeReused = YES;
[request setUseKeychainPersistence:YES];
[request setTimeOutSeconds:60];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setShouldPresentAuthenticationDialog:YES];
[request setDidFinishSelector:@selector(downloadCompleted:)];
[request setDidFailSelector:@selector(downloadFailed:)];
[request appendPostData:json];
[request addRequestHeader:@"Content-Type" value:@"application/json"];
[request setRequestMethod:@"POST"];
[request buildPostBody];
ServiceBlocks *blocks = [[ServiceBlocks alloc] initWithRequest:request completedBlock:requestCompletedBlk failedBlock:requestFailedBlk];
[requests setValue:blocks forKey:[self toKey:request]];
[blocks release];
[request startAsynchronous];
}
- (void)get:(NSString *) uri completedBlock:(requestCompletedBlk_t) requestCompletedBlk failedBlock:(requestFailedBlk_t) requestFailedBlk {
NSURL *url = [NSURL URLWithString:uri];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setUseKeychainPersistence:YES];
[request setDelegate:self];
[request setShouldPresentAuthenticationDialog:YES];
[request setDidFinishSelector:@selector(downloadCompleted:)];
[request setDidFailSelector:@selector(downloadFailed:)];
ServiceBlocks *blocks = [[ServiceBlocks alloc] initWithRequest:request completedBlock:requestCompletedBlk failedBlock:requestFailedBlk];
[requests setValue:blocks forKey:[self toKey:request]];
[blocks release];
[request startAsynchronous];
}
- (void)downloadFailed:(ASIHTTPRequest *)theRequest {
ServiceBlocks *blk = [requests valueForKey:[self toKey:theRequest]];
if (blk != nil) {
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] - blk.started;
NSLog(@"#####downloadFailed for %@ in %f seconds", [self toKey:theRequest], interval);
if (blk.requestFailedBlk != nil) {
blk.requestFailedBlk([theRequest error]);
}
[requests removeObjectForKey:[self toKey:theRequest]];
} else {
NSLog(@"#####downloadFailed could not find blocks for %@, available %@", [self toKey:theRequest], [requests allKeys]);
}
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
- (void)downloadCompleted:(ASIHTTPRequest *)theRequest {
ServiceBlocks *blk = [requests valueForKey:[self toKey:theRequest]];
if (blk != nil) {
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] - blk.started;
NSLog(@"################################downloadCompleted for %@ in %f seconds", [self toKey:theRequest], interval);
NSData *content = [theRequest responseData];
if (blk.requestCompletedBlk != nil) {
blk.requestCompletedBlk(content);
}
[requests removeObjectForKey:[self toKey:theRequest]];
} else {
NSLog(@"################################downloadCompleted could not find blocks for %@, available %@", [self toKey:theRequest], [requests allKeys]);
}
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
Gateway and Proxy Patterns
Check-If-Modified
Network Errors
Caching
#define K_CACHE_TIMEOUT_SECS 3600
@interface FileCache : NSObject {
}
+(BOOL)saveObject:(NSString *)name withData:(id)entry;
+(id)loadObject:(NSString *)name expired:(BOOL*)expired;
@end
#import "FileCache.h"
@implementation FileCache
+(id)loadObject:(NSString *)name expired:(BOOL*)expired {
*expired = YES;
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [docs stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.obj", name]];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attrs = [fileManager attributesOfItemAtPath:filePath error:&error];
id obj = nil;
if (attrs != nil && [attrs count] > 0) {
NSDate *date = [attrs valueForKey:@"NSFileModificationDate"];
if (date != nil) {
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
if (interval <= K_CACHE_TIMEOUT_SECS) {
*expired = NO;
}
obj = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
}
}
return obj;
}
+(BOOL)saveObject:(NSString *)name withData:(id)entry {
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [docs stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.obj", name]];
return [NSKeyedArchiver archiveRootObject:entry
toFile:filePath];
}
@end
Using Timers for Repeatable Tasks
dispatch_time_t now = dispatch_walltime(DISPATCH_TIME_NOW, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
int kPOLL_INTERVAL_SECS = 5;
dispatch_source_set_timer(timer, now, [kPOLL_INTERVAL_SECS * NSEC_PER_SEC, 5000ull);
dispatch_source_set_event_handler(timer, ^{
// fetch data and update
});
Multi-tasking
Active
InActive
Background
Suspended
Callback Methods
- (void)applicationWillResignActive:(UIApplication *)application {
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone
call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to it
s current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
Multi-Threading
NSThread
NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(myThreadMainMethod:)
object:nil];
[myThread start]; // Actually create the thread
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
- (void)myThreadMainRoutine
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
// Do thread work here.
[pool release]; // Release the objects in the pool.
}
NSOperationQueue
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saveData:) object:data];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
@interface SaveOperation : NSOperation {
NSData *data;
}
@property(retain) NSData *data;
- (id)initWithData:(NSData*)data;
@end
@implementation SaveOperation
@synthesize data;
- (id)initWithData:(NSData*)theData;
{
if ([super init]) {
self.data = theData;
}
return self;
}
- (void)dealloc {
[data release], data = nil;
[super dealloc];
}
- (void)main {
// saving
}
@end
SaveOperation *op = [[SaveOperation alloc] initWithDataL:data];
[queue addOperation:op];
[op release];
[queue setMaxConcurrentOperationCount:2];
Grand Central Dispatch
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// save data
NSLog(@"finished saving data");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// save data
NSLog(@"finished saving data");
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData]
});
});
dispatch_queue_t myQueue = dispatch_queue_create("com.plexobject.myqueue", NULL);
dispatch_async(myQueue, ^{
// save data
// ...
// now release the queue (optionally)
dispatch_release(myQueue);
});
Final word on Multi-threading
Event Notifications
@interface MyEvent : NSObject {
NSString *type;
}
@property(nonatomic, copy) NSString *type;
-(void)fireEvent;
@end
#import
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMyevent:) name:@"MyEvent" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"MyEvent" object:nil];
- (void) handleMyevent:(NSNotification *)notif {
MyEvent *event = (MyEvent *) [notif object];
}
MyEvent *event = new MyEvent();
event.type = @"MyEvent"
event.fireEvent;
sending bug reports Automatically
- (void)validateAndSaveCriticalApplicationData:(NSException *)exception {
NSMutableString *message = [[[NSMutableString alloc] initWithCapacity:200] autorelease];
[message appendFormat:@"{name:'%@', ", exception.name];
[message appendFormat:@"reason:'%@', ", exception.reason];
[message appendFormat:@"userInfo:'%@', callStack:[", exception.userInfo];
int count=0;
for (NSObject *obj in exception.callStackReturnAddresses) {
if (count > 0) {
[message appendString:@","];
}
[message appendFormat:@"callStack_%d:'%@'", obj];
}
[message appendString:@"], callSymbols:["];
count=0;
for (NSObject *obj in exception.callStackSymbols) {
if (count > 0) {
[message appendString:@","];
}
[message appendFormat:@"callSymbol_%d:'%@'", obj];
}
[message appendString:@"]}"];
[self sendBug:@"Crash report" withMessage:message];
}
Persistence
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *storeUrl = [NSURL fileURLWithPath: [docs stringByAppendingPathComponent: @"MyData"]];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserverForName:NSManagedObjectContextDidSaveNotification object:insertManagedObjectContext
queue:nil usingBlock:^(NSNotification *saveNotification) {
[[self readManagedObjectContext] mergeChangesFromContextDidSaveNotification:saveNotification];
}];
// update here
[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:ctx];
The NSManagedObject with ID:#### has been invalidated.
Performance
Memory Management
NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];
// ...
[pool drain];
Testing
Conclusion
An implementation of Virtual Node Router with Consistent Hash algorithm
September 22nd, 2010
Since the Dynamo paper, published a few years ago, DHTs and consistent hash have become mainstream. Here is my implementation of a virtual node router that uses consistent hash algorithm for splitting requests to the virtual nodes:
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
public class VirtualNodeRouter {
interface HashCalculator {
long calculateHash(String key);
}
private static class VirtualNode {
final String nodeName;
final int replicaNumber;
VirtualNode(final String nodeName, final int replicaNumber) {
this.nodeName = nodeName.toLowerCase();
this.replicaNumber = replicaNumber;
}
boolean matches(String host) {
return nodeName.equalsIgnoreCase(host);
}
@Override
public String toString() {
return nodeName + ":" + replicaNumber;
}
}
private final HashCalculator hashFunction;
private final SortedMap<Long, VirtualNode> virtualNodePoolByHash = new TreeMap<Long, VirtualNode>(
new Comparator<Long>() {
public int compare(Long i, Long j) {
if (i > j) {
return 1;
} else if (i < j) {
return -1;
} else {
return 0;
}
}
});
public VirtualNodeRouter() {
this(new HashCalculator() {
public long calculateHash(String key) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.update(key.getBytes());
byte[] digest = sha1.digest();
return bytesToLong(digest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
public VirtualNodeRouter(final HashCalculator f) {
this.hashFunction = f;
}
/**
* Adds a node with one replica
*
* @param node
* - node name
*/
public void add(String node) {
add(node, 1);
}
/**
* Adds a node to the available pool
*
* @param node
* - node name
* @param replicas
* - # of replicas - increase # of replicas based on the
* computing power of the machine
*/
public void add(String node, int replicas) {
// Note: You can call this method incrementally by adding more replicas,
// so that you don't cause DOS on
// your own services
int existingReplicas = getReplicas(node);
for (int i = 0; i < replicas; i++) {
VirtualNode virtualNode = new VirtualNode(node, i
+ existingReplicas);
virtualNodePoolByHash.put(hashFunction.calculateHash(virtualNode
.toString()), virtualNode);
}
}
/**
* remove the node from available pool
*
* @param node
*/
public void remove(String node) {
Iterator<Long> it = virtualNodePoolByHash.keySet().iterator();
while (it.hasNext()) {
Long key = it.next();
VirtualNode virtualNode = virtualNodePoolByHash.get(key);
if (virtualNode.matches(node)) {
it.remove();
}
}
}
public String getNode(String key) {
if (virtualNodePoolByHash.isEmpty()) {
return null;
}
long hash = hashFunction.calculateHash(key);
for (Map.Entry<Long, VirtualNode> e : virtualNodePoolByHash.entrySet()) {
if (hash < e.getKey()) {
return e.getValue().nodeName;
}
}
SortedMap<Long, VirtualNode> tailMap = virtualNodePoolByHash
.tailMap(hash);
hash = tailMap.isEmpty() ? virtualNodePoolByHash.firstKey() : tailMap
.firstKey();
return virtualNodePoolByHash.get(hash).nodeName;
}
public void dump() {
for (Map.Entry<Long, VirtualNode> e : virtualNodePoolByHash.entrySet()) {
System.out.println(" " + e.getKey() + " => " + e.getValue());
}
}
public int getReplicas(String nodeName) {
int replicas = 0;
for (VirtualNode node : virtualNodePoolByHash.values()) {
if (node.matches(nodeName)) {
replicas++;
}
}
return replicas;
}
private static long bytesToLong(byte[] b) {
ByteBuffer bb = ByteBuffer.wrap(b);
return bb.getLong();
}
}
The virtual nodes are added by specifying the node name and the number of replicas. You can add more virtual nodes for the more powerful machines than the low-end machines. You can call remove method when the node goes down or is unavailable. The getNode is called when a request needs to be routed. For example, if you are caching results of a service, you can use the key of cache to find the virtual node and then store the cache value at that node. You can read following resources to learn more about the consistent hash algorithm:
My impression of Diaspora codebase
September 16th, 2010
I briefly reviewed the Diaspora source code, which is all written in Ruby on Rails 3.0. Here are my initial thoughts on the codebase:
What I liked:
- It’s all open source — YES!
- Diaspora uses latest Rails 3.0 APIs such as global respond_to in controllers, thin controllers, and fat models with new query APIs. The code uses RSpec for tests and Factory-Girl for creating test objects instead of fixtures, which are much easily managed. There are a few Selenium tests, but most of the tests are in RSpec.
- Diaspora is built using latest technologies and standards such as PKI (OpenSSL), HTML5, VCard, Websockets, Microformat, XRDS, PubSubHubbub along with popular libraries such as EventMachine, HAML, jQuery, Fancybox, Sprinkle, Bundler, Blueprint, etc.
- Deployment scripts and documentation – Though, there isn’t any documentation on overall architecture or comments in the code, but I found installation documentation very helpful. Also, the deployment rake tasks and configurations are all included in the source code.
What I disliked:
Though, I found the code to be fairly easy to read and consistent in style I found following problems related to the performance, scalability and modularity.
- Service API – Diaspora uses Rails controller for serving HTML as well as JSON, XML and XRDS requests but I would have preferred separate services for the API with much more defined contract.
- Pagination – I found sporadic use of pagination in the code but a number of classes use Rails’ builtin relationships without any pagination. It’s like when you ask for banana you get the gorilla holding the banana. In my experience, this has been problem with all O/R mapping tools, which give you nice syntax for fetching related objects until your server runs out of memory or your database dies on you.
- Before/after filters – I found a number of such filters in the code, which I have found to be another common issue with the scalability when before/after filters require a lot of overhead.
- Asynchronous Messaging – I didn’t see any use of asynchronous messaging as a lot of requests such as adding/removing a friend can be done asynchronous.
- Modularity – Diaspora code uses modules and models for the business logic, but I found a couple of models such as user to be too big, which can be divided into suitable modules.
- MongoDB – I have nothing against MongoDB but I found a lot of code depends on MongoDB. I would have preferred using data access service instead, which completely encapsulates underlying storage technology and allows you to replace it without modifying all the code.
Conclusion
Despite the Knuth’s advice: “Forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil,” you need the architecture for scalability and performance when you are building the platform to replace Facebook. As social networking tools depend much more on Network effects or Metcalfe’s law instead of best technology, I hope early release of the software allows it to capture more users who use it. I was somewhat disappointed that identi.ca has not caught much attraction as open source alternative to Twitter. And I hope that Diaspora succeeds in becoming a good alternative to Facebook.
Implementing a Single Sign-on solution for Wordpress (PHP) and Rails applications using Central Authentication Service (CAS)
August 12th, 2010
I am starting a new Rails project that would be using Wordpress for CMS functionality and I needed a way to share the user sessions between Wordpress and Rails. Though, I found a couple of Wordpress plugins, but I decided to use Central Authentication Service (CAS). I found two server implementations, where the first one was in Java and second one was in Ruby. I had some experience with the Java based CAS server back in 2005, so I decided to use it. Here are the key installation and configuration steps for setting up the Single-Sign-On solution:
Create Rails Project
I had already installed Rails 2.3, so I created a simple project using:
rails ssoapp
Install Wordpress
I downloaded latest 3.0 version of Wordpress and unzipped it under ssoapp/public directory.
Configuring PHP for Apache
I decided to use Apache for hosting Wordpress and Rails applications. As I was using Mac, it already had Apache installed on my machine. All, I had to do was to uncomment following line from /etc/apache2/httpd.conf file:
LoadModule php5_module libexec/apache2/libphp5.so
I then restarted Apache using
sudo /usr/sbin/apachectl restart
I created a database for wordpress and defined a username/password as follows:
create database wp CREATE USER 'wp'@'localhost' IDENTIFIED BY 'wp'; GRANT ALL ON *.* TO 'wp'@'localhost';
I specified database settings in wp-config.php as follows:
define('DB_NAME', 'wp');
define('DB_USER', 'wp');
define('DB_PASSWORD', 'wp');
define('DB_HOST', 'localhost');
and created an admin account by pointing my browser to http://localhost/wp/wp-admin/install.php.
Installing Phusion Passenger on Apache
I installed passenger gem as follows:
gem install passenger
And installed passenger module for Apache using:
passenger-install-apache2-module
I then added following lines to /etc/apache2/httpd.conf file:
LoadModule passenger_module /Users/sbhatti/.rvm/gems/ruby-1.9.1-p378/gems/passenger-2.2.15/ext/apache2/mod_passenger.so PassengerRoot /Users/sbhatti/.rvm/gems/ruby-1.9.1-p378/gems/passenger-2.2.15 PassengerRuby /Users/sbhatti/.rvm/rubies/ruby-1.9.1-p378/bin/ruby
I created a file called ssoapp.conf under /etc/apache2/users with following contents:
<VirtualHost localhost:80>
ServerName localhost
DocumentRoot /Users/sbhatti/src/yoga-rails/public
RailsEnv development
<Directory /Users/sbhatti/src/yoga-rails/public>
Allow from all
Options Indexes -MultiViews
</Directory>
<Location /wp>
PassengerEnabled off
AllowOverride all
</Location>
<Location /cas>
PassengerEnabled off
AllowOverride all
</Location>
</VirtualHost>
Configuring SSL for Apache
I needed SSL for some functionality so I created self-signed keys and certificates a follows:
openssl genrsa -des3 -out server.key 1024 openssl req -new -key server.key -out server.csr
and copied those files to /etc/apache2 directory. I then modified /etc/apache2/httpd.conf by uncommenting following line:
LoadModule ssl_module libexec/apache2/mod_ssl.so
and adding
Listen 443
I then modified /etc/apache2/users/ssoapp.conf and added following contents:
<IfModule mod_ssl.c>
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
SSLProtocol all -SSLv2
SSLPassPhraseDialog builtin
SSLSessionCache dbm:/var/run/ssl_scache
SSLSessionCacheTimeout 300
SSLMutex file:/var/run/ssl_mutex
SSLRandomSeed startup builtin
<VirtualHost localhost:443>
ServerName localhost
DocumentRoot /Users/sbhatti/src/yoga-rails/public
ServerAdmin sbhatti@peak6.com
ErrorLog /tmp/error_log
TransferLog /tmp/access_log
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile /etc/apache2/server.crt
SSLCertificateKeyFile /etc/apache2/server.key
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars
</Files>
<Directory /Users/sbhatti/src/yoga-rails/public>
Allow from all
Options Indexes -MultiViews
SSLOptions +StdEnvVars
</Directory>
<Location /wp>
PassengerEnabled off
AllowOverride all
</Location>
<Location /cas>
PassengerEnabled off
AllowOverride all
</Location>
SetEnvIf User-Agent ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
CustomLog /var/log/httpd/ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>
</IfModule>
Basically, above configuration allows all HTTP and HTTPS requests be sent to my SSO rails app except /wp requests, which are sent to the Wordpress.
Password Encoding
I decided to use Wordpress users table (wp_users) as primary users table. Wordpress by default uses PHPPass library for encrypting passwords. The latest Wordpress uses combination of salt, MD5 and iterations for encrypting, but I wanted to use simple MD5 for now, so I edited ./wp-includes/pluggable.php and replaced default hash encoding to MD5 as follows:
function wp_hash_password($password) {
return md5($password, FALSE);
}
I then reset the password in Mysql database as follows:
update wp_users set user_pass = md5('hello') where user_login = 'admin';
Setting CAS Server
I downloaded and unzipped CAS Server. I needed to configure CAS server to use the wp_users table for authenticating users, so I added following dependencies to the cas-server-webapp/pom.xml:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cas-server-support-jdbc
</artifactId>
<version>${project.version}</version>
</dependency>
I then added following beans to the cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml:
<bean id="md5PasswordEncoder"
class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg index="0" value="MD5" />
</bean>
<bean id="wpDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/wp
</value>
</property>
<property name="username">
<value>wp</value>
</property>
<property name="password">
<value>wp</value>
</property>
</bean>
and removed default authentication class org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler and added following bean instead:
<property name="authenticationHandlers">
<list>
...
<bean
class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">
<property name="tableUsers">
<value>wp_users</value>
</property>
<property name="fieldUser">
<value>user_login</value>
</property>
<property name="fieldPassword">
<value>user_pass</value>
</property>
<property name="dataSource" ref="wpDataSource" />
<property name="passwordEncoder" ref="md5PasswordEncoder" />
</bean>
</list>
</property>
Above bean configuration specifies MD5 for hashing passwords.
Creating WAR file for CAS-Server
I then created the CAS war file by typing:
mvn clean install
Install and Configure Tomcat
The CAS-server requires Java application server so I downloaded and installed latest Apache Tomcat. Next, I needed to setup SSL for secure login so I created another set of certificate and private key using keytool as follows:
keytool -genkey -alias ssoapp -keypass ssoapp -keystore ssoapp.ks -storepass ssoapp
I copied ssoapp.ks to the conf directory under Apache tomcat and modified conf/server.xml and added following contents:
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true" clientAuth="false"
sslProtocol="TLS" keystoreFile="${catalina.home}/conf/ssoapp.bin"
keystorePass="ssoapp" />
I then started the Apache Tomcat using:
bin/startup.sh
Configuring CAS-client for Wordpress
First, I downloaded phpCAS client library and unzipped it under public directory of my Rails app. I used wpCas plugin for Wordpress, which uses phpCAS library for connecting to the CAS server. I installed the plugin under wp-contents/plugins directory and logged in to Wordpress as admin. I clicked the plugins link from left navigation and then activated the plugin. After activating it, I clicked the settings link from the left navigation and configured the CAS as follows:

Now, the moment of truth. I opened another instance of browser and pointed to http://localhost/wp, which showed my blog.

I then clicked on “Log in” link, which showed me certificate warning due to self-signed certificate. After accepting it, it took me to the CAS login page, e.g.

Voila, after typing my username and password, it took me back to the Wordpress.
Setting up CAS-Client for Rails
I used rubycas-client plugin for Rails and installed it as follows:
./script/plugin install git://github.com/gunark/rubycas-client.git
I then modified config/environment.rb file and added:
require 'casclient'
require 'casclient/frameworks/rails/filter'
CASClient::Frameworks::Rails::Filter.configure(
:cas_base_url => "https://localhost:8443/cas"
)
One gotcha in above configuration is that you have to place it at the end of configuration file as you get load error if you place it inside initializer block.
I then added before_filter CASClient::Frameworks::Rails::Filter to all controllers that needed authentication and added CASClient::Frameworks::Rails::Filter.logout(self) where I needed to logout. After changing my controller, I went back to the Rails app and sure enough it worked like a charm!
Summary
Though, there were many small pieces to get this working, but I am happy with this solution. This would help create seamless experience to the users, though the other half of the experience depends on unified user interface.
References
NoSql databases bring “Stored Procedures” back in fashion
August 10th, 2010
One of best tip I learned from my post-graduate research in Parallel & Distributed area was to bring the computation closer to the data. However, most applications in the real world are designed as three or more tiers that separate databases from the application server, where the business logic resides. Though, stored procedures have long been used in client server architecture, dataware services, reporting, and other forms to run the business logic closer to the database, but they are generally shunned due to the maintenance issues. I find it interesting that NoSQL databases are bringing back the stored procedures in the form of map/reduce queries. NoSQL databases come in various forms such as key-value stores, document stores, column stores, and graph stores. They are primarily influenced by the Brewer’s CAP Theorem and use BASE (basically available, soft state, eventually consistent) transactions as opposed to ACID (atomicity, consistency, isolation, durability) transactions. NoSQL databases are designed for horizontal scalability and are able to support large data by partitioning it. NoSQL offer rich queries based on map/reduce, which are generally written in javascript or other scripting languages. These queries provide powerful mechanism to define the business logic for filtering or aggregating results, which are then executed inside the database or closer to the data. Thus, NoSQL databases are able to provide much better performance as a side effect if the application logic is transferred to the host where the data resides. Everything old is new again and stored procedures are back in the fashion.
References:

































