import java.io.IOException; import java.util.Vector; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import net.rim.device.api.servicebook.ServiceBook; import net.rim.device.api.servicebook.ServiceRecord; import net.rim.device.api.system.CoverageInfo; import net.rim.device.api.system.RadioInfo; import net.rim.device.api.ui.UiApplication; /** * @author Marcus Watkins (marcus@versatilemonkey.com) * @version 1.0 (June 24, 2009) * http://www.versatilemonkey.com

* * This code is public domain, do with it whatever you wish. *

* This class aims to simplify HTTP requests on the BlackBerry platform. It could be extended * for other uses (socket, specifically), but that exercise is left up to the reader.

* Use of this class will require signing your application using RIM supplied signing keys. *

* The BlackBerry platform provides a multitude of different transports for network access. These include WiFi, BES, BIS, WAP2 and Direct TCP. *

* Not all transports are available on all devices, carriers or service plans. Ordinarily an application must determine on its own which * transports are available for a given device, and attempt to connect via them in order. * * Sample use:

*

 * 
 * HttpConnectionFactory factory = new HttpConnectionFactory( "http://www.versatilemonkey.com/test.txt", HttpConnectionFactory.TRANSPORT_WIFI | HttpConnectionFactory.TRANSPORT_BES );
 * while( true ) {
 *    try {
 *       HttpConnection connection = factory.getNextConnection();
 *       try {
 *          connection.setRequestMethod( "POST" );
 *          connection.setRequestProperty( "Content-type", "application/x-www-form-urlencoded" );
 *          OutputStream os = connection.openOutputStream( );
 *          os.write( "foo=bar&var2=val2".getBytes() );
 *          os.close();
 *          InputStream is = connection.openInputStream();
 *          //do something with the input stream
 *          if( whatever we did worked ) {
 *             break;
 *          }
 *       }
 *       catch( IOException ) {
 *          //Log the error or store it for displaying to the end user if no transports succeed
 *       }
 *    }
 *    catch( NoMoreTransportsException e ) {
 *       //There are no more transports to attempt
 *       Dialog.alert( "Unable to perform request" ); //Note you should never attempt network activity on the event thread
 *       break;
 *    }
 * }
 * 
 * 
*

* It is possible that an HttpConnection returned by getNextConnection will fail in a method undetectable before attemping the request. *

* Notes about the various transports from my experience: *

* Wifi: *

* Wifi is the least cost to the user and is also the fasted by orders of magnitude *

* BES/BIS: *

* - These are largely the same, except BES goes through the user's corporate network and may be subject to corporate firewalls

* - BES/BIS are generally offered as unlimited use to anyone with a BlackBerry specific data plan

* - There is usually a limit imposed on the size of files that can be retrieved (usually 5mb, but can be as low as 100kb)

*

* Direct TCP and WAP2:

* - These transports use carrier data plans which are sometimes billed at the same rate as BES/BIS, but sometimes are billed * separately. It is possible for users to be on unlimited data plans via BES/BIS but be charged per MB for TCP and WAP2. I * have never seen the reverse, however.

* - Some carriers do not have limits on the file size, others will timeout if you request a file over their limit (instead of a 413 error or similar)

* - Some carriers have intermediary proxies that will alter the content of returned files (wrapping them in carrier specific content, for example)

*

*

* Good luck, I hope this makes networking on BlackBerry easier for you. */ public class HttpConnectionFactory { /** * Specifies that only wifi should be used */ public static final int TRANSPORT_WIFI = 1; /** * Specifies that only BES (also known as MDS or corporate servers) */ public static final int TRANSPORT_BES = 2; /** * Specifies that only BIS should be used (Basically RIM hosted BES) */ public static final int TRANSPORT_BIS = 4; /** * Specifies that TCP should be used (carrier transport) */ public static final int TRANSPORT_DIRECT_TCP = 8; /** * Specifies that WAP2 should be used (carrier transport) */ public static final int TRANSPORT_WAP2 = 16; /** * Equivalent to: TRANSPORT_WIFI | TRANSPORT_BES | TRANSPORT_BIS | TRANSPORT_DIRECT_TCP | TRANSPORT_WAP2 */ public static final int TRANSPORTS_ANY = TRANSPORT_WIFI | TRANSPORT_BES | TRANSPORT_BIS | TRANSPORT_DIRECT_TCP | TRANSPORT_WAP2; /** * Equivalent to: TRANSPORT_WIFI | TRANSPORT_BES | TRANSPORT_BIS */ public static final int TRANSPORTS_AVOID_CARRIER = TRANSPORT_WIFI | TRANSPORT_BES | TRANSPORT_BIS; /** * Equivalent to: TRANSPORT_DIRECT_TCP | TRANSPORT_WAP2 */ public static final int TRANSPORTS_CARRIER_ONLY = TRANSPORT_DIRECT_TCP | TRANSPORT_WAP2; /** * The default order in which selected transports will be attempted * */ public static final int DEFAULT_TRANSPORT_ORDER[] = { TRANSPORT_WIFI, TRANSPORT_BES, TRANSPORT_BIS, TRANSPORT_DIRECT_TCP, TRANSPORT_WAP2 }; private static final int TRANSPORT_COUNT = DEFAULT_TRANSPORT_ORDER.length; private static ServiceRecord srMDS[], srBIS[], srWAP2[], srWiFi[]; private static boolean serviceRecordsLoaded = false; private int curIndex = 0; private int curSubIndex = 0; private String url; private String extraParameters; private int transports[]; private int lastTransport = 0; /** * Equivalent to HttpConnectionFactory( url, null, HttpConnectionFactory.DEFAULT_TRANSPORT_ORDER ) * @see #HttpConnectionFactory(String, String, int[]) * @param url See {@link #HttpConnectionFactory(String, String, int[])} */ public HttpConnectionFactory( String url ) { this( url, null, 0 ); } /** * Equivalent to HttpConnectionFactory( url, null, allowedTransports ) * * @see #HttpConnectionFactory(String, String, int) * @param url See {@link #HttpConnectionFactory(String, String, int)} * @param allowedTransports See {@link #HttpConnectionFactory(String, String, int)} */ public HttpConnectionFactory( String url, int allowedTransports ) { this( url, null, allowedTransports ); } /** * Equivalent to HttpConnectionFactory( url, null, transportPriority ) * * @see #HttpConnectionFactory(String, String, int[]) * @param url See {@link #HttpConnectionFactory(String, String, int[])} * @param transportPriority See {@link #HttpConnectionFactory(String, String, int[])} */ public HttpConnectionFactory( String url, int transportPriority[] ) { this( url, null, transportPriority ); } /** * Creates a factory specifying the target http/https url and mask containing which transports should be allowed (default order) * * This method converts allowedTransports into an array of transports to use arranging * the included transports in the order specified by {@link #DEFAULT_TRANSPORT_ORDER} * Once the translation is complete it is equivalent to calling: *

* * HttpConnectionFactory( String url, String extraParameters, int transportPriority[] ); * *

* But only the transports matching the input mask are included in the array. * * @param url See {@link #HttpConnectionFactory(String, String, int[])} * @param extraParameters See {@link #HttpConnectionFactory(String, String, int[])} * @param allowedTransports A set of transports that should be allowed, for example, to set only wifi and BES use: TRANSPORT_WIFI | TRANSPORT_BES */ public HttpConnectionFactory( String url, String extraParameters, int allowedTransports ) { this( url, extraParameters, transportMaskToArray( allowedTransports ) ); } /** * Creates a factory specifying the target http/https url and ordered list of transports to attempt * * This method constructs an HttpConnectionFactory for the URL specified, using any extra connection parameters * specified in extraParemeters that will follow the order of transports specified in transportPriority *

* Transports not in transportPriority are not used in any order. The order of transports attempted will follow the order the * are presented in transportPriority * * extraParameters are additional parameters you want added to the connection string, each must be preceded by a semicolon. These are some useful ones:

* ConnectionTimeout=120000 (2 minute connection timeout)

* EncryptRequired=true (No idea what this does)

* Example: ";ConnectionTimeout=120000;EncryptRequired=true" * See http://www.blackberryforums.com/developer-forum/113137-undocumented-connector-open-parameters.html for more info. * * @param url The url to generate the HttpConnection for (only http and https) * @param extraParameters Extra parameters that will get appended to the end of the final url used in {@link #javax.microedition.io.Connector.open( String ) } * @param transportPriority An array of transports in the order they should be attempted */ public HttpConnectionFactory( String url, String extraParameters, int transportPriority[] ) { if( !serviceRecordsLoaded ) { loadServiceBooks( false ); } if( url == null ) { throw new IllegalArgumentException( "Null URL passed in" ); } if( !url.toLowerCase().startsWith( "http" ) ) { throw new IllegalArgumentException( "URL not http or https" ); } this.url = url; this.extraParameters = extraParameters; transports = transportPriority; } /** * Generates an HttpConnection using the next available transport according to the order specified during factory creation to * the url specified in factory creation. See the class description for details on use. * * @return An HttpConnection using the next transport configured during factory creation * @throws NoMoreTransportsException No more transports are available to use */ public HttpConnection getNextConnection() throws NoMoreTransportsException { HttpConnection con = null; int curTransport = 0; while( con == null && curIndex < transports.length ) { curTransport = transports[curIndex]; switch( curTransport ) { case TRANSPORT_WIFI: curIndex++; curSubIndex = 0; try { con = getWifiConnection(); } catch( Exception e ) { } break; case TRANSPORT_BES: curIndex++; curSubIndex = 0; try { con = getBesConnection(); } catch( Exception e ) { } break; case TRANSPORT_BIS: while( con == null ) { try { con = getBisConnection( curSubIndex++ ); } catch( NoMoreTransportsException e ) { curIndex++; curSubIndex = 0; break; } catch( Exception e ) { } } break; case TRANSPORT_DIRECT_TCP: curIndex++; try { con = getTcpConnection(); } catch( Exception e ) { } break; case TRANSPORT_WAP2: while( con == null ) { try { con = getWap2Connection( curSubIndex++ ); } catch( NoMoreTransportsException e ) { curIndex++; curSubIndex = 0; break; } catch( Exception e ) { } } break; } } if( con == null ) { throw new NoMoreTransportsException( ); } lastTransport = curTransport; return con; } /** * Returns the transport used in the connection last returned via {@link #getNextConnection()} * @return the transport used in the connection last returned via {@link #getNextConnection()} or 0 if none */ public int getLastTransport() { return lastTransport; } /** * Generates a connection using the BIS transport if available * * @param index The index of the service book to use * @return An {@link HttpConnection} if this transport is available, otherwise null * @throws NoMoreTransportsException * @throws IOException throws exceptions generated by {@link getConnection( String transportExtras1, String transportExtras2 )} */ private HttpConnection getBisConnection( int index ) throws NoMoreTransportsException, IOException { if( index >= srBIS.length ) { throw new NoMoreTransportsException( "Out of BIS transports" ); } ServiceRecord sr = srBIS[index]; return getConnection( ";deviceside=false;connectionUID=", sr.getUid() ); } /** * Generates a connection using the BES transport if available * * @return An {@link HttpConnection} if this transport is available, otherwise null * @throws IOException throws exceptions generated by {@link getConnection( String transportExtras1, String transportExtras2 )} */ private HttpConnection getBesConnection( ) throws IOException { if( CoverageInfo.isCoverageSufficient(CoverageInfo.COVERAGE_MDS) ) { return getConnection( ";deviceside=false", null ); } return null; } /** * Generates a connection using the WIFI transport if available * * @return An {@link HttpConnection} if this transport is available, otherwise null * @throws IOException throws exceptions generated by {@link getConnection( String transportExtras1, String transportExtras2 )} */ private HttpConnection getWifiConnection() throws IOException { if( RadioInfo.areWAFsSupported( RadioInfo.WAF_WLAN ) && ( RadioInfo.getActiveWAFs() & RadioInfo.WAF_WLAN ) != 0 && CoverageInfo.isCoverageSufficient( 1 /* CoverageInfo.COVERAGE_DIRECT */, RadioInfo.WAF_WLAN, false) ) { return getConnection( ";deviceside=true;interface=wifi", null ); } return null; } /** * Generates a connection using the WAP2 transport if available * * @param index The index of the service book to use * @return An {@link HttpConnection} if this transport is available, otherwise null * @throws NoMoreTransportsException if index is outside the range of available service books * @throws IOException throws exceptions generated by {@link getConnection( String transportExtras1, String transportExtras2 )} */ private HttpConnection getWap2Connection( int index ) throws NoMoreTransportsException, IOException { if( index >= srWAP2.length ) { throw new NoMoreTransportsException( "Out of WAP2 transports" ); } if( CoverageInfo.isCoverageSufficient( 1 /* CoverageInfo.COVERAGE_DIRECT*/ ) ) { ServiceRecord sr = srWAP2[index]; return getConnection( ";deviceside=true;ConnectionUID=", sr.getUid() ); } return null; } /** * Generates a connection using the TCP transport if available * * @return An {@link HttpConnection} if this transport is available, otherwise null * @throws IOException throws exceptions generated by {@link getConnection( String transportExtras1, String transportExtras2 )} */ private HttpConnection getTcpConnection( ) throws IOException { if( CoverageInfo.isCoverageSufficient( 1 /* CoverageInfo.COVERAGE_DIRECT */ ) ) { return getConnection( ";deviceside=true", null ); } return null; } /** * Utility method for actually getting a connection using whatever transport arguments the transport may need * * @param transportExtras1 If not null will be concatenated onto the end of the {@link url} * @param transportExtras2 If not null will be concatenated onto the end of {@link url} after transportExtras1 * @return An {@link HttpConnection} built using the url and transport settings provided * @throws IOException any exceptions thrown by {@link Connector.open( String name )} */ private HttpConnection getConnection( String transportExtras1, String transportExtras2 ) throws IOException { StringBuffer fullUrl = new StringBuffer( ); fullUrl.append( url ); if( transportExtras1 != null ) { fullUrl.append( transportExtras1 ); } if( transportExtras2 != null ) { fullUrl.append( transportExtras2 ); } if( extraParameters != null ) { fullUrl.append( extraParameters ); } return (HttpConnection)Connector.open( fullUrl.toString() ); } /** * Public method used to reload service books for whatever reason (though I can't think of any) */ public static void reloadServiceBooks() { loadServiceBooks( true ); } /** * Loads all pertinent service books into local variables for later use. Called upon first instantiation of the class and upload {@link reloadServiceBooks()} * @param reload Whether to force a reload even if they've already been loaded. */ private static synchronized void loadServiceBooks( boolean reload ) { if( serviceRecordsLoaded && !reload ) { return; } ServiceBook sb = ServiceBook.getSB(); ServiceRecord[] records = sb.getRecords(); Vector mdsVec = new Vector(); Vector bisVec = new Vector(); Vector wap2Vec = new Vector(); Vector wifiVec = new Vector(); if( !serviceRecordsLoaded ) { for (int i = 0; i < records.length; i++) { ServiceRecord myRecord = records[i]; String cid, uid; if (myRecord.isValid() && !myRecord.isDisabled()) { cid = myRecord.getCid().toLowerCase(); uid = myRecord.getUid().toLowerCase(); // BIS if (cid.indexOf("ippp") != -1 && uid.indexOf("gpmds") != -1) { bisVec.addElement( myRecord ); } // WAP1.0: Not implemented. // BES if (cid.indexOf("ippp") != -1 && uid.indexOf("gpmds") == -1) { mdsVec.addElement( myRecord ); } // WiFi if (cid.indexOf("wptcp") != -1 && uid.indexOf("wifi") != -1) { wifiVec.addElement( myRecord ); } // Wap2 if (cid.indexOf("wptcp") != -1 && uid.indexOf("wap2") != -1) { wap2Vec.addElement( myRecord ); } } } srMDS = new ServiceRecord[mdsVec.size()]; mdsVec.copyInto( srMDS ); mdsVec.removeAllElements(); mdsVec = null; srBIS = new ServiceRecord[bisVec.size()]; bisVec.copyInto( srBIS ); bisVec.removeAllElements(); bisVec = null; srWAP2 = new ServiceRecord[wap2Vec.size()]; wap2Vec.copyInto( srWAP2 ); wap2Vec.removeAllElements(); wap2Vec = null; srWiFi = new ServiceRecord[wifiVec.size()]; wifiVec.copyInto( srWiFi ); wifiVec.removeAllElements(); wifiVec = null; serviceRecordsLoaded = true; } } /** * Utility methd for converting a mask of transports into an array of transports in default order * @param mask ORed collection of masks, example: TRANSPORT_WIFI | TRANSPORT_BES * @return an array of the transports specified in mask in default order, example: { TRANSPORT_WIFI, TRANSPORT_BES } */ private static int[] transportMaskToArray( int mask ) { if( mask == 0 ) { mask = TRANSPORTS_ANY; } int numTransports = 0; for( int i = 0; i < TRANSPORT_COUNT; i++ ) { if( ( DEFAULT_TRANSPORT_ORDER[i] & mask ) != 0 ) { numTransports++; } } int transports[] = new int[numTransports]; int index = 0; for( int i = 0; i < TRANSPORT_COUNT; i++ ) { if( ( DEFAULT_TRANSPORT_ORDER[i] & mask ) != 0 ) { transports[index++] = DEFAULT_TRANSPORT_ORDER[i]; } } return transports; } }