The first step is to get the CopyCat
service working. This service is written in .NET and is built with
Microsoft Visual Studio 2008. Not everyone will have access to this tool so feel free to create
a web service using your favorite tool. The thing we are interested in
at this point is a web method named CopyMe that accepts a string parameter named Value and returns a string. In fact, the implementation of CopyMe is exceedingly simple as it just returns the same string that was passed into it.
As
you actually need to send data to a server now, you need to start by
adding an edit field to the application that prompts for the data to
send. Add an EditField as a data member to the application.
protected EditField _CopyString = new EditField();
Next, add the field to the screen by adding this code into the constructor.
_CopyString.setLabel("Copy Source: ");
_CopyString.setMaxSize(50);
_add(CopyString);
We'll come back to the Screen class later. At this point, we need to modify the ServiceRequestThread class to add a data member and a method to set it.
protected URLEncodedPostData _PostData = null;
public void setPOSTData(URLEncodedPostData data)
{
_PostData = data;
}
Once you have the _PostData object, you need to use it. In the run method, replace the setRequestMethod call with this if statement.
if (_PostData != null)
{
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", Integer.toString(_PostData.size()));
OutputStream strmOut = conn.openOutputStream();
strmOut.write(_PostData.getBytes());
strmOut.close();
}
else
{
conn.setRequestMethod(HttpConnection.GET);
}
Now that the ServiceRequestThread class is changed you can come back to HttpBasicsMainScreen
and add a new menu item that you will use to trigger a request. You
will likely need to change the address in the connection string or, at
the very least, the port number of the service. Make sure that the URL
is correct for referencing the web service and that the service is
currently running.
protected MenuItem _PostDataAction = new MenuItem("PostData" , 100000, 10)
{
public void run()
{
URLEncodedPostData oPostData = new URLEncodedPostData( URLEncodedPostData.DEFAULT_CHARSET, false);
oPostData.append("Value",_CopyString.getText());
String URL = "http://localhost:2997/CopycatWebServiceCSharp/ Service.asmx/CopyMe;deviceside=true";
ServiceRequestThread svc = new ServiceRequestThread(URL, (HTTPBasicsMainScreen)
UiApplication.getUiApplication().getActiveScreen());
svc.setPOSTData(oPostData);
svc.start();
}
};
Lastly, you need to add the menu into the application by adding the next line to the constructor.
addMenuItem(_PostDataAction);
What just happened?
Step 1 of this section is
simply about setting up a web service that you can test with. While I do
provide some code, I also know that not everyone will be able to use
the tool or be able to set up the service. This is why there are
specific directions given in case you need to create your own. We could
have tried to rely on one of the public web services such as at
www.webservicex.net, but the services can change, break, or be removed
all together and the example wouldn't be valid any more. Therefore, it's
better to utilize a service that you control. If you choose to use the
provided code, great! If you prefer to write your own, that's great too!
The other steps are mostly housekeeping steps until you get to steps 4 and 5, which modify the ServiceRequestThread class. As we pointed out earlier, the URLEncodedPostData class is used to collect the parameters that are passed with the HTTP POST request. This thread is designed to be generic, therefore it can't populate the parameters. Instead, the setPOSTData method is there to allow the class that starts the thread to supply a pre-populated URLEncodedPostData class that will be used later when the request is actually processed.
protected URLEncodedPostData _PostData = null;
public void setPOSTData(URLEncodedPostData data)
{
_PostData = data;
}
The run method is also changed slightly to check and see if there are any POST parameters supplied. If so, then the request type is set to use a POST
method and the parameters are added to the output stream. Also, there
are a couple of properties that are set to support the additional POST data. The Content-type
property is used to let the receiving computer know more about the
data that is included within the request. In this case, you are
specifying that the data has been URL encoded by using the application/x-www-form-urlencoded
value. This value isn't an arbitrary value, but a specific value that
the server already knows about. In addition to setting the content type,
you also set the size of the data being sent with the request.
Once the supporting
attributes are set you get a stream that will be used to add the
parameter data in with the request by using the openOutputStream method. The actual data is written by using the stream's write method before being closed.
All of this work is necessary because of the extra data that needs to go with an HTTP POST command. If the _PostData object isn't set, then we skip all of this work and issue an HTTP GET command instead.
if (_PostData != null)
{
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", Integer.toString(_PostData.size()));
OutputStream strmOut = conn.openOutputStream();
strmOut.write(_PostData.getBytes());
strmOut.close();
}
else
{
conn.setRequestMethod(HttpConnection.GET);
}
Once the thread is taken care
of, you go back to the screen and add the menu that will be used to
issue the request. The request is largely the same as the request that
is used fetch a web page, but this adds the step of creating and
populating the URLEncodedPostData
object. When populating this object keep in mind that the parameters
that you add must be named the same as those in the web service
definition. In this case, the web service declares the input parameter
to be named Value so you must use that same name when adding the parameter to the PostData.
URLEncodedPostData oPostData = new
URLEncodedPostData(URLEncodedPostData.DEFAULT_CHARSET, false);
oPostData.append("Value",_CopyString.getText());
The final step is to give the completed URLEncodedPostData object to the thread that will be handling the request by using the setPostData method that we created earlier.
Once you run the application you can see that the CopyMe
service is doing exactly what it is supposed to do, which is to simply
return the input parameter as the output of the web service. But, if
that's the case, why is there all of this extra stuff around it?
Web services return data wrapped in XML using a protocol called SOAP
that can get very large and complicated when dealing with custom
structures or complex objects. This is about as simple as we can get.
You can see that the real data we are after is buried in the middle of a
lot of XML data. Sooner or later, you will need to parse that XML in
order to get to the meaningful data that you care about.
Pop quiz
What must be done to any data sent with an HTTP request?
a. It must be converted into a byte array
b. It must be a string type
c. It must be URL encoded
What properties must be set passing additional data with an HTTP request?
a. The content length property
b. The content type property
c. Both content length and content type
When adding parameter data, what name should be used?
a. The name that is defined in the web service definition
b. The name is irrelevant; any name will work.
c. The name should be in the form of param with a sequential number afterward
There are a few
different approaches to parsing the data as well. You can try to do the
string matching and manipulations yourself. If the data you are working
with is both small and static, then this might be the easiest approach.
However, if the data is at all complicated, then this approach gets real
messy real quick. It's better to rely on a tool that has already been
tested.
The BlackBerry SDK provides an implementation of a Simple API for XML
(SAX) parser that we will use to retrieve data from the returned
pages. As the name implies, this tool is simple to use and simple in
capabilities, which can mean that if you want to do something complex,
it may no longer be simple! In technical speak, a SAX parser
is a non-validating parser that uses a callback mechanism to notify the
application each time it encounters a new attribute or element.
When we say it is a
non-validating parser it means that the parser does not have any
knowledge about how the XML should be formatted and it doesn't try to
validate whether the XML is formatted properly or not. As long as the
XML structure is technically valid the parser will parse it without
issue. If certain nodes are supposed to be nested, if a node is supposed
to be of a particular type, or only supposed to occur once the SAX
parser will not enforce any of these rules.
Generally, you don't need to
worry much about validating the structure of the XML when consuming the
string from a service; you can usually trust that the service will
provide well structured output. Because of this, a SAX parser usually
works out just fine.
In order to utilize the SAX parser you must create a specific Handler class for each kind of XML response that you want to parse. This class is derived from a class called DefaultHandler
and is how the parser makes callbacks to notify the application of
various events that are encountered when parsing the XML. For example, a
method called StartElement is there and can be overridden to do some processing each time a new element tag is encountered.
So with that bit of overview in place, let's expend the HttpBasics class to process the results of the CopyMe web method call.