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