// HTTPDemon.java // ----------------------- // (C) by Michael Peter Christen; mc@yacy.net // first published on http://www.anomic.de // Frankfurt, Germany, 2004 // // $LastChangedDate$ // $LastChangedRevision$ // $LastChangedBy$ // // 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 version 2 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 Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package net.yacy.server.http; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import net.yacy.cora.document.encoding.UTF8; import net.yacy.cora.document.id.DigestURL; import net.yacy.cora.protocol.HeaderFramework; import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.protocol.ResponseHeader; import net.yacy.cora.util.ByteBuffer; import net.yacy.cora.util.ConcurrentLog; import net.yacy.kelondro.util.FileUtils; import net.yacy.search.Switchboard; import net.yacy.search.SwitchboardConstants; import net.yacy.server.serverObjects; /** * Instances of this class can be passed as argument to the serverCore. * The generic server dispatches HTTP commands and calls the * method GET, HEAD or POST in this class * these methods parse the command line and decide wether to call * a proxy servlet or a file server servlet */ public final class HTTPDemon { private static final int ERRORCASE_MESSAGE = 4; private static final int ERRORCASE_FILE = 5; // static objects private static volatile Switchboard switchboard = Switchboard.getSwitchboard(); static final void sendRespondError( final HashMap<String, Object> conProp, final OutputStream respond, final int errorcase, final int httpStatusCode, final String httpStatusText, final String detailedErrorMsg, final Throwable stackTrace ) throws IOException { sendRespondError( conProp, respond, errorcase, httpStatusCode, httpStatusText, detailedErrorMsg, null, null, stackTrace, null ); } static final void sendRespondError( final HashMap<String, Object> conProp, final OutputStream respond, final int httpStatusCode, final String httpStatusText, final File detailedErrorMsgFile, final serverObjects detailedErrorMsgValues, final Throwable stackTrace ) throws IOException { sendRespondError( conProp, respond, 5, httpStatusCode, httpStatusText, null, detailedErrorMsgFile, detailedErrorMsgValues, stackTrace, null ); } private static final void sendRespondError( final HashMap<String, Object> conProp, final OutputStream respond, final int errorcase, final int httpStatusCode, String httpStatusText, final String detailedErrorMsgText, final Object detailedErrorMsgFile, final serverObjects detailedErrorMsgValues, final Throwable stackTrace, ResponseHeader header ) throws IOException { FileInputStream fis = null; ByteArrayOutputStream o = null; try { // setting the proper http status message String httpVersion = (String) conProp.get(HeaderFramework.CONNECTION_PROP_HTTP_VER); if (httpVersion == null) httpVersion = HeaderFramework.HTTP_VERSION_1_1; if ((httpStatusText == null)||(httpStatusText.length()==0)) { //http1_1 includes http1_0 messages if (HeaderFramework.http1_1.containsKey(Integer.toString(httpStatusCode))) httpStatusText = HeaderFramework.http1_1.get(Integer.toString(httpStatusCode)); else httpStatusText = "Unknown"; } HttpServletRequest origrequest = (HttpServletRequest) conProp.get(HeaderFramework.CONNECTION_PROP_CLIENT_HTTPSERVLETREQUEST); final String method = origrequest.getMethod(); DigestURL url = (DigestURL) conProp.get(HeaderFramework.CONNECTION_PROP_DIGESTURL); // set rewrite values final serverObjects tp = new serverObjects(); tp.put("peerName", (switchboard.peers == null) ? "" : switchboard.peers.myName()); tp.put("errorMessageType", Integer.toString(errorcase)); tp.put("httpStatus", Integer.toString(httpStatusCode) + " " + httpStatusText); tp.put("requestMethod", method); tp.put("requestURL", url.toString()); switch (errorcase) { case ERRORCASE_FILE: tp.put("errorMessageType_file", (detailedErrorMsgFile == null) ? "" : detailedErrorMsgFile.toString()); if ((detailedErrorMsgValues != null) && !detailedErrorMsgValues.isEmpty()) { // rewriting the value-names and add the proper name prefix: for (final Entry<String, String> entry: detailedErrorMsgValues.entrySet()) { tp.put("errorMessageType_" + entry.getKey(), entry.getValue()); } } break; case ERRORCASE_MESSAGE: default: tp.put("errorMessageType_detailedErrorMsg", (detailedErrorMsgText == null) ? "" : detailedErrorMsgText.replaceAll("\n", "<br />")); break; } // building the stacktrace if (stackTrace != null) { tp.put("printStackTrace", "1"); final ByteBuffer errorMsg = new ByteBuffer(100); final PrintStream printStream = new PrintStream(errorMsg); stackTrace.printStackTrace(printStream); tp.put("printStackTrace_exception", stackTrace.toString()); tp.put("printStackTrace_stacktrace", UTF8.String(errorMsg.getBytes())); printStream.close(); } else { tp.put("printStackTrace", "0"); } // Generated Tue, 23 Aug 2005 11:19:14 GMT by brain.wg (squid/2.5.STABLE3) // adding some system information final String systemDate = HeaderFramework.formatRFC1123(new Date()); tp.put("date", systemDate); // rewrite the file final File htRootPath = new File(switchboard.getAppPath(), switchboard.getConfig(SwitchboardConstants.HTROOT_PATH,SwitchboardConstants.HTROOT_PATH_DEFAULT)); TemplateEngine.writeTemplate( "/proxymsg/error.html", fis = new FileInputStream(new File(htRootPath, "/proxymsg/error.html")), o = new ByteArrayOutputStream(512), tp ); final byte[] result = o.toByteArray(); o.close(); o = null; if (header == null) header = new ResponseHeader(httpStatusCode); header.put(HeaderFramework.CONNECTION_PROP_PROXY_RESPOND_STATUS, Integer.toString(httpStatusCode)); header.put(HeaderFramework.DATE, systemDate); header.put(HeaderFramework.CONTENT_TYPE, "text/html"); header.put(HeaderFramework.CONTENT_LENGTH, Integer.toString(result.length)); header.put(HeaderFramework.PRAGMA, "no-cache, no-store"); sendRespondHeader(conProp,respond,httpVersion,httpStatusCode,httpStatusText,header); if (! method.equals(HeaderFramework.METHOD_HEAD)) { // write the array to the client FileUtils.copy(result, respond); } respond.flush(); } finally { if (fis != null) try { fis.close(); } catch (final Exception e) { ConcurrentLog.logException(e); } if (o != null) try { o.close(); } catch (final Exception e) { ConcurrentLog.logException(e); } } } static final void sendRespondHeader( final HashMap<String, Object> conProp, final OutputStream respond, final String httpVersion, final int httpStatusCode, final ResponseHeader header ) throws IOException { sendRespondHeader(conProp,respond,httpVersion,httpStatusCode,null,header); } static final void sendRespondHeader( final HashMap<String, Object> conProp, final OutputStream respond, String httpVersion, final int httpStatusCode, String httpStatusText, ResponseHeader responseHeader ) throws IOException { if (respond == null) throw new NullPointerException("The outputstream must not be null."); if (conProp == null) throw new NullPointerException("The connection property structure must not be null."); if (httpVersion == null) httpVersion = (String) conProp.get(HeaderFramework.CONNECTION_PROP_HTTP_VER); if (httpVersion == null) httpVersion = HeaderFramework.HTTP_VERSION_1_1; if (responseHeader == null) responseHeader = new ResponseHeader(httpStatusCode); try { if ((httpStatusText == null)||(httpStatusText.length()==0)) { if (HeaderFramework.http1_1.containsKey(Integer.toString(httpStatusCode))) //http1_1 includes http1_0 messages httpStatusText = HeaderFramework.http1_1.get(Integer.toString(httpStatusCode)); else httpStatusText = "Unknown"; } final StringBuilder header = new StringBuilder(560); // "HTTP/0.9" does not have a status line or header in the response if (! httpVersion.toUpperCase().equals(HeaderFramework.HTTP_VERSION_0_9)) { // write status line header.append(httpVersion).append(" ") .append(Integer.toString(httpStatusCode)).append(" ") .append(httpStatusText).append("\r\n"); // prepare header if (!responseHeader.containsKey(HeaderFramework.DATE)) responseHeader.put(HeaderFramework.DATE, HeaderFramework.formatRFC1123(new Date())); if (!responseHeader.containsKey(HeaderFramework.CONTENT_TYPE)) responseHeader.put(HeaderFramework.CONTENT_TYPE, "text/html; charset=UTF-8"); // fix this if (!responseHeader.containsKey(RequestHeader.CONNECTION) && conProp.containsKey(HeaderFramework.CONNECTION_PROP_PERSISTENT)) responseHeader.put(RequestHeader.CONNECTION, (String) conProp.get(HeaderFramework.CONNECTION_PROP_PERSISTENT)); if (!responseHeader.containsKey(RequestHeader.PROXY_CONNECTION) && conProp.containsKey(HeaderFramework.CONNECTION_PROP_PERSISTENT)) responseHeader.put(RequestHeader.PROXY_CONNECTION, (String) conProp.get(HeaderFramework.CONNECTION_PROP_PERSISTENT)); if (conProp.containsKey(HeaderFramework.CONNECTION_PROP_PERSISTENT) && conProp.get(HeaderFramework.CONNECTION_PROP_PERSISTENT).equals("keep-alive") && !responseHeader.containsKey(HeaderFramework.TRANSFER_ENCODING) && !responseHeader.containsKey(HeaderFramework.CONTENT_LENGTH)) responseHeader.put(HeaderFramework.CONTENT_LENGTH, "0"); //read custom headers if (responseHeader.getCookiesEntries() != null) { for (Cookie c : responseHeader.getCookiesEntries()) { //Append user properties to the main String //TODO: Should we check for user properites. What if they intersect properties that are already in header? header.append(HeaderFramework.SET_COOKIE+": "+c.getName()).append("=").append(c.getValue()).append(";\r\n"); } } // write header final Iterator<String> i = responseHeader.keySet().iterator(); String key; char tag; int count; while (i.hasNext()) { key = i.next(); tag = key.charAt(0); if ((tag != '*') && (tag != '#')) { // '#' in key is reserved for proxy attributes as artificial header values count = responseHeader.keyCount(key); for (int j = 0; j < count; j++) { header.append(key).append(": ").append(responseHeader.getSingle(key, j)).append("\r\n"); } } } // end header header.append("\r\n"); // sending headers to the client respond.write(UTF8.getBytes(header.toString())); // flush stream respond.flush(); } conProp.put(HeaderFramework.CONNECTION_PROP_PROXY_RESPOND_HEADER,responseHeader); conProp.put(HeaderFramework.CONNECTION_PROP_PROXY_RESPOND_STATUS,Integer.toString(httpStatusCode)); } catch (final Exception e) { // any interruption may be caused be network error or because the user has closed // the windows during transmission. We simply pass it as IOException throw new IOException(e.getMessage()); } } }