Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Java Development

Reply
Developer
Posts: 268
Registered: ‎08-19-2009
My Device: Bold 9700
Accepted Solution

Getting 302 redirect loop only when connected via BES

[ Edited ]

Hello,

 
I have a problem when I am connected via BES, I get a redirect loop from the server (it returns a 302 response with the originial URL in the "location" header field). When connected via BIS-B, WiFi or carrier, the server returns redirects as expected and the app works. However I require a BES connection and give it priority when available.

 

Note that I have verified the connection string returned from Networking.java is correct (that is we append ";deviceside=false" when BES is being using).

 

Here is the networking code I'm using:

 

package [redacted]

/*
 * Networking.java
 *
 * This code is based on the connection code developed by Mike Nelson of AccelGolf.
 * http://blog.accelgolf.com/2009/05/22/blackberry-cross-carrier-and-cross-network-http-connection
 *  
 */

import net.rim.device.api.system.CoverageInfo;
import net.rim.device.api.system.DeviceInfo;
import net.rim.device.api.system.WLANInfo;

public class Networking
{
	Networking()
	{
	}

// Whether or not to the simulator should use MDS to connect.
// By default this should be false, however if you are testing
// in an environment where MDS will be the expected connection method,
// set this to true to have the simulator attempt to use MDS.  This variable
// has no effect on what happens on a real device. 
	private static final boolean isMDS = false;

	// the timeout 
	public static final int TIMEOUT = 30000;

	/**
	 * Determines what connection type to use and returns the necessary string
	 * to use it.
	 * 
	 * @return A string with the connection info
	 */
	public static String getConnectionString()
	{

		String connectionString = null;

		// Simulator behavior is controlled by the USE_MDS_IN_SIMULATOR variable.
		if (DeviceInfo.isSimulator())
		{
			if (isMDS)
			{
				connectionString = ";ConnectionTimeout=" + TIMEOUT + ";deviceside=false";
			}
			else
			{
				connectionString = ";ConnectionTimeout=" + TIMEOUT + ";deviceside=true";
			}
		}

		// Check for an MDS connection instead (BlackBerry Enterprise Server)
		else if ((CoverageInfo.getCoverageStatus() & CoverageInfo.COVERAGE_MDS) == CoverageInfo.COVERAGE_MDS)
		{
			System.out.println("MDS coverage found");
			connectionString = ";ConnectionTimeout=" + TIMEOUT + ";deviceside=false";
		}

		else if ((CoverageInfo.getCoverageStatus() & CoverageInfo.COVERAGE_BIS_B) == CoverageInfo.COVERAGE_BIS_B)
		{
			// otherwise, use the Uid to construct a valid carrier BIS-B request
			System.out.println("Using BIS");
			connectionString = ";ConnectionTimeout=" + TIMEOUT + ";deviceside=false;ConnectionType=[redacted]";
		}

		// Wifi is the preferred transmission method
		else if (WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED)
		{
			System.out.println("Using WIFI");
			connectionString = ";interface=wifi";
		}

		// Is the carrier network the only way to connect?
		else if ((CoverageInfo.getCoverageStatus() & CoverageInfo.COVERAGE_DIRECT) == CoverageInfo.COVERAGE_DIRECT)
		{

			// Has carrier coverage, but not BIBS.  So use the carrier's TCP network
			System.out.println("Device is connected Direct");
			connectionString = ";ConnectionTimeout=" + TIMEOUT + ";deviceside=true";

		}

		// If there is no connection available abort to avoid bugging the user unnecssarily.
		else if (CoverageInfo.getCoverageStatus() == CoverageInfo.COVERAGE_NONE)
		{
			System.out.println("There is no available connection.");
		}

		// In theory, all bases are covered so this shouldn't be reachable.
		else
		{
			System.out.println("no other options found, assuming device.");
			connectionString = ";ConnectionTimeout=" + TIMEOUT + ";deviceside=true";
		}

		return connectionString;
	}
}

 

Here is the sendRequest method that handled the connection and redirect:

 

public HttpResponse sendRequest(HttpRequest request)
	{
		String url = request.getUrl();
		if (url == null)
		{
			HttpResponse result = new HttpResponse();
			result.responseCode = 404;
			result.responseMessage = "Not Found";
			return result;
		}

		ConnectionWrapper cw = connectionWrapperForRequest(request);
		if (cw == null)
		{
			return null;
		}

		boolean compressionEnabled = !request.disableCompression() && COMPRESSION_ENABLED;

		HttpResponse result = new HttpResponse();
		try
		{
			String finalURL = url.trim() + Networking.getConnectionString();
			cw.connection = (HttpConnection) Connector.open(finalURL, Connector.READ_WRITE, false);
			System.out.println("Connection string: " + Networking.getConnectionString());
			System.out.println("Full connec.  URL: " + finalURL);

			if (cw.connection == null)
			{
				result.errorMessage = "Could not open a network connection.";
				result.completedWithError = true;
				return result;
			}

			cw.connection.setRequestMethod(request.getHttpMethod());

			if (compressionEnabled)
			{
				cw.connection.setRequestProperty("Accept-Encoding", "gzip");
			}

			cw.connection.setRequestProperty("User-Agent",
					"" + DeviceInfo.getManufacturerName() + "/" + DeviceInfo.getDeviceName() + "/" + Config.getVersionNumber());
			if (request.username() != null)
			{
				cw.connection.setRequestProperty("Authorization", "Basic " + Utils.base64Encode(request.username() + ":" + request.password()));
			}
			
			// set this header so BES servers will not change the content of the headers
			cw.connection.setRequestProperty("x-rim-transcode-content", "none");

			//add cookies
			if (HttpCookieJar.getInstance().cookieCount() > 0)
			{
				cw.connection.setRequestProperty("Cookie", HttpCookieJar.getInstance().getCookiesAsString());
			}

			//pull request headers from HttpRequest
			
			Hashtable headers = request.getHeaders();
			for (Enumeration e = headers.keys(); e.hasMoreElements();)
			{
				String key = (String) e.nextElement();
				cw.connection.setRequestProperty(key, (String) headers.get(key));
			}

			byte[] upstreamBytes = request.requestBytes();
			if (upstreamBytes != null && upstreamBytes.length > 0)
			{
				Logger.getLogger().log(new String(upstreamBytes));
				cw.connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_TYPE,
						HttpProtocolConstants.CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED);
				cw.connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(upstreamBytes.length));
				cw.outputStream = cw.connection.openOutputStream();
				cw.outputStream.write(upstreamBytes);
			}

			Logger.getLogger().log("Get response");

			result.responseCode = cw.connection.getResponseCode();
			result.responseMessage = cw.connection.getResponseMessage();

			Logger.getLogger().log("Status Code: " + result.responseCode);
			Logger.getLogger().log("Status Message: " + result.responseMessage);

			//suck out the cookies here
			int fieldNo = 0;
			String headerField;
			while ((headerField = cw.connection.getHeaderField(fieldNo)) != null)
			{
				if (cw.connection.getHeaderFieldKey(fieldNo).equals("Set-Cookie"))
				{
					HttpCookieJar.getInstance().setCookie(headerField);
				}
				fieldNo++;
			}

			System.out.println("get redirect");

			//get redirect location
			String location;
			if ((location = cw.connection.getHeaderField("Location")) != null)
			{
				if (location == url.trim())
				{
					Logger.getLogger().log("Redirect loop");
				}
				Logger.getLogger().log("Redirect: " + location);
				result.redirectLocation = location.trim();
			}
			else
				result.redirectLocation = null;

			byte[] buffer = new byte[HTTP_BUFFER_SIZE];
			int count;

			System.out.println("compression");

			cw.inputStream = cw.connection.openInputStream();
			if (compressionEnabled)
			{
				String encoding = cw.connection.getEncoding();
				if ("gzip".equalsIgnoreCase(encoding))
				{
					cw.inputStream = new GZIPInputStream(cw.inputStream);
				}
			}

			cw.inputStream = new DataInputStream(cw.inputStream);

			System.out.println("output stream");

			ByteArrayOutputStream out = new ByteArrayOutputStream();
			try
			{
				while ((count = cw.inputStream.read(buffer)) >= 0)
				{
					out.write(buffer, 0, count);
				}
				result.bytes = out.toByteArray();
			}
			finally
			{
				out.close();
			}
			cw.close();
			Logger.getLogger().log("Response complete");
		}
		catch (IOException e)
		{
			result.errorMessage = e.getMessage();
			result.completedWithError = true;
			Logger.getLogger().log("ERROR!:" + e.getMessage());
		}
		finally
		{
			removeConnectionWrapper(cw);
		}
		return result;
	}

 

 

Here is the entire HttpService.java for context of the SendRequest method above:

 

//#preprocess

//package
//imports

public class HttpService
{
	private static HttpService _instance;

	private static final boolean COMPRESSION_ENABLED = false;
	private static final int HTTP_BUFFER_SIZE = 1024;

	public static synchronized HttpService instance()
	{
		if (_instance == null)
		{
			_instance = new HttpService();
		}
		return _instance;
	}

	private WorkQueue _requestQueue = new WorkQueue(1024, 4);
	private Hashtable _connections = new Hashtable(10);

	private HttpService()
	{
		// singleton
	}

	private ConnectionWrapper connectionWrapperForRequest(HttpRequest request)
	{
		ConnectionWrapper cw = null;
		synchronized (request)
		{
			if (!request.cancelled())
			{
				cw = new ConnectionWrapper(request);
				synchronized (_connections)
				{
					_connections.put(request, cw);
				}
			}
		}
		return cw;
	}

	private void removeConnectionWrapper(ConnectionWrapper cw)
	{
		synchronized (_connections)
		{
			_connections.remove(cw.request);
		}
		cw.close();
	}

	public void cancelRequest(HttpRequest request)
	{
		ConnectionWrapper cw = null;
		synchronized (request)
		{
			synchronized (_connections)
			{
				cw = (ConnectionWrapper) _connections.remove(request);
			}
			request.setCancelled();
		}
		if (cw != null)
		{
			cw.close();
		}
	}

	public void executeRequest(final HttpRequest request)
	{
		_requestQueue.addWorkItem(new Runnable()
		{
			public void run()
			{
				HttpResponse response = sendRequest(request);
				if (!request.cancelled() && response != null)
				{
					request.completeRequest(response);
				}
			};
		});
	}

	public HttpResponse sendRequest(HttpRequest request)
	{
		String url = request.getUrl();
		if (url == null)
		{
			HttpResponse result = new HttpResponse();
			result.responseCode = 404;
			result.responseMessage = "Not Found";
			return result;
		}

		ConnectionWrapper cw = connectionWrapperForRequest(request);
		if (cw == null)
		{
			return null;
		}

		boolean compressionEnabled = !request.disableCompression() && COMPRESSION_ENABLED;

		HttpResponse result = new HttpResponse();
		try
		{
			String finalURL = url.trim() + Networking.getConnectionString();
			cw.connection = (HttpConnection) Connector.open(finalURL, Connector.READ_WRITE, false);
			System.out.println("Connection string: " + Networking.getConnectionString());
			System.out.println("Full connec.  URL: " + finalURL);

			if (cw.connection == null)
			{
				result.errorMessage = "Could not open a network connection.";
				result.completedWithError = true;
				return result;
			}

			cw.connection.setRequestMethod(request.getHttpMethod());

			if (compressionEnabled)
			{
				cw.connection.setRequestProperty("Accept-Encoding", "gzip");
			}

			cw.connection.setRequestProperty("User-Agent",
					"" + DeviceInfo.getManufacturerName() + "/" + DeviceInfo.getDeviceName() + "/" + Config.getVersionNumber());
			if (request.username() != null)
			{
				cw.connection.setRequestProperty("Authorization", "Basic " + Utils.base64Encode(request.username() + ":" + request.password()));
			}
			
			// set this header so BES servers will not change the content of the headers
			cw.connection.setRequestProperty("x-rim-transcode-content", "none");

			//add cookies
			if (HttpCookieJar.getInstance().cookieCount() > 0)
			{
				cw.connection.setRequestProperty("Cookie", HttpCookieJar.getInstance().getCookiesAsString());
			}

			//pull request headers from HttpRequest
			
			Hashtable headers = request.getHeaders();
			for (Enumeration e = headers.keys(); e.hasMoreElements();)
			{
				String key = (String) e.nextElement();
				cw.connection.setRequestProperty(key, (String) headers.get(key));
			}

			byte[] upstreamBytes = request.requestBytes();
			if (upstreamBytes != null && upstreamBytes.length > 0)
			{
				Logger.getLogger().log(new String(upstreamBytes));
				cw.connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_TYPE,
						HttpProtocolConstants.CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED);
				cw.connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(upstreamBytes.length));
				cw.outputStream = cw.connection.openOutputStream();
				cw.outputStream.write(upstreamBytes);
			}

			Logger.getLogger().log("Get response");

			result.responseCode = cw.connection.getResponseCode();
			result.responseMessage = cw.connection.getResponseMessage();

			Logger.getLogger().log("Status Code: " + result.responseCode);
			Logger.getLogger().log("Status Message: " + result.responseMessage);

			//suck out the cookies here
			int fieldNo = 0;
			String headerField;
			while ((headerField = cw.connection.getHeaderField(fieldNo)) != null)
			{
				if (cw.connection.getHeaderFieldKey(fieldNo).equals("Set-Cookie"))
				{
					HttpCookieJar.getInstance().setCookie(headerField);
				}
				fieldNo++;
			}

			System.out.println("get redirect");

			//get redirect location
			String location;
			if ((location = cw.connection.getHeaderField("Location")) != null)
			{
				if (location == url.trim())
				{
					Logger.getLogger().log("Redirect loop");
				}
				Logger.getLogger().log("Redirect: " + location);
				result.redirectLocation = location.trim();
			}

			byte[] buffer = new byte[HTTP_BUFFER_SIZE];
			int count;

			System.out.println("compression");

			cw.inputStream = cw.connection.openInputStream();
			if (compressionEnabled)
			{
				String encoding = cw.connection.getEncoding();
				if ("gzip".equalsIgnoreCase(encoding))
				{
					cw.inputStream = new GZIPInputStream(cw.inputStream);
				}
			}

			cw.inputStream = new DataInputStream(cw.inputStream);

			System.out.println("output stream");

			ByteArrayOutputStream out = new ByteArrayOutputStream();
			try
			{
				while ((count = cw.inputStream.read(buffer)) >= 0)
				{
					out.write(buffer, 0, count);
				}
				result.bytes = out.toByteArray();
			}
			finally
			{
				out.close();
			}
			cw.close();
			Logger.getLogger().log("Response complete");
		}
		catch (IOException e)
		{
			result.errorMessage = e.getMessage();
			result.completedWithError = true;
			Logger.getLogger().log("ERROR!:" + e.getMessage());
		}
		finally
		{
			removeConnectionWrapper(cw);
		}
		return result;
	}

	private static class ConnectionWrapper
	{
		final HttpRequest request;
		InputStream inputStream = null;
		OutputStream outputStream = null;
		HttpConnection connection = null;

		public ConnectionWrapper(HttpRequest request)
		{
			this.request = request;
		}

		public void close()
		{
			try
			{
				if (outputStream != null)
					outputStream.close();
			}
			catch (Exception e)
			{
			}
			try
			{
				if (inputStream != null)
					inputStream.close();
			}
			catch (Exception e)
			{
			}
			try
			{
				if (connection != null)
					connection.close();
			}
			catch (Exception e)
			{
			}
		}
	}
}

 

Sorry for the amount of code.

 

 

Developer
Posts: 268
Registered: ‎08-19-2009
My Device: Bold 9700

Re: Getting 302 redirect loop only when connected via BES

Any thoughts?

 

The header field "location" contains the redirect to the same page. Very frustrating.

Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Getting 302 redirect loop only when connected via BES

Just as a complete guess, remove the User Agent.  BIS-B and BES will, if they think they recognize the User Agent, assume they are talking to the Browser, and perhaps they have a figured out another way of handling redirects - for example, perhaps the MDS service will cope with redirects for you.  Anyway, just a thought. 

 

if you have appropriate tools you could test this from a Simulator and watch what happens when the Browser is used too with the URL you are targeting. 

Developer
Posts: 268
Registered: ‎08-19-2009
My Device: Bold 9700

Re: Getting 302 redirect loop only when connected via BES

[ Edited ]

Thanks Peter, I was secretly hoping you'd come to the rescue. Unfortunately removing the user-agent didn't change anything. I even tried faking it as Firefox.

 

What would the browser in the simulator tell me? The browser redirects properly, and in fact the app works in the simulator with the MDS sim (go figure).

 

The browser on the physical device handles the redirect correctly - then again I'm using 6.0 and have no way of knowing which connection it's using.

Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Getting 302 redirect loop only when connected via BES

"you'd come to the rescue" - I wish I was that good!

 

Anyway, I must admit to being a bit confused by this.

 

Can you confirm that the same thing does NOT happen with the MDS Simulator.  You should use an OS 6.0 Simulator and try both the Browser and your application.  The Browser in the Simulator will be forced to use BES if you have not enabled WiFi for it. 

 

If this does not show the problem, then I think the next thing to try to figure out is if the redirect is coming from MDS, or from something the other side of the MDS.  I'm guessing the MDS logs will tell you this, but I have no idea which one to look at.  So I'm hoping someone with more experience of debugging BES communication will look at this thread

 

 

Developer
Posts: 268
Registered: ‎08-19-2009
My Device: Bold 9700

Re: Getting 302 redirect loop only when connected via BES

Thanks again Peter.

 

The problem definitely does not happen in the 6.0 simulator. I fired it up with the MDS simulator, turned off WiFi, verified the connection string being sent was "deviceside=false" and the application worked, as well as the browser.

 

I've asked our BES admin for any logs containing anything with the specific URL in it. There was only one log that had the URL and it really didn't seem to be useful at all (looks just like the MDS simulator output). Maybe I'll have another look.

 

I guess I really need to figure out whether it's the web server behaving funny toward the BES, or the BES manipulating my reponse from the web server. Does that sound like a reasonable course of action?

 

How is a whole other story...

Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Getting 302 redirect loop only when connected via BES

"Does that sound like a reasonable course of action?"

 

Yep.  Good luck.

Developer
Posts: 268
Registered: ‎08-19-2009
My Device: Bold 9700

Re: Getting 302 redirect loop only when connected via BES

I had our BES admin visit the problematic page via a browser on the BES server itself.

 

Turns out, the webserver was returning an AJAX-style page update informing the user they didn't have access to this particular content. Programmatically, all we see is "The page has been moved here" -- but in a browser it's a different story. 

 

Thanks again Peter for your insight!

BlackBerry Development Advisor
Posts: 15,753
Registered: ‎07-09-2008
My Device: BlackBerry PRIV
My Carrier: Bell

Re: Getting 302 redirect loop only when connected via BES

Do you have access to the web server?  Can you confirm it is sending the 302 or could there be a proxy server in place in front of the BlackBerry Enterprise Server that is causing this?  Ask your BES admin if MDS-CS on the BES is configured to go through a proxy.  You could then add this configuration to the MDS-CS Simulator and see if you get the same results.

 

Configure the BlackBerry MDS Simulator to work behind a proxy

http://supportforums.blackberry.com/t5/Testing-and-Deployment/Configure-the-BlackBerry-MDS-Simulator...

Mark Sohm
BlackBerry Development Advisor

Please refrain from posting new questions in solved threads.
Problem solved? Click the Accept As Solution button.
Found a bug? Report it using Issue Tracker