Threading
Another thing that makes working with networking difficult is that you must also use threading.
Networking just can't be done without threading, but this also makes
the applications more complicated and difficult. Network traffic is the
epitome of a "long running task" by even desktop computer standards.
Mobile devices and mobile networks are much slower than desktops!
Therefore, you should always utilize a thread when doing anything that
relates to sending or receiving of data over a network. Unfortunately,
this means more complicated code, but there is just no avoiding it.
Threading is a way to branch
the flow of execution and, in essence, performing two actions at the
same time. Every piece of code that is executed is done so in a thread.
We've never had to mention it before though because the samples that
we've created haven't needed more than one execution path.
By branching the flow of
execution we can have one thread sending or receiving data through a
network connection while the other is allowing the user to continue to
use the application, display menus, or enter data. If a thread were not
used, the user would not be able to do anything else in the application
while the network communication is happening. It would be unresponsive
to menu clicks or even redraw the screen properly so the user would
probably believe the application was broken or had crashed. This
obviously isn't good, which is why the recommendation is to never do
network communication unless it is done in a thread.
If you are unfamiliar with
threads, the sample application we make here won't make much sense. It
would be best to get this understanding and then return to networking
later.
Connector class
Another critical part of the networking toolkit is the Connector class. This class has only a few static members and acts as a factory for all of the Connection
types. The networking that we will be doing is of course one of these,
but there are, in fact, many kinds of connections that can be made
through the Connector class. This class is very flexible because the open method takes only one parameter-a connection string.
It may seem funny but the Connector class doesn't actually initiate a connection. It simply creates an object that represents the connection. The Connection object does the actual work of opening the connection sometime later when it needs to do so. Therefore, the Connector won't fail unless the connection string is invalid in some way.
A connection string is also
called a URL but it is more than just an address like you might think
of when web browsing. It can also contain all of the instructions and
options that the Connection class
might need in order to establish the connection. Certainly an address is
part of that, but the options may be just as important and are likely
to vary greatly depending on what kind of connection is being made.
When a connection string has
options they are given as a name/value pair and are separated by a
semicolon. An example of a connection string with an option that we will
see and use later is http://www.someserver.com;deviceside=true. The Connector
class puts no limits on the number of options a connection string might
have. In the right circumstances, a connection string can get very long
and complex.
HTTP basics
The BlackBerry SDK does
provide good support for networking, but the support is primitive. Not
only are there a myriad of ways to connect to a server in order to make a
request, and that we as developers are left to account for them all,
but we must also have a fairly detailed understanding of the underlying
protocol.
In this case, we will be creating an HttpConnection object so we also need to have a basic understanding of the HTTP protocol. The only thing that most people really know about HTTP is that it is something you put in front of the website addresses in a browser.
The extremely simplistic answer is that HTTP
is a protocol designed for requesting files from one computer by
another computer. The name, Hyper Text Transfer Protocol, comes from
the idea that a document would have HyperText links embedded into it,
which a user can use to gather more information about a topic. When this
happens, one computer would request the linked document from the other
so that the user can see it. In order to make this happen, both
computers have to understand what each other are saying and so the
protocol, HTTP, was created to make
this possible. The protocol has grown over time to do more than just get
documents, but the basic idea is still the way it is primarily used,
even today.
In order to support more than just document retrieval the protocol has a
series of commands, or methods that are supported. The basic operations
of GET and POST are by far the most common uses. HTTP v 1.1 added a few more commands such as PUT, DELETE, and a few others that are still not widely used.
The protocol requires a
request/response pattern in which the sender issues a request, to get a
file for instance, and the receiver then issues a response with the
requested data if there were no errors. There is also a header in both
the request and response where additional parameters can be placed.
These parameters might give an indication about what type of document it
is, how large it is, or the event authentication information if the
document is secured.
This all makes sense when
you look at it from this a low level like this. The point, however, is
that we do need to know about it in order to make an application that
uses it. Only the basic operations of GET and POST
will be used here, but the important thing is that we have to build the
request. Therefore, we need to know what pieces we will need to go into
it and when to use them.
HTTP GET requests
So now that we have some of
the basic information out of the way, let's start with a simple page
fetch from a website. This is just a baby step of course, but we need to
start somewhere.
The biggest part of this will
involve simply setting up the thread to service the connection and then
setting up the connection. The code isn't too difficult, but there are a
lot of things that can go wrong as well.
Time for action - ‑ HTTP Basics
Let's get started with a new project named HTTPBasics. Create a BlackBerry project in Eclipse and create a standard UiApplication derived class and a MainScreen derived class.
At this point you need only one field added to the screen-a RichTextField called _Output. Add a constructor for the screen and then add that field to the screen in the constructor.
You need one more class in this application, a thread to handle the
processing of the request. One more time, create a new class derived
from Thread called ServiceRequestThread.
Now
that the skeleton is in place, start adding some code. The first step
will be to set up the thread with the data that you will need in order
to make the request. Add these data members and the following
constructor to the ServiceRequestThread class.
protected String _URL;
protected HTTPBasicsMainScreen _Dest = null;
public ServiceRequestThread(String URL, HTTPBasicsMainScreen screen)
{
super();
_Dest = screen;
_URL = URL;
}
We will need more code in the thread, but we will come back to that in a bit. Instead, add a menu item to the HTTPBasicsMainScreen class so that you can start the request.
protected MenuItem _GetDataAction = new MenuItem("GetData" , 100000, 10)
{
public void run()
{
String URL = "http://www.google.com;deviceside=true";
ServiceRequestThread svc = new ServiceRequestThread(URL, (HTTPBasicsMainScreen)
UiApplication.getUiApplication().getActiveScreen());
svc.start();
}
};
Don't forget to add the new menu item to the menu by adding this code in the constructor of the screen.
addMenuItem(_GetDataAction);
Another thing you need in the screen is a method to make sure the screen is updated safely.
public void updateDestination(final String text)
{
UiApplication.getUiApplication().invokeLater(new Runnable()
{
public void run()
{
_Output.setText(text);
HTTP GET requestsHTTP GET requestsHTTP basics}
});
}
The last step is to implement the actual request and network call by implementing the run method of the ServiceRequestThread.
public void run()
{
try
{
HttpConnection conn = (HttpConnection)
Connector.open(_URL, Connector.READ_WRITE);
conn.setRequestMethod(HttpConnection.GET);
int responseCode = conn.getResponseCode();
if (responseCode == HttpConnection.HTTP_OK)
{
InputStream data = conn.openInputStream();
StringBuffer raw = new StringBuffer();
byte[] buf = new byte[4096];
int nRead = data.read(buf);
while (nRead > 0)
{
raw.append(new String(buf,0,nRead));
nRead = data.read(buf);
}
_Dest.updateDestination(raw.toString());
}
else
{
_Dest.updateDestination("responseCode="+ Integer.toString(responseCode));
}
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
_Dest.updateDestination("Exception:"+e.toString());
}
}
If you run the application and click on the Get Data menu item, the screen will soon fill up with raw HTML from the Google.com home page!
What just happened?
Wow, that was exciting, wasn't
it? It's not much to look at, but this simple application is a great
example for demonstrating the basics of networking. The first few steps
were all about setting up and getting started, but I didn't give you
much help there. How did it feel? That basic setup has been done with
every project we've made so far and will likely be the first steps of
nearly every project in the future. I don't know why the "new BlackBerry
project" template doesn't do all that boilerplating for you, but it
doesn't.
We got into some code in Step 4 with setting up the Thread
object. This is just basic stuff so we could add the code to create
and start the thread in the menu in Step 5. Although this just shows
that even though the thread runs separately from the rest of the
application doesn't mean it can exist in a vacuum. We had to have some
information to start the request, that is, the URL, and we had to have
some way to get the results back out to the main application. This part
was simply about making sure that information was collected.
In Steps 5 and 6 we created
the menu item that will start the thread and added it to the
application. For this sample, the connection string was simply hardcoded
in the menu, but the thread is set up to be dynamic and allow the URL
to be passed in as a parameter.
As we've said before, the URL is
more than just a web address and this URL includes that parameter we
mentioned earlier. First, let me explain that www.google.com
was chosen simply because the web page loads fast and it isn't very
large. Can you imagine trying to load a page with a lot more content?
Secondly, that parameter is
used to force the device to initiate a TCP/IP direct connection. While
there may be concern about using TCP/IP direct in a production
application, this is just the simulator and we shouldn't face any
problems using it. We could have left the parameter off completely and
the result would still be the same. Each device has a default set that
makes sense for the device, and for the simulator, the default is a
TCP/IP direct connection.
Step 7 is where we added an odd looking little method to update the _Output
field with the text that is being passed into the method. Why is this
needed? Remember that this application is a multithreaded application.
Updating user interface fields from another thread is dangerous! This is
often said in another way; it is not thread-safe. There are other
things going on in the other thread and when we try to set the text from
the ServiceRequestThread, those
things might conflict and cause problems. This is particularly important
when something could happen that would cause the screen to be redrawn,
such as in this case.
The invokeLater method is used as a way for one thread to ask another thread to run some code when there is a chance. The calling thread (the ServiceRequestThread)
doesn't have any control over WHEN the code runs. The receiving thread,
which in this case is the main application thread, will decide when to
execute the code.
The last step in this code is
where the real fun stuff happens. Everything up until this point is
supporting code so we can make the request and do something useful with
the response.
This first line is where we set up the connection and use the Connector class that was mentioned previously. The Connector class will always return a Connection HttpConnection object and so, it is cast to the proper type. We can safely cast it because we know for certain the URL class, but we really need an will begin with http://, which is what tells the Connector to make an HttpConnection object. Also, remember that the Connector
doesn't actually open the connection, so as long as the URL is valid we
should be fine. The second parameter specifies the permissions
requested of the new connection. Even though we won't be writing at this
stage we're still going to ask for Read and Write permissions for later on.
HttpConnection conn = (HttpConnection)Connector.open(_URL, Connector.READ_WRITE);
The call to the setRequestMethod is how we specify which HTTP
command is to be sent to the destination address. Like we've seen a
number of times earlier in the SDK, the valid values have been defined
in the HttpConnection class to make things clear and easy to understand.
conn.setRequestMethod(HttpConnection.GET);
If we were making a more complicated request then there may be other parameters and values to be set before calling getResponseCode.
However, for the simple fetch that we are doing here, there is nothing
else required and we can issue the request. It should be plain to see
that we make that request with a call to the getRespsonseCode method.
What? No, that's not clear at all!
Well, it does sort of make
sense. How can you get a response code if the request hasn't been sent?
The bottom line here though is that there isn't a single method that
will make the request. Any method which requires the request to be sent
will actually cause it to happen. It's an on-demand methodology that has
its benefits, but clarity isn't one of them.
The whole purpose of getting the response code though was to get and
check the return value to know if the request was successful or not. The HTTP
protocol uses a defined set of numbers to indicate various success or
failure conditions. A large number of them are defined as constants in
the HttpConnection class. For the most part though, you will only be concerned about whether it worked or not and we check for this with the HTTP_OK
constant. There are other constant values that can mean success as
well, and so again, this is where the framework leaves the details to us
to handle at a low level. Generally, any response code in the range of 200 to 300 means success of some kind, but this is where a detailed understanding of HTTP comes in useful.
if (responseCode == HttpConnection.HTTP_OK)
Once we know the request has succeeded, we can start to get the data
that has been returned. In this case, it is done by reading 4k of data
at a time and building up a StringBuffer object with the data. You can use a ByteArrayInputStream or other technique if you prefer.
InputStream data = conn.openInputStream();
StringBuffer raw = new StringBuffer();
byte[] buf = new byte[4096];
int nRead = data.read(buf);
while (nRead > 0)
{
raw.append(new String(buf,0,nRead));
nRead = data.read(buf);
}
The last step is to simply update the screen by using the helper method we created, called updateDestination.
Generally, you won't be displaying the raw HTML in the screen like this
though and you would process the data somehow.
Dest.updateDestination(raw.toString());
Pop quiz
What method is used to determine if the request succeeded?
a. isSuccess
b. HttpConnection.HTTP_OK
c. getResponseCode
Which command is used to issue an HTTP request?
a. HTTPConnection.GET
b. HTTPConnection.REQUEST
c. HTTPConnection.SEND
All networking communication should be done within a thread.
a. TRUE
b. FALSE
Have a go hero - but what if it didn't work?
In spite of laying out good
plans and even using already tested code, sometimes things just don't
work. Debugging this kind of situation can be very hard! For me, I
always assume that there is some kind of bug in the code first, but when
dealing with networking code we have to look at the environment as
well. If this sample didn't work you are in luck because you know that
it can't be the code that is wrong.
Remember that even though we
are running this in the simulator, that simulator is running on your
computer, which is a part of your network. The simulator doesn't try to
simulate the network traffic. No, it actually issues the request on your
computer so that the request can be completed for real. This means that
the simulator is connecting to the network and sending data to the host
just like the device will do. This also means that if there is some
networking issue on the computer you are running the simulator on, it
will interfere with the application being run on the simulator in the
same way. Do you have a firewall running? Is your networking configured
correctly? There are a 101 possibilities as to why the simulator might
be failing-all of them dealing with the computer it's running on.
Now, you may try to run the
browser in the simulator as a test to make sure that the simulator is
working right. Sadly, this isn't going to work without running an
additional program on your desktop. We'll use this program later, but
for now, just understand that the simulator browser application isn't a
good way to test things out.
The best way to test things
out is through your standard desktop browser. Can you address the host
or service you are trying to connect to? If you can't do it through your
desktop browser then the simulator isn't going to work either.
HTTP POST requests
The other method that is available in HTTP is the POST method. Using an HTTP POST is the most common way to send data to a server and it is supported with the HttpConnection (which we just used). The server will send some data back as well, so a POST command isn't just a one way message. Consider it to be a POST immediately followed by another GET, all rolled into one command.
Historically, an HTTP POST is used to submit a form on a web page once the user is done filling it out and has clicked a Submit button. You can certainly use a BlackBerry application to do an HTTP POST to interact with forms like a user would do, but this isn't the best approach.
Because the forms are meant
to look good, be displayed on the screen and interact with a user there
is a lot of other stuff that comes along with the HTML than just the
form. There will be HTML tags related to the layout of fields on the
form, there may be color or image tags, or even JavaScript, all of which
a BlackBerry application just doesn't need to be concerned with. For
these reasons, you shouldn't interact with HTML pages unless there is no
other choice.
The clear alternative is to
utilize a web service of some kind to trade information with a server.
With a web service there isn't any other data related to formatting or
presentation, and so the amount of data being transmitted is
significantly less than utilizing web pages like a user might use. These
can also use the HTTP POST method.
For simple web services,
the process for making a request is exactly the same as the process of
submitting a form like a user might do. The biggest difference is in the
response data-a web service response should be much more concise.
In order to pass the data with an HTTP POST command we must get familiar with a new class-the URLEncodedPostData
class. This class has two important purposes. First, it stores the
data that will be sent with the request to the server as a list of
name-value pairs. There is no data type information to go along with
these parameters either; they are all just string values.
The second purpose is to
encode the data into a URL encoded format. Why is that needed? The URL
encoded format isn't complicated or special; it just makes sure that the
data provided with the parameters can be sent to the server. The HTTP
specification uses a lot of characters for special purposes within the
protocol, and so those characters aren't allowed to be in the data being
sent through HTTP. Some things, such as spaces, are ignored while
others such as a greater-than-sign are given special meaning. We can't
just do without these characters so we must encode them in some way so
that they can be passed with the request.