// 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());
}
}
}