/**
* Copyright (c) 2012-2013 Nokia Corporation. All rights reserved.
* Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
* Oracle and Java are trademarks or registered trademarks of Oracle and/or its
* affiliates. Other product and company names mentioned herein may be trademarks
* or trade names of their respective owners.
* See LICENSE.TXT for license information.
*/
package com.nokia.example.rlinks.network;
import com.nokia.example.rlinks.SessionManager;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.pki.CertificateException;
/**
* A client for asynchronous HTTP operations.
*/
public class HttpClient implements Runnable {
private static boolean prompted = false;
private static boolean allowed = false;
private static final int MAX_NETWORK_THREADS = 2;
private static final Thread[] NETWORK_THREADS = new Thread[MAX_NETWORK_THREADS];
private static final Vector QUEUE = new Vector();
private static final CookieJar COOKIE_JAR = SessionManager.getInstance();
// HTTP User-Agent string sent by this client.
private static final String USER_AGENT_STRING = "Reddit for Series 40 Touch & Type";
/**
* An interface for persisting cookies received by this HTTP client.
*/
public interface CookieJar {
public void put(String cookieContent);
public String getCookieHeader();
}
private HttpClient() {}
/**
* Add a network operation in the client's queue.
*
* @param operation HttpOperation to enqueue
*/
public static void enqueue(final HttpOperation operation) {
synchronized (QUEUE) {
QUEUE.addElement(operation);
QUEUE.notifyAll();
}
synchronized (NETWORK_THREADS) {
for (int i = 0; i < NETWORK_THREADS.length; i++) {
if (NETWORK_THREADS[i] == null) {
NETWORK_THREADS[i] = new Thread(new HttpClient(), "NetworkThread" + i);
NETWORK_THREADS[i].start();
return;
}
}
}
}
/**
* Abort a previously enqueued operation.
*
* @param operation HttpOperation to remove from the queue
*/
public static void abort(final HttpOperation operation) {
synchronized (QUEUE) {
QUEUE.removeElement(operation);
}
}
/**
* Sends a synchronous HTTP request.
*
* @param operation HttpOperation with request details
* @throws NetworkError
*/
private byte[] sendRequest(HttpOperation operation)
throws NetworkException {
HttpConnection hcon = null;
DataInputStream dis = null;
DataOutputStream dos = null;
ByteArrayOutputStream response = new ByteArrayOutputStream();
try {
// Only set mode to READ_WRITE if there's data to be written
final int mode = operation.getRequestBody() == null ? Connector.READ : Connector.READ_WRITE;
hcon = (HttpConnection) Connector.open(operation.getUrl(), mode);
if (hcon == null) {
throw new NetworkException("No network access");
}
hcon.setRequestMethod(operation.getRequestMethod());
hcon.setRequestProperty("User-Agent", USER_AGENT_STRING);
// Set cookies in request
if (operation.isCookiesEnabled()) {
writeCookies(hcon);
}
// Send request body
if (operation.getRequestBody() != null) {
// Content-Type is must to pass parameters in POST Request
hcon.setRequestProperty("Content-Type", operation.getRequestContentType());
// Obtain DataOutputStream for sending the request string
dos = hcon.openDataOutputStream();
byte[] requestBody = operation.getRequestBody().getBytes();
// Send request string to server
for (int i = 0, len = requestBody.length; i < len; i++) {
dos.writeByte(requestBody[i]);
}
}
// Read cookies from response
if (operation.isCookiesEnabled()) {
readCookies(hcon);
}
// Obtain DataInputStream for receiving server response
dis = new DataInputStream(hcon.openInputStream());
// Retrieve the response from server
byte[] buffer = new byte[1024];
int read = 0;
while ((read = dis.read(buffer)) > -1 && !operation.isAborted()) {
response.write(buffer, 0, read);
}
}
catch (CertificateException ce) {
throw new NetworkException(ce.getMessage());
}
catch (Exception e) {
throw new NetworkException(e.getMessage());
}
finally {
// Free up I/O streams and HTTP connection
try {
if (hcon != null) {
hcon.close();
}
if (dis != null) {
dis.close();
}
if (dos != null) {
dos.close();
}
}
catch (IOException ioe) {
}
}
return response.toByteArray();
}
/**
* Read cookies from the HttpConnection and store them in the cookie jar.
*
* @param hcon HttpConnection object
* @throws IOException
*/
private void readCookies(HttpConnection hcon) throws IOException {
String headerName = hcon.getHeaderField(0);
// Loop through the headers, filtering "Set-Cookie" headers
for (int i = 0; headerName != null; i++) {
headerName = hcon.getHeaderFieldKey(i);
if (headerName != null && headerName.toLowerCase().equals("set-cookie")) {
String cookieContent = hcon.getHeaderField(i);
COOKIE_JAR.put(cookieContent);
}
}
}
/**
* Take cookies from the cookie jar and write them as request propreties
* in the HttpConnection.
*
* @param hcon HttpConnection object
* @throws IOException
*/
private void writeCookies(HttpConnection hcon) throws IOException {
String cookieHeader = COOKIE_JAR.getCookieHeader();
if (cookieHeader == null) {
return;
}
hcon.setRequestProperty("Cookie", cookieHeader);
}
/**
* Thread entry point. Network operation is run in a separate thread.
*/
public final void run() {
HttpOperation operation = null;
try {
while (true) {
// Take the next operation in queue, or wait if none available
synchronized (QUEUE) {
if (QUEUE.size() > 0) {
operation = (HttpOperation) QUEUE.elementAt(0);
QUEUE.removeElementAt(0);
}
else {
QUEUE.wait();
}
}
if (operation == null) {
continue;
}
// Run any operation that was found in the queue
try {
byte[] response = sendRequest(operation);
if (!operation.isAborted()) {
operation.responseReceived(response);
}
}
catch (NetworkException e) {
System.out.println("NetworkError: " + e.getMessage());
operation.responseReceived(null);
}
operation = null;
}
}
catch (Throwable t) {
System.out.println("HttpClient error: " + t.getMessage());
operation.responseReceived(null);
}
}
/**
* Determine if network access is available.
*
* @return True if network access is available, false otherwise
*/
public synchronized static boolean isAllowed() {
if (!prompted) {
promptNetworkAccess();
}
return allowed;
}
/**
* Opens a network connection just to trigger the network prompt.
*/
private static void promptNetworkAccess() {
prompted = true;
HttpConnection stimulus = null;
try {
stimulus = (HttpConnection) Connector.open("http://www.example.com/");
}
catch (SecurityException se) {
allowed = false;
return;
}
catch (Exception e) { /* Catch all the other exceptions */ }
finally {
try {
if (stimulus != null) {
stimulus.close();
}
}
catch (IOException ioe) {}
}
allowed = true;
}
}