I'm trying to implement simple Androd client with RestLet for my thesis.Evrything works well over HTTP but I have to get it to work over HTTPS with self-signed certificate and this is were I have problems.
I was unable to find any helpful articles or anything similar about how to implement this on Android with RestLet. I know how to do this with code from Android Developers but I need to do this with Restlet classes.
I managed to implement some code which will execute GET method ( though very slow, it takes about 20-30 seconds to get data from server) but i have problems with PUT method.
I used the following code to implement HTTPS client
private static ClientResource makeInstance(String url, Context con) {
Reference reference = new Reference(url);
Log.d("URL", url);
String keyStoreFileAbsolutePath = PreferenceManager
.getDefaultSharedPreferences(con).getString(
Constants.PREF_KEY_KEYSTORE_FILE_PATH, "");
System.setProperty("javax.net.ssl.trustStore",keyStoreFileAbsolutePath);
System.setProperty("javax.net.ssl.trustStorePassword", "ks090278d");
System.setProperty("ssl.TrustManagerFactory.algorithm",
javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
org.restlet.Context context = new org.restlet.Context();
context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
ClientResource resource = new ClientResource(context, reference);
Engine.getInstance().getRegisteredClients().clear();
Engine.getInstance().getRegisteredClients()
.add(new HttpClientHelper(null));
resource.release();
return resource;
}
and I'm using it like this
ClientResource request = new ClientResource(url);
request.setRetryAttempts(0);
Series<Header> responseHeaders = (Series<Header>) request.getResponse()
.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Series(Header.class);
request.getRequestAttributes().put("org.restlet.http.headers",
responseHeaders);
}
responseHeaders.add(new Header("Accept", "application/json;"));
responseHeaders.add(new Header("Content-Type", "application/json;"));
responseHeaders.add(new Header("Authorization",
"Basic ZXN0dWRlbnQ6YW5kcm9pZA==;"));
try {
request.put(new JsonRepresentation(jsonString));
Response response = request.getResponse();
if (response != null) {
Log.d("Response", "HTTP Response: " + response.toString());
ResponseHandler
.handleResponse(response);
}
} catch (Exception e) {
}
When I execute this request I'm getting "Internal server error - 500". I'm using basically the same code for GET method only insted of
request.put(new JsonRepresentation(jsonString));
I'm doing
request.get();
and it works (though very slow as I mentioned above).
I checked if this is due to some error with the server but I think is not because I managed to get appropriate response when I'm using code similar to the code from Android Developers and also when I'm trying to execute PUT to the same URL from RestClient in Chrome.
Can anyone give me any suggestions how to get this to work? I'm not sure if my aproach is good to begin with, may be that I'm gooing in the wrong direction from the start.
Thnaks in advance.
Related
Just as a demonstration the code will work, I am attempting to fetch some JSON data within my oncreate function. I know it should run on a different thread but I want to be sure the code successfully fetches my JSON before moving it into it's own thread.
The code is below:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
/***************************************************/
final String TAG = "PostFetcher";
final String SERVER_URL = "http://kylewbanks.com/rest/posts";
// final String TAG = "PostsActivity";
// List<Post> posts;
try {
//Create an HTTP client
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(SERVER_URL);
//Perform the request and check the status code
HttpResponse response = client.execute(post);
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
try {
//Read the server response and attempt to parse it as JSON
Reader reader = new InputStreamReader(content);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setDateFormat("M/d/yy hh:mm a");
Gson gson = gsonBuilder.create();
List<JsonObject> posts = new ArrayList<JsonObject>();
Log.e(TAG, "Checking: " + posts);
// posts = Arrays.asList(gson.fromJson(reader, JsonObject[].class));
content.close();
} catch (Exception ex) {
Log.e(TAG, "Failed to parse JSON due to: " + ex);
}
} else {
Log.e(TAG, "Server responded with status code: " + statusLine.getStatusCode());
}
} catch(Exception ex) {
Log.e(TAG, "Failed to send HTTP POST request due to: " + ex);
}
}
When I run the code, I get the second to last exception message:
Server responded with status code: 500
Can anyone please tell me what I'm doing wrong?
You are sending a HttpPost request to (obviously) an website that uses RESTful styled API.
This means, it works with HTTP Verbs (GET, POST, PUT, DELETE).
If you want to read data and the read access never changes data, use GET.
If you want to update or replace data, user PUT or POST (put usually to replace, POST to change/add). However, JavaScript does (or did) only support GET and POST requests, so keep that in mind.
If you want to delete a resource or collection, use DELETE.
That being said: If you want to load data, use Get in your case HttpGet instead of HttpPost.
Also read more about RESTful web APIs.
Edit:
In fact, calling the given URL in Fiddler2 (as stated in the comment on the other answer) results a HTML website reporting the error:
You called this URL via POST, but the URL doesn't end in a slash and
you have APPEND_SLASH set. Django can't redirect to the slash URL
while maintaining POST data. Change your form to point to
kylewbanks.com/rest/posts/ (note the trailing slash), or set
APPEND_SLASH=False in your Django settings.
Its internal server error..check if there are any exceptions are getting thrown at server side.
It has nothing to do with your android code, the problem is at server.
You can use AsyncTask to run network/filesystem related operations.
I've got a class:
public class WebReader implements IWebReader {
HttpClient client;
public WebReader() {
client = new DefaultHttpClient();
}
public WebReader(HttpClient httpClient) {
client = httpClient;
}
/**
* Reads the web resource at the specified path with the params given.
* #param path Path of the resource to be read.
* #param params Parameters needed to be transferred to the server using POST method.
* #param compression If it's needed to use compression. Default is <b>true</b>.
* #return <p>Returns the string got from the server. If there was an error downloading file,
* an empty string is returned, the information about the error is written to the log file.</p>
*/
public String readWebResource(String path, ArrayList<BasicNameValuePair> params, Boolean compression) {
HttpPost httpPost = new HttpPost(path);
String result = "";
if (compression)
httpPost.addHeader("Accept-Encoding", "gzip");
if (params.size() > 0){
try {
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
}
try {
HttpResponse response = client.execute(httpPost);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
if (entity.getContentEncoding() != null
&& "gzip".equalsIgnoreCase(entity.getContentEncoding()
.getValue()))
result = uncompressInputStream(content);
else
result = convertStreamToString(content);
} else {
Log.e(MyApp.class.toString(), "Failed to download file");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private String uncompressInputStream(InputStream inputStream)
throws IOException {...}
private String convertStreamToString(InputStream is) {...}
}
I cannot find a way to test it using a standard framework. Especially, I need to simulate total internet lost from inside the test.
There are suggestions to manually turn the Internet in the emulator off while performing the test. But it seems to me as not quite a good solution, because the automatic tests should be... automatic.
I added a "client" field to the class trying to mock it from inside the test class. But implementation of the HttpClient interface seems quite complex.
The Robolectric framework allows the developers to test Http connection as far as I know. But I guess there is some way to write such a test without using so big additional framework.
So are there any short and straightforward ways of unit testing classes that use HttpClient? How did you solve this in your projects?
I added a "client" field to the class trying to mock it from inside the test class. But implementation of the HttpClient interface seems quite complex.
I am a little bit confuse about this statement. From the question title, you are asking about unit-testing httpClint, by mocking a FakeHttpClient may help you unit-testing other part of app except httpClient, but doesn't help anything for unit-testing httpClient. What you need is a FakeHttpLayer for unit-testing httpClient (no remote server, network requires, hence unit-testing).
HttpClient Dummy Test:
If you only need examine app behavior in the situation that internet is lost, then a classic Android Instrument Test is sufficient, you can programmatically turn the Internet in the emulator off while performing the test:
public void testWhenInternetOK() {
... ...
webReader.readWebResource();
// expect HTTP 200 response.
... ...
}
public void testWhenInternetLost() {
... ...
wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
wifiManager.setWifiEnabled(false);
webReader.readWebResource();
// expect no HTTP response.
... ...
}
This requires the remote http server is completely setup and in a working state, and whenever you run your test class, a real http communication is made over network and hit on http server.
HttpClient Advanced Test:
If you want to test app behavior more precisely, for instance, you want to test a http call in you app to see if it is handle different http response properly. the Robolectric is the best choice. You can use FakeHttpLayer and mock the http request and response to whatever you like.
public void setup() {
String url = "http://...";
// First http request fired in test, mock a HTTP 200 response (ContentType: application/json)
HttpResponse response1 = new DefaultHttpResponseFactory().newHttpResponse(HttpVersion.HTTP_1_1, 200, null);
BasicHttpEntity entity1 = new BasicHttpEntity();
entity1.setContentType("application/json");
response1.setEntity(entity1);
// Second http request fired in test, mock a HTTP 404 response (ContentType: text/html)
HttpResponse response2 = new DefaultHttpResponseFactory().newHttpResponse(HttpVersion.HTTP_1_1, 404, null);
BasicHttpEntity entity2 = new BasicHttpEntity();
entity2.setContentType("text/html");
response2.setEntity(entity2);
List<HttpResponse> responses = new ArrayList<HttpResponse>();
responses.add(response1);
responses.add(response2);
Robolectric.addHttpResponseRule(new FakeHttpLayer.UriRequestMatcher("POST", url), responses);
}
public void testFoo() {
... ...
webReader.readWebResource(); // <- a call that perform a http post request to url.
// expect HTTP 200 response.
... ...
}
public void testBar() {
... ...
webReader.readWebResource(); // <- a call that perform a http post request to url.
// expect HTTP 404 response.
... ...
}
Some pros of using Robolectric are:
Purely JUnit test, no instrument test so don't need start emulator (or real device) to run the test, increase development speed.
Latest Robolectric support single line of code to enable/disable FakeHttpLayer, where you can set http request to be interpreted by FakeHttpLayer (no real http call over network), or set the http request bypass the FakeHttpLayer(perform real http call over network). Check out this SO question for more details.
If you check out the source of Robolectric, you can see it is quite complex to implement a FakeHtppLayer properly by yourself. I would recommend to use the existing test framework instead of implementing your own API.
Hope this helps.
I am using the Jackson JSON parser as I heard it was a lot more efficient than the default Android parser. I learned how to use it off this tutorial here
http://www.mkyong.com/java/jackson-streaming-api-to-read-and-write-json/
which is great tutorial if anyone wants to learn how to use Jackson json parser.
However, I am having an issue in that I can parse data fine in Java from a URL, however when I use Jackson with Android, I get null values or the screen just shows up black for some reason.
In order to retrieve the data from the website I am using this code from here
http://www.javacodegeeks.com/2011/01/android-json-parsing-gson-tutorial.html
private InputStream retrieveStream(String url) {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet getRequest = new HttpGet(url);
try {
HttpResponse getResponse = client.execute(getRequest);
final int statusCode = getResponse.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w(getClass().getSimpleName(),
"Error " + statusCode + " for URL " + url);
return null;
}
HttpEntity getResponseEntity = getResponse.getEntity();
return getResponseEntity.getContent();
}
catch (IOException e) {
getRequest.abort();
Log.w(getClass().getSimpleName(), "Error for URL " + url, e);
}
return null;
}
Then in my parse data method
InputStream source = retrieveStream(url);
try {
JsonFactory jfactory = new JsonFactory();
JsonParser jParser = jfactory.createJsonParser(source);
Then I parse data as was shown in the tutorial I linked above
while (jParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jParser.getCurrentName();
if ("Name".equals(fieldname)) {
jParser.nextToken();
this.setName(jParser.getText());
}
if ("Number".equals(fieldname)) {
jParser.nextToken();
this.setNumber(jParser.getText());
}
}
The url I am using is a dummy site set up which just has a JSON file on it which I am using to practice Jackson JSON parsing.
Now I know my parse data code is fine, as I in normal Java class, I can parse the data from the website using the code I created, and it works fine.
However if I try to use the code in Android with the code I have just shown, I just get a black screen for some odd reason. I have internet permissions enabled in manifest
Is there something wrong with the http code I have used? If so could someone show me how it should be done? And also why I am getting a black screen, I don't understand why it would show that.
Thanks in advance
Not sure if this is the problem, but your looping construct is unsafe: depending on kind of data you get, it is quite possible that you do not get END_OBJECT as the next token. And at the end of content, nextToken() will return null to indicate end-of-input. So perhaps you get into infinite loop with certain input?
I found the issue, the link was local host which could not be accessed from Emulator. Settings were changed, and can now access link, works perfectly now :D
I'm trying to write application for Android to access Google Tasks. I decided to use ClientLogin authorization method.
I'm getting ClientLogin "Auth" marker from first POST request. Then i try to retrieve a user's task lists with GET request. I wrote the following code for this:
String requestString = "https://www.googleapis.com/tasks/v1/users/#me/lists";
String resultString = "";
try {
URLConnection connection1 = null;
URL url = new URL(requestString);
connection1 = url.openConnection( );
HttpURLConnection httpsConnection1 = (HttpURLConnection)connection1;
httpsConnection1.setRequestMethod("GET");
httpsConnection1.setRequestProperty("Authorization", "GoogleLogin auth="+authkeyString);
httpsConnection1.setDoInput(true);
httpsConnection1.connect();
int responseCode = httpsConnection1.getResponseCode();
System.out.println(responseCode);
if (responseCode == HttpsURLConnection.HTTP_OK) {
InputStream in = httpsConnection1.getInputStream();
InputStreamReader isr = new InputStreamReader(in, "UTF-8");
StringBuffer data = new StringBuffer();
int c;
while ((c = isr.read()) != -1){
data.append((char) c);
}
resultString = new String (data.toString());
}
else{
resultString = "Errror - connection problem";
}
}
httpsConnection1.disconnect();
}
catch (MalformedURLException e) {
resultString = "MalformedURLException1:" + e.getMessage();
}
catch (IOException e) {
resultString = "IOException1:" + e.getMessage();
}
Here is "authkeyString" - string variable with authorization marker.
When i run application under real Android device i receive: "IOException:SSL handshake failure: Failure is ssl library, usually a protocol error ..... "
Also i tried to run this code from simple java application from desktop:
Exception in thread "main" java.lang.IllegalArgumentException: Illegal character(s) in message header value: GoogleLogin auth=DQ ..... UT
at sun.net.www.protocol.http.HttpURLConnection.checkMessageHeader(HttpURLConnection.java:428)
at sun.net.www.protocol.http.HttpURLConnection.isExternalMessageHeaderAllowed(HttpURLConnection.java:394)
at sun.net.www.protocol.http.HttpURLConnection.setRequestProperty(HttpURLConnection.java:2378)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.setRequestProperty(HttpsURLConnectionImpl.java:296)
at Tasks.main(Tasks.java:81)
ClientLogin with username / password
If you want to use ClientLogin with the Google APIs Client Library for Java , you'll need to setup a HttpRequestFactory that supports authentication.
private static HttpTransport transport = new ApacheHttpTransport();
public static HttpRequestFactory createRequestFactory(
final HttpTransport transport) {
return transport.createRequestFactory(new HttpRequestInitializer() {
public void initialize(HttpRequest request) {
GoogleHeaders headers = new GoogleHeaders();
headers.setApplicationName("MyApp/1.0");
request.headers=headers;
try {
authorizeTransport(request);
} catch (Exception e) {
e.printStackTrace();
}
}
});
Notice the authorizeTransport method, that will basically authorize the request. The authorizeTransport looks like this:
private void authorizeTransport(HttpRequest request) throws HttpResponseException, IOException {
// authenticate with ClientLogin
ClientLogin authenticator = new ClientLogin();
authenticator.authTokenType = Constants.AUTH_TOKEN_TYPE;
authenticator.username = Constants.USERNAME;
authenticator.password = Constants.PASSWORD;
authenticator.transport = transport;
try {
Response response = authenticator.authenticate();
request.headers.authorization=response.getAuthorizationHeaderValue();
} catch (HttpResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
You basically setup a ClientLogin authentication method by providing a username/passsword. The authenticate method will authenticate based on the provided values and returns a response object that can be added to the HTTP header to provide ClientLogin authentication.
Android AccountManager
In order to integrate with the Android AccountManager (avoiding the android user to type in his username / password) , you can find some sample code here http://code.google.com/p/google-api-java-client/wiki/AndroidAccountManager
The fact that the user doesn't need to key in his username/passwords adds to the users comfort level, but the solution remains relatively insecure.
I would strongly suggest doing the following :
Use a client library
I would suggest moving to Google APIs Client Library for Java for this type of interaction. It's Android compatible java client library for all kinds of Google APIs.
You don't want to be bothered with implementing low level HTTP, security and JSON plumbing.
The Google Task API Developers guide also mentions the same library. The library will take care of the authentication for you. If you want to use ClientLogin, all you'll have to do is specify a username/password, or integrate with the Android AccountManager.
Avoid using ClientLogin
ClientLogin is considered insecure, and a number of security holes have been found regarding this authentication mechanism. Google also doesn't recommend it. However, should you decide to continue using ClientLogin, the Google APIs Client Library for Java does support it.
I am using Ksoap2-Android for consuming the WCF Services.
For the dotnet client we keep the allowCookies="true" in our binding configuration and it sends the same sessionid and keeps my sessions intact in my WCF services (My services are
interdependent and use the sessions).
Any one know any such setting for ksoap2-android, that will allow me to consume the
WCF service keeping my session intact on the server.
Currently when i make a new call to the service, the sessionid gets changed and all my
session variables clear out and loose their values.
In C# i do the next, just use the android methods to do this:
1.- Make the Http request,
2.- Make a Cookie Container of the first request.
3.- Put the cookieContainer over the second request, for example you can put in a bundle in a intent for the 2nd activity, and use this cookies for send the second http request...
My C# Code;
protected static void GetData()
{
CookieContainer cookies = new CookieContainer();
HttpWebRequest request1 = (HttpWebRequest)WebRequest.Create("https://any.com/url");
request1.CookieContainer = cookies;
HttpWebResponse response1 = (HttpWebResponse)request1.GetResponse();
StreamReader responseReader1 = new StreamReader(response1.GetResponseStream());
Response1 = responseReader1.ReadToEnd();
responseReader1.Close();
responseReader1.Dispose();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
request.CookieContainer = cookies;
request.Method = "GET";
request1.KeepAlive = true;
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader responseReader = new StreamReader(response.GetResponseStream());
Response = responseReader.ReadToEnd();
responseReader.Close();
responseReader.Dispose();
if (Response.Contains("Server Error in '/Verification' Application."))
{
Console.WriteLine("Empty Registry" + Url);
}
}
catch (WebException ex)
{
if (ex.Response != null)
{
Console.WriteLine("Failed at: " + Url);
}
if (ex.Status == WebExceptionStatus.ProtocolError)
{
if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine(ex.Status);
}
}
else if (ex.Status == WebExceptionStatus.NameResolutionFailure)
{
Console.WriteLine(ex.Status);
}
}
}
I do That for keep the sesionID of the first request, and later, in the second request, i add the cookieContainer (because the server requires me) (to make a bot search) ;)... hope this give you ideas.