package com.rapidftr.net; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import javax.microedition.io.HttpConnection; import com.sun.me.web.path.Result; import com.sun.me.web.request.Arg; import com.sun.me.web.request.Part; import com.sun.me.web.request.PostData; import com.sun.me.web.request.Request; import com.sun.me.web.request.RequestListener; import com.sun.me.web.request.Response; public class HttpGateway { private static final int BUFFER_SIZE = 512; private static final boolean DEBUG = true; public static final String UTF8_CHARSET = "utf-8"; private ConnectionFactory connectionFactory; public HttpGateway(ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } public Response perform(Request request) throws IOException { final StringBuffer args = new StringBuffer(); final Arg[] inputArgs = request.getInputArgs(); if (inputArgs != null) { if (inputArgs.length > 0) { for (int i = 0; i < inputArgs.length; i++) { if (inputArgs[i] != null) { args.append(encode(inputArgs[i].getKey())); args.append("="); args.append(encode(inputArgs[i].getValue())); if (i + 1 < inputArgs.length && inputArgs[i + 1] != null) { args.append("&"); } } } } } final StringBuffer location = new StringBuffer(request.getUrl()); final String method = request.getMethod(); if (HttpConnection.GET.equals(method) && args.length() > 0) { location.append("?"); location.append(args.toString()); } HttpConnection conn = null; try { conn = connectionFactory.openConnection(location.toString()); conn.setRequestMethod(method); Arg[] httpArgs = request.getHttpArgs(); if (httpArgs != null) { for (int i = 0; i < httpArgs.length; i++) { if (httpArgs[i] != null) { final String value = httpArgs[i].getValue(); if (value != null) { conn.setRequestProperty(httpArgs[i].getKey(), value); } } } } if (HttpConnection.POST.equals(method)) { OutputStream os = null; try { os = conn.openOutputStream(); writePostData(request, args, os); } finally { if (os != null) { try { os.close(); } catch (IOException ignore) { } } } } Response response = new Response(); copyResponseHeaders(conn, response); response.setResponseCode(conn.getResponseCode()); if (response.getResponseCode() != HttpConnection.HTTP_OK) { } processContentType(conn, response); readResponse(conn, response, request.getListener(), request.getContext()); return response; } finally { if (conn != null) { conn.close(); } } } private void writePostData(final Request request, final StringBuffer args, final OutputStream os) throws IOException { int totalToSend = 0; PostData multiPart = request.getPostData(); RequestListener listener = request.getListener(); Object context = request.getContext(); if (multiPart != null) { final byte[] multipartBoundaryBits = multiPart.getBoundary().getBytes(); final byte[] newline = "\r\n".getBytes(); final byte[] dashdash = "--".getBytes(); // estimate totalBytesToSend final Part[] parts = multiPart.getParts(); for (int i = 0; i < parts.length; i++) { final Arg[] headers = parts[i].getHeaders(); for (int j = 0; j < headers.length; j++) { totalToSend += headers[j].getKey().getBytes().length; totalToSend += headers[j].getValue().getBytes().length; totalToSend += multipartBoundaryBits.length + dashdash.length + 3 * newline.length; } totalToSend += parts[i].getData().length; } // closing boundary marker totalToSend += multipartBoundaryBits.length + 2 * dashdash.length + 2 * newline.length; for (int i = 0; i < parts.length; i++) { write(os, dashdash, totalToSend, listener, context); write(os, multipartBoundaryBits, totalToSend, listener, context); write(os, newline, totalToSend, listener, context); boolean wroteAtleastOneHeader = false; final Arg[] headers = parts[i].getHeaders(); for (int j = 0; j < headers.length; j++) { write(os, (headers[j].getKey() + ": " + headers[j].getValue()).getBytes(), totalToSend, listener, context); write(os, newline, totalToSend, listener, context); wroteAtleastOneHeader = true; } if (wroteAtleastOneHeader) { write(os, newline, totalToSend, listener, context); } write(os, parts[i].getData(), totalToSend, listener, context); write(os, newline, totalToSend, listener, context); } // closing boundary marker // write(os, newline); write(os, dashdash, totalToSend, listener, context); write(os, multipartBoundaryBits, totalToSend, listener, context); write(os, dashdash, totalToSend, listener, context); write(os, newline, totalToSend, listener, context); } else if (args != null) { final byte[] argBytes = args.toString().getBytes(); totalToSend = argBytes.length; write(os, argBytes, totalToSend, listener, context); } else { throw new IOException("No data to POST - either input args or multipart must be non-null"); } } // data may be large, send in chunks while reporting progress and checking for interruption private void write(final OutputStream os, final byte[] data, int totalToSend, RequestListener listener, Object context) throws IOException { int sent = 0; // optimization if a small amount of data is being sent if (data.length <= BUFFER_SIZE) { os.write(data); sent += data.length; if (listener != null) { try { listener.writeProgress(context, sent, totalToSend); } catch (Throwable th) { if (DEBUG) { System.err.println("Uncaught throwable in listener: "); th.printStackTrace(); } } } } else { int offset = 0; int length = 0; do { length = Math.min(BUFFER_SIZE, data.length - offset); if (length > 0) { os.write(data, offset, length); offset += length; sent += length; if (listener != null) { try { listener.writeProgress(context, sent, totalToSend); } catch (Throwable th) { if (DEBUG) { System.err.println("Uncaught throwable in listener: "); th.printStackTrace(); } } } } } while (length > 0); } } private void processContentType(final HttpConnection conn, final Response response) throws IOException { response.setContentType(conn.getHeaderField(Arg.CONTENT_TYPE)); if (response.getContentType() == null) { // assume UTF-8 and XML if not specified response.setContentType(Result.APPLICATION_XML_CONTENT_TYPE); response.setCharset(UTF8_CHARSET); return; } final int semi = response.getContentType().indexOf(';'); if (semi >= 0) { response.setCharset(response.getContentType().substring(semi + 1).trim()); final int eq = response.getCharset().indexOf('='); if (eq < 0) { throw new IOException("Missing charset value: " + response.getCharset()); } response.setCharset(unquote(response.getCharset().substring(eq + 1).trim())); response.setContentType(response.getContentType().substring(0, semi).trim()); } if (response.getCharset() != null && !UTF8_CHARSET.equals(response.getCharset().toLowerCase())) { throw new IOException("Unsupported charset: " + response.getCharset()); } } private static String unquote(final String str) { if (str.startsWith("\"") && str.endsWith("\"") || str.startsWith("'") && str.endsWith("'")) { return str.substring(1, str.length() - 1); } return str; } private void readResponse(final HttpConnection conn, final Response response, RequestListener listener, Object context) throws IOException { int totalToReceive = conn.getHeaderFieldInt(Arg.CONTENT_LENGTH, 0); int received = 0; final byte[] cbuf = new byte[BUFFER_SIZE]; ByteArrayOutputStream bos = null; InputStream is = null; try { is = conn.openInputStream(); bos = new ByteArrayOutputStream(); int nread = 0; while ((nread = is.read(cbuf)) > 0) { bos.write(cbuf, 0, nread); received += nread; if (listener != null) { try { listener.readProgress(context, received, totalToReceive); } catch (Throwable th) { if (DEBUG) { System.err.println("Uncaught throwable in listener: "); th.printStackTrace(); } } } } } finally { if (is != null) { is.close(); } if (bos != null) { bos.close(); } } if (response.getContentType().startsWith(Result.IMAGE_CONTENT_TYPE_PATTERN)) { response.setResult(Result.fromContent(bos.toByteArray(), response.getContentType())); } else { response.setResult(Result.fromContent(bos.toString().trim(), response.getContentType())); } } private void copyResponseHeaders(final HttpConnection conn, final Response response) throws IOException { // pass 1 - count the number of headers int headerCount = 0; for (int i = 0; i < Short.MAX_VALUE; i++) { final String key = conn.getHeaderFieldKey(i); final String val = conn.getHeaderField(i); if (key == null || val == null) { break; } else { headerCount++; } } Arg[] headers = new Arg[headerCount]; // pass 2 - now copy the headers for (int i = 0; i < Short.MAX_VALUE; i++) { final String key = conn.getHeaderFieldKey(i); final String val = conn.getHeaderField(i); if (key == null || val == null) { break; } else { headers[i] = new Arg(key, val); } } response.setHeaders(headers); } // TODO: verify correctness private static String encode(final String str) throws UnsupportedEncodingException { if (str == null) { return null; } final byte[] buf = str.getBytes("utf-8"); final StringBuffer sbuf = new StringBuffer(buf.length * 3); for (int i = 0; i < buf.length; i++) { final byte ch = buf[i]; if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-' || ch == '_' || ch == '.' || ch == '~')) { sbuf.append((char) ch); } else if (ch == ' ') { sbuf.append('+'); } else { sbuf.append('%'); sbuf.append(Integer.toHexString(ch & 0xff)); } } return sbuf.toString(); } }