/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program 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 General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ /* * Created on Nov 19, 2005 */ package org.lobobrowser.html.test; import java.awt.Image; import java.awt.Toolkit; import java.beans.PropertyChangeListener; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; 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.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.xml.parsers.DocumentBuilderFactory; import org.lobobrowser.http.AbstractBean; import org.lobobrowser.http.Header; import org.lobobrowser.http.Method; import org.lobobrowser.http.ReadyState; import org.lobobrowser.http.Request; import org.lobobrowser.http.UserAgentContext; import org.lobobrowser.util.SSLCertificate; import org.lobobrowser.util.Urls; import org.lobobrowser.util.io.IORoutines; import org.w3c.dom.Document; /** * The <code>SimpleHttpRequest</code> class implements the * {@link org.lobobrowser.http.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 extends AbstractBean { /** The Constant logger. */ private static final Logger logger = LogManager.getLogger(SimpleHttpRequest.class); /** The ready state. */ private ReadyState readyState; /** The status. */ private int status; /** The status text. */ private String statusText; /** The response bytes. */ private byte[] responseBytes; /** The context. */ private final UserAgentContext context; /** The proxy. */ private final Proxy proxy; /** The req. */ private Request req; /** The is async. */ private boolean isAsync; /** * The <code>URLConnection</code> is assigned to this field while it is * ongoing. */ protected URLConnection connection; /** * Response headers are set in this map after a response is received. */ protected Map responseHeadersMap; /** * Response headers are set in this string after a response is received. */ protected String responseHeaders; /** * Instantiates a new simple http request. * * @param context * the context * @param proxy * the proxy */ public SimpleHttpRequest(UserAgentContext context, Proxy proxy) { super(); this.context = context; this.proxy = proxy; req = new Request(); } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getReadyState() */ /** * Gets the ready state. * * @return the ready state */ public synchronized ReadyState getReadyState() { return this.readyState; } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getResponseText() */ /** * Gets the response text. * * @return the response text */ public synchronized String getResponseText() { byte[] bytes = this.responseBytes; java.net.URLConnection connection = this.connection; String encoding = connection == null ? "UTF-8" : Urls.getCharset(connection); if (encoding == null) { encoding = "UTF-8"; } try { return bytes == null ? null : new String(bytes, encoding); } catch (UnsupportedEncodingException uee) { logger.error( "getResponseText(): Charset '" + encoding + "' did not work. Retrying with UTF-8.", uee); try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException uee2) { // Ignore this time return null; } } } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getResponseXML() */ /** * Gets the response xml. * * @return the response xml */ public synchronized Document getResponseXML() { byte[] bytes = this.responseBytes; if (bytes == null) { return null; } InputStream in = new ByteArrayInputStream(bytes); try { return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); } catch (Exception err) { logger.error("Unable to parse response as XML.", err); return null; } } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getResponseBytes() */ /** * Gets the response bytes. * * @return the response bytes */ public synchronized byte[] getResponseBytes() { return this.responseBytes; } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getResponseImage() */ /** * Gets the response image. * * @return the response image */ public synchronized Image getResponseImage() { byte[] bytes = this.responseBytes; if (bytes == null) { return null; } return Toolkit.getDefaultToolkit().createImage(bytes); } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getStatus() */ /** * Gets the status. * * @return the status */ public synchronized int getStatus() { return this.status; } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getStatusText() */ /** * Gets the status text. * * @return the status text */ public synchronized String getStatusText() { return this.statusText; } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#abort() */ 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) { logger.error(ioe.getMessage()); } } } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getAllResponseHeaders() */ /** * Gets the all response headers. * * @return the all response headers */ public synchronized String getAllResponseHeaders() { return this.responseHeaders; } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#getResponseHeader(java.lang.String) */ public synchronized String getResponseHeader(String headerName) { Map headers = this.responseHeadersMap; return headers == null ? null : (String) headers.get(headerName); } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#open(java.lang.String, * java.net.URL) */ public void open(Method method, URL url) { this.open(method, url, true, null, null); } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#open(java.lang.String, * java.net.URL, boolean) */ public void open(Method method, URL url, boolean asyncFlag) { this.open(method, url, asyncFlag, null, 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 Method method, final URL url, boolean asyncFlag, final String userName, final String password) { this.abort(); Proxy proxy = this.proxy; SSLCertificate.setCertificate(); URLConnection c; try { c = (proxy == null) || (proxy == Proxy.NO_PROXY) ? url.openConnection() : url.openConnection(proxy); synchronized (this) { this.connection = c; this.isAsync = asyncFlag; req.setUsername(userName); req.setPassword(password); req.setUrl(url.toString()); if (method.equals(Method.GET)) { req.setMethod(Method.GET); } else { req.setMethod(Method.POST); } } this.changeState(ReadyState.LOADING, 0, null, null); } catch (IOException e) { e.printStackTrace(); } } /** * 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) { try { final URL url = new URL(req.getUrl()); if (this.isAsync) { // Should use a thread pool instead new Thread("SimpleHttpRequest-" + url.getHost()) { @Override public void run() { try { sendSync(content); } catch (Throwable thrown) { logger.error("send(): Error in asynchronous request on " + url, thrown); } } }.start(); } else { sendSync(content); } } catch (IOException e) { e.printStackTrace(); } } /** * Gets the post charset. * * @return the post charset */ 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 * Signals that an I/O exception has occurred. */ protected void sendSync(String content) throws IOException { try { // FireFox posts a "loading" state twice as well. this.changeState(ReadyState.LOADING, 0, null, null); URLConnection c; synchronized (this) { c = this.connection; } c.setRequestProperty("User-Agent", this.context.getUserAgent()); int istatus; String istatusText; InputStream err; if (c instanceof HttpURLConnection) { HttpURLConnection hc = (HttpURLConnection) c; String method = req.getMethod(); 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(ReadyState.LOADED, istatus, istatusText, null); InputStream in = err == null ? c.getInputStream() : err; int contentLength = c.getContentLength(); this.changeState(ReadyState.INTERACTIVE, istatus, istatusText, null); byte[] bytes = IORoutines.load(in, contentLength == -1 ? 4096 : contentLength); this.changeState(ReadyState.COMPLETE, istatus, istatusText, bytes); } finally { synchronized (this) { this.connection = null; } } } public void addReadyStateChangeListener(PropertyChangeListener listener) { super.addPropertyChangeListener("readyState", listener); } public void removeReadyStateChangeListener(PropertyChangeListener listener) { super.removePropertyChangeListener("readyState", listener); } /** * Gets the ready state change listeners. * * @return the ready state change listeners */ public PropertyChangeListener[] getReadyStateChangeListeners() { return super.getPropertyChangeListeners("readyState"); } /** * Change state. * * @param readyState * the ready state * @param status * the status * @param statusMessage * the status message * @param bytes * the bytes */ private void changeState(ReadyState readyState, int status, String statusMessage, byte[] bytes) { synchronized (this) { this.readyState = readyState; this.status = status; this.statusText = statusMessage; this.responseBytes = bytes; } } /** * Gets the all response headers. * * @param c * the c * @return the all response headers */ private String getAllResponseHeaders(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(); } /* * (non-Javadoc) * * @see org.lobobrowser.html.HttpRequest#setRequestHeader(java.lang.String, * java.lang.String) */ /** * Specifies a request header for the HTTP request. * * @param header * @param value */ public void setRequestHeader(String header, String value) { if (getReadyState() != ReadyState.LOADING) { throw new IllegalStateException( "The AsyncHttpRequest must be opened prior to " + "setting a request header"); } if ((header == null) || (value == null)) { throw new IllegalArgumentException("Neither the header, nor value, may be null"); } if (header.equalsIgnoreCase("Accept-Charset") || header.equalsIgnoreCase("Accept-Encoding") || header.equalsIgnoreCase("Content-Length") || header.equalsIgnoreCase("Expect") || header.equalsIgnoreCase("Date") || header.equalsIgnoreCase("Host") || header.equalsIgnoreCase("Keep-Alive") || header.equalsIgnoreCase("Referer") || header.equalsIgnoreCase("TE") || header.equalsIgnoreCase("Trailer") || header.equalsIgnoreCase("Transfer-Encoding") || header.equalsIgnoreCase("Upgrade")) { return; } if (header.equalsIgnoreCase("Authorization") || header.equalsIgnoreCase("Content-Base") || header.equalsIgnoreCase("Content-Location") || header.equalsIgnoreCase("Content-MD5") || header.equalsIgnoreCase("Content-Range") || header.equalsIgnoreCase("Content-Type") || header.equalsIgnoreCase("Content-Version") || header.equalsIgnoreCase("Delta-Base") || header.equalsIgnoreCase("Depth") || header.equalsIgnoreCase("Destination") || header.equalsIgnoreCase("ETag") || header.equalsIgnoreCase("Expect") || header.equalsIgnoreCase("From") || header.equalsIgnoreCase("If-Modified-Since") || header.equalsIgnoreCase("If-Range") || header.equalsIgnoreCase("If-Unmodified-Since") || header.equalsIgnoreCase("Max-Forwards") || header.equalsIgnoreCase("MIME-Version") || header.equalsIgnoreCase("Overwrite") || header.equalsIgnoreCase("Proxy-Authorization") || header.equalsIgnoreCase("SOAPAction") || header.equalsIgnoreCase("Timeout")) { // replace the current header, if any for (Header h : req.getHeaders()) { if (h.getName().equalsIgnoreCase(header)) { req.removeHeader(h); req.setHeader(new Header(header, value)); break; } } } else { // append the value to the header, if one is already specified. // Else, // just add it as a new header boolean appended = false; for (Header h : req.getHeaders()) { if (h.getName().equalsIgnoreCase(header)) { req.removeHeader(h); req.setHeader(new Header(header, h.getValue() + ", " + value)); appended = true; break; } } if (!appended) { req.setHeader(new Header(header, value)); } } } }