/* * Copyright 2012 Monits * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.monits.blackberry.commons.connection; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import javax.microedition.io.HttpConnection; import net.rim.blackberry.api.browser.URLEncodedPostData; import net.rim.device.api.io.IOUtilities; import net.rim.device.api.io.http.HttpProtocolConstants; import net.rim.device.api.util.StringMatch; import com.monits.blackberry.commons.logger.Logger; import com.thirdparty.connectivity.HttpConnectionFactory; import com.thirdparty.connectivity.NoMoreTransportsException; /** * HTTPRequestRunnable * Create a Runnable that will * process the URL supplied and supply the data that the server returns * * See Constructor for more details */ public class HTTPRequestRunnable extends Object implements Runnable { public static final Logger logger = Logger.getLogger(HTTPRequestRunnable.class); public static final int POST = 1; public static final int GET = 2; private static final String CONTENT_TYPE_HEADER = "content-type"; private static final String CHARSET = "charset="; private String _connectionURL = null; // Actual connection private URLEncodedPostData _parameters; private String _errorMessage = null; private byte [] _response = null; private String _contentType = null; private int responseCode = -1; private String _encoding = null; private int request; /** * This utility class handles GETs and POSTs, via HTTP and HTTPS, * with various connection methods and options * and will even cope with Basic Authentication, and running in the background. * This means it is actually a bit complicated, sorry about that... * * An HTTP Request is created with these parameters * @param targetURL - the actual URL to be used * @param parameters - data to be added as POST / GET data. */ public HTTPRequestRunnable(String targetURL, URLEncodedPostData parameters) { super(); _connectionURL = targetURL; _parameters = parameters; _response = null; } /** * return response * @return data bytes */ public byte[] getResponse() { return _response; } /** * Retrieves the response as string in the proper encoding * @return The response as string using the proper encoding. */ public String getResponseAsString() { if (_response == null) { return null; } try { return new String(_response, _encoding); } catch (UnsupportedEncodingException e) { return new String(_response); } } /** * get Error Message * @return error Message */ public String getErrorMessage() { return _errorMessage; } /** * get Connection URL used * @return full connection URL used */ public String getConnectionURL() { return _connectionURL; } /** * get Content Type returned * @return Content Type */ public String getResponseContentType() { return _contentType; } public int getResponseCode() { return responseCode; } /** * Actual process which gets the response by trying the HTTP request */ public void run () { HttpConnection c = null; InputStream is = null; boolean tryAgain = false; OutputStream os = null; int rc = -1; try { String connectionMethod; do { tryAgain = false; logger.info("Contacting: " + _connectionURL + "."); String url = _connectionURL; if (request == GET) { url += "?" + _parameters.toString(); } HttpConnectionFactory factory = new HttpConnectionFactory(url); do { c = factory.getNextConnection(); switch (request) { case POST: if (_parameters != null && _parameters.size() > 0) { connectionMethod = HttpConnection.POST; c.setRequestMethod(connectionMethod); c.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_TYPE, _parameters.getContentType()); // Now create our 'posting' stuff byte [] postBytes = _parameters.getBytes(); logger.info(new String(postBytes)); // Note assumption post data is UTF-8, it might not be... c.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, Integer.toString(postBytes.length)); os = c.openOutputStream(); os.write(postBytes); os.flush(); os.close(); os = null; } break; case GET: connectionMethod = HttpConnection.GET; c.setRequestMethod(connectionMethod); break; default: // ??? logger.error("Unsupported http method " + request); break; } rc = c.getResponseCode(); responseCode = rc; } while (rc == HttpConnection.HTTP_FORBIDDEN); // Sometimes the BIS gets in the way when accessing non standard ports } while (tryAgain); } catch (IOException ioe) { String exMsg = "Unexpected Exception sending request - correct URL or Connection used."; // See if message just timed out if ( ioe instanceof InterruptedIOException ) { exMsg = "Message timed out - check connectivity."; } logger.error(exMsg); logger.error(ioe.toString() + "\n" + _connectionURL); return; } catch (NoMoreTransportsException e) { logger.error(e.toString() + "\n" + _connectionURL); return; } finally { if ( os != null ) { try { os.close(); } catch (Exception e) { } os = null; } } try { logger.info(getResponseToLog(rc,c)); if (rc != HttpConnection.HTTP_OK) { logErrorMessage(rc); return; } is = processResponse(c); } catch (Exception e) { logger.error("Unexpected Exception Receiving Response - try later."); logger.error(e.toString() + "\n" + _connectionURL); } finally { try { if (is != null) { is.close(); is = null; } } catch ( IOException ioe ) { } try { if (c != null) { c.close(); c = null; } } catch ( IOException ioe ) { } } } /** * Processes the response. * @param c Connection to the server. * @return is Response input * @throws IOException If an I/O error occurs. */ private InputStream processResponse(HttpConnection c) throws IOException { InputStream is; is = c.openInputStream(); // Get the ContentType in case the User wants to know _contentType = c.getType(); // Get the length and process the data int len = (int)c.getLength(); byte [] response; int bytesRead; if (len > 0) { // Length supplied - just read that amount int actual = 0; bytesRead = 0 ; response = new byte[len]; // We have found reading it in one go doesn't work as well as the following while ((bytesRead != len) && (actual != -1)) { actual = is.read(response, bytesRead, len - bytesRead); bytesRead += actual; } } else { // No length supplied - read until EOF response = IOUtilities.streamToBytes(is); bytesRead = response.length; } // Look for an encoding String encoding = c.getEncoding(); if (encoding == null) { // Not autodetected, look for a content-type HTTP header StringMatch matcher = new StringMatch(CHARSET, false); String contentType = c.getHeaderField(CONTENT_TYPE_HEADER); int pos = matcher.indexOf(contentType); if (pos >= 0) { encoding = contentType.substring(pos + CHARSET.length()); } } // now have, in response byte array, the data that we have received logger.info("Response (" + Integer.toString(response.length) + ")"); logger.info(byteToString(response)); // Note assumption response is UTF8 - it might not be .... returnResponse(response, encoding); return is; } /** * According to an error code, this method log a proper message. * @param rc Response code. */ private void logErrorMessage(int rc) { // We stop here - but try to give the user (and log) something useful. String errorMessage; if ( rc < 300 ) { // Processed without error, but why not OK? errorMessage = "Request not completed - try later. Code: " + rc + "."; } else if ( rc < 400 ) { // Redirection, which should be handled.... // Should update the requested address with a new one? errorMessage = "Requested address has changed - contact support. Code: " + rc + "."; } else if ( rc < 500 ) { // We did something wrong? Most commonly Server not up.... errorMessage = "Request not understood - try later. Code: " + rc + "."; } else if ( rc < 600 ) { // Server error... errorMessage = "Processing Error - try later. Code: " + rc + "."; } else { errorMessage = "Unexpected HTTP response - try later. Code: " + rc + "."; } logger.error(errorMessage); logger.error(_connectionURL); } /** * got a reply!!!! * @param reply - byte array with response in it * @param encoding The encoding of the reply */ private void returnResponse(byte [] reply, String encoding) { _response = reply; _encoding = encoding; } /** * Retrieve a String representation of the byte array. * @param item Array to parse * @return Byte array to string. */ private String byteToString (byte[] item) { StringBuffer hexString = new StringBuffer(); for (int i = 0; i < item.length; i++) { String token = Integer.toHexString(0xFF & item[i]); // Make sure each is exactly 2 chars long if (token.length() < 2) { hexString.append("0"); } hexString.append(token); } return hexString.toString(); } /** * Log response with the corresponding response code. * @param rc Response code. * @param c Connection from where to get the response to log * @return A message containing the response info. * @throws IOException When an I/O error occurs. */ private String getResponseToLog(int rc, HttpConnection c) throws IOException { StringBuffer sb = new StringBuffer(); sb.append("Response: " + Integer.toString(rc) + ", " + c.getResponseMessage() + "\n"); for ( int j = 0; j < 20; j++ ) { // Limit to 20 headers, just to reduce data sent to log. String key = c.getHeaderFieldKey(j); String field = c.getHeaderField(j); if ( key == null && j > 0 ) { // Stop when there are no more! break; } sb.append("Header: " + Integer.toString(j) + ", " + key + " : " + field + "\n"); if ( key.equalsIgnoreCase(HttpProtocolConstants.HEADER_LOCATION) ) { // Save redirect location in case we are redirected // Note potential bug, if we have more than 20 headers this will not be found! } } return sb.toString(); } /** * Set the request method. * @param request The request method. */ public void setRequest(int request) { this.request = request; } }