/* * GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Contact info: lobochief@users.sourceforge.net */ /* * Created on Nov 19, 2005 */ package com.nvarghese.beowulf.common.cobra.html.test; import java.awt.Image; import java.awt.Toolkit; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; import java.util.EventObject; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import com.nvarghese.beowulf.common.cobra.html.HttpRequest; import com.nvarghese.beowulf.common.cobra.html.ReadyStateChangeListener; import com.nvarghese.beowulf.common.cobra.html.UserAgentContext; import com.nvarghese.beowulf.common.cobra.util.EventDispatch; import com.nvarghese.beowulf.common.cobra.util.GenericEventListener; import com.nvarghese.beowulf.common.cobra.util.Urls; import com.nvarghese.beowulf.common.cobra.util.io.IORoutines; /** * The <code>SimpleHttpRequest</code> class implements the * {@link com.nvarghese.beowulf.common.cobra.html.HttpRequest} interface. The * <code>HttpRequest</code> implementation provided by this class is simple, * with no caching. It creates a new thread for each new asynchronous request. * * @author J. H. S. */ public class SimpleHttpRequest implements HttpRequest { private static final Logger logger = Logger.getLogger(SimpleHttpRequest.class.getName()); private int readyState; private int status; private String statusText; private byte[] responseBytes; private final UserAgentContext context; private final Proxy proxy; private boolean isAsync; private URL requestURL; protected String requestMethod; protected String requestUserName; protected String requestPassword; /** * The <code>URLConnection</code> is assigned to this field while it is * ongoing. */ protected java.net.URLConnection connection; /** * Response headers are set in this map after a response is received. */ protected java.util.Map responseHeadersMap; /** * Response headers are set in this string after a response is received. */ protected String responseHeaders; public SimpleHttpRequest(final UserAgentContext context, final java.net.Proxy proxy) { super(); this.context = context; this.proxy = proxy; } public synchronized int getReadyState() { return this.readyState; } private synchronized URL getRequestURL() { return this.requestURL; } private synchronized void setRequestURL(URL url) { this.requestURL = url; } public synchronized String getResponseText() { byte[] bytes = this.responseBytes; java.net.URLConnection connection = this.connection; String encoding = connection == null ? "ISO-8859-1" : Urls.getCharset(connection); if (encoding == null) { encoding = "ISO-8859-1"; } try { return bytes == null ? null : new String(bytes, encoding); } catch (UnsupportedEncodingException uee) { logger.log(Level.WARNING, "getResponseText(): Charset '" + encoding + "' did not work. Retrying with ISO-8859-1.", uee); try { return new String(bytes, "ISO-8859-1"); } catch (UnsupportedEncodingException uee2) { // Ignore this time return null; } } } public synchronized Document getResponseXML() { byte[] bytes = this.responseBytes; if (bytes == null) { return null; } java.io.InputStream in = new ByteArrayInputStream(bytes); try { return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); } catch (Exception err) { logger.log(Level.WARNING, "Unable to parse response as XML.", err); return null; } } public synchronized byte[] getResponseBytes() { return this.responseBytes; } /* * (non-Javadoc) * * @see org.xamjwg.html.HttpRequest#getResponseImage() */ public synchronized Image getResponseImage() { byte[] bytes = this.responseBytes; if (bytes == null) { return null; } return Toolkit.getDefaultToolkit().createImage(bytes); } public synchronized int getStatus() { return this.status; } public synchronized String getStatusText() { return this.statusText; } public void abort() { URLConnection c; synchronized (this) { c = this.connection; } if (c instanceof HttpURLConnection) { ((HttpURLConnection) c).disconnect(); } else if (c != null) { try { c.getInputStream().close(); } catch (IOException ioe) { ioe.printStackTrace(); } } } public synchronized String getAllResponseHeaders() { return this.responseHeaders; } public synchronized String getResponseHeader(final String headerName) { Map headers = this.responseHeadersMap; return headers == null ? null : (String) headers.get(headerName); } public void open(final String method, final String url) throws IOException { this.open(method, url, true); } public void open(final String method, final URL url) throws IOException { this.open(method, url, true, null, null); } public void open(final String method, final URL url, boolean asyncFlag) throws IOException { this.open(method, url, asyncFlag, null, null); } public void open(final String method, final String url, boolean asyncFlag) throws IOException { URL urlObj = Urls.createURL(null, url); this.open(method, urlObj, asyncFlag, null); } public void open(final String method, final java.net.URL url, final boolean asyncFlag, final String userName) throws IOException { this.open(method, url, asyncFlag, userName, null); } /** * Opens the request. Call {@link #send(String)} to complete it. * * @param method * The request method. * @param url * The request URL. * @param asyncFlag * Whether the request should be asynchronous. * @param userName * The user name of the request (not supported.) * @param password * The password of the request (not supported.) */ public void open(final String method, final URL url, boolean asyncFlag, final String userName, final String password) throws IOException { this.abort(); Proxy proxy = this.proxy; URLConnection c = proxy == null || proxy == Proxy.NO_PROXY ? url.openConnection() : url.openConnection(proxy); synchronized (this) { this.connection = c; this.isAsync = asyncFlag; this.requestMethod = method; this.setRequestURL(url); this.requestUserName = userName; this.requestPassword = password; } this.changeState(HttpRequest.STATE_LOADING, 0, null, null); } /** * Sends POST content, if any, and causes the request to proceed. * <p> * In the case of asynchronous requests, a new thread is created. * * @param content * POST content or <code>null</code> if there's no such content. */ public void send(final String content) throws java.io.IOException { final URL url = this.getRequestURL(); if (url == null) { throw new IOException("No URL has been provided."); } if (this.isAsync) { // Should use a thread pool instead new Thread("SimpleHttpRequest-" + url.getHost()) { public void run() { try { sendSync(content); } catch (Throwable thrown) { logger.log(Level.WARNING, "send(): Error in asynchronous request on " + url, thrown); } } }.start(); } else { sendSync(content); } } /** * This is the charset used to post data provided to {@link #send(String)}. * It returns "UTF-8" unless overridden. */ protected String getPostCharset() { return "UTF-8"; } /** * This is a synchronous implementation of {@link #send(String)} method * functionality. It may be overridden to change the behavior of the class. * * @param content * POST content if any. It may be <code>null</code>. * @throws IOException */ protected void sendSync(final String content) throws IOException { try { // FireFox posts a "loading" state twice as well. this.changeState(HttpRequest.STATE_LOADING, 0, null, null); URLConnection c; synchronized (this) { c = this.connection; } c.setRequestProperty("User-Agent", this.context.getUserAgent()); int istatus; String istatusText; java.io.InputStream err; if (c instanceof HttpURLConnection) { HttpURLConnection hc = (HttpURLConnection) c; String method = this.requestMethod; if (method == null) { throw new java.io.IOException("Null method."); } method = method.toUpperCase(); hc.setRequestMethod(method); if ("POST".equals(method) && content != null) { hc.setDoOutput(true); byte[] contentBytes = content.getBytes(this.getPostCharset()); hc.setFixedLengthStreamingMode(contentBytes.length); OutputStream out = hc.getOutputStream(); try { out.write(contentBytes); } finally { out.flush(); } } istatus = hc.getResponseCode(); istatusText = hc.getResponseMessage(); err = hc.getErrorStream(); } else { istatus = 0; istatusText = ""; err = null; } synchronized (this) { this.responseHeaders = this.getAllResponseHeaders(c); this.responseHeadersMap = c.getHeaderFields(); } this.changeState(HttpRequest.STATE_LOADED, istatus, istatusText, null); java.io.InputStream in = err == null ? c.getInputStream() : err; int contentLength = c.getContentLength(); // TODO: In the "interactive" state, some response text is supposed // to be available. this.changeState(HttpRequest.STATE_INTERACTIVE, istatus, istatusText, null); byte[] bytes = IORoutines.load(in, contentLength == -1 ? 4096 : contentLength); this.changeState(HttpRequest.STATE_COMPLETE, istatus, istatusText, bytes); } finally { synchronized (this) { this.connection = null; } } } private final EventDispatch readyEvent = new EventDispatch(); public void addReadyStateChangeListener(final ReadyStateChangeListener listener) { readyEvent.addListener(new GenericEventListener() { public void processEvent(final EventObject event) { listener.readyStateChanged(); } }); } private void changeState(final int readyState, final int status, final String statusMessage, final byte[] bytes) { synchronized (this) { this.readyState = readyState; this.status = status; this.statusText = statusMessage; this.responseBytes = bytes; } this.readyEvent.fireEvent(null); } private String getAllResponseHeaders(final URLConnection c) { int idx = 0; String value; StringBuffer buf = new StringBuffer(); while ((value = c.getHeaderField(idx)) != null) { String key = c.getHeaderFieldKey(idx); buf.append(key); buf.append(": "); buf.append(value); idx++; } return buf.toString(); } @Override public void setRequestHeader(final String headerName, final String value) { // TODO Auto-generated method stub } @Override public boolean wasSend() { // TODO Auto-generated method stub return false; } }